diff --git a/editor/index.html b/editor/index.html
index 0dd382b50546e1..20c598695122b5 100644
--- a/editor/index.html
+++ b/editor/index.html
@@ -72,6 +72,8 @@
import { AnimationResizer } from './js/AnimationResizer.js';
import { Animation } from './js/Animation.js';
+ import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
window.URL = window.URL || window.webkitURL;
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
@@ -82,6 +84,8 @@
window.editor = editor; // Expose editor to Console
window.THREE = THREE; // Expose THREE to APP Scripts and Console
+ THREE.ObjectLoader.registerGeometry( 'TextGeometry', TextGeometry );
+
const viewport = new Viewport( editor );
document.body.appendChild( viewport.dom );
diff --git a/editor/js/Menubar.Add.js b/editor/js/Menubar.Add.js
index 41b2a33928f72e..b49c18a23c3499 100644
--- a/editor/js/Menubar.Add.js
+++ b/editor/js/Menubar.Add.js
@@ -4,6 +4,9 @@ import { UIPanel, UIRow } from './libs/ui.js';
import { AddObjectCommand } from './commands/AddObjectCommand.js';
+import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
function MenubarAdd( editor ) {
const strings = editor.strings;
@@ -267,6 +270,43 @@ function MenubarAdd( editor ) {
} );
meshSubmenu.add( option );
+ // Mesh / Text
+
+ option = new UIRow();
+ option.setClass( 'option' );
+ option.setTextContent( strings.getKey( 'menubar/add/text' ) );
+ option.onClick( function () {
+
+ const loader = new FontLoader();
+ loader.load( '../examples/fonts/helvetiker_bold.typeface.json', function ( font ) {
+
+ const text = 'THREE.JS';
+
+ const geometry = new TextGeometry( text, {
+ text: text,
+ font,
+ size: 1,
+ depth: 0.5,
+ curveSegments: 4,
+
+ bevelEnabled: false,
+ bevelThickness: 0.1,
+ bevelSize: 0.01,
+ bevelOffset: 0,
+ bevelSegments: 3
+
+ } );
+
+ const mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
+ mesh.name = 'Text';
+
+ editor.execute( new AddObjectCommand( editor, mesh ) );
+
+ } );
+
+ } );
+ meshSubmenu.add( option );
+
// Mesh / Torus
option = new UIRow();
diff --git a/editor/js/Sidebar.Geometry.TextGeometry.js b/editor/js/Sidebar.Geometry.TextGeometry.js
new file mode 100644
index 00000000000000..a22d510f12b03b
--- /dev/null
+++ b/editor/js/Sidebar.Geometry.TextGeometry.js
@@ -0,0 +1,136 @@
+import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+import { UIDiv, UIRow, UIText, UINumber, UIInteger, UIInput, UICheckbox } from './libs/ui.js';
+
+import { SetGeometryCommand } from './commands/SetGeometryCommand.js';
+
+function GeometryParametersPanel( editor, object ) {
+
+ const strings = editor.strings;
+
+ const container = new UIDiv();
+
+ const geometry = object.geometry;
+ const parameters = geometry.parameters.options;
+
+ // text
+
+ const textRow = new UIRow();
+ const text = new UIInput().setValue( parameters.text ).onChange( update );
+
+ textRow.add( new UIText( strings.getKey( 'sidebar/geometry/text_geometry/text' ) ).setClass( 'Label' ) );
+ textRow.add( text );
+
+ container.add( textRow );
+
+ // size
+
+ const sizeRow = new UIRow();
+ const size = new UINumber().setPrecision( 3 ).setValue( parameters.size ).onChange( update );
+
+ sizeRow.add( new UIText( strings.getKey( 'sidebar/geometry/text_geometry/size' ) ).setClass( 'Label' ) );
+ sizeRow.add( size );
+
+ container.add( sizeRow );
+
+ // depth
+
+ const depthRow = new UIRow();
+ const depth = new UINumber().setPrecision( 3 ).setValue( parameters.depth ).onChange( update );
+
+ depthRow.add( new UIText( strings.getKey( 'sidebar/geometry/text_geometry/depth' ) ).setClass( 'Label' ) );
+ depthRow.add( depth );
+
+ container.add( depthRow );
+
+ // curveSegments
+
+ const curveSegmentsRow = new UIRow();
+ const curveSegments = new UIInteger( parameters.curveSegments ).setRange( 1, Infinity ).onChange( update );
+
+ curveSegmentsRow.add( new UIText( strings.getKey( 'sidebar/geometry/text_geometry/curveseg' ) ).setClass( 'Label' ) );
+ curveSegmentsRow.add( curveSegments );
+
+ container.add( curveSegmentsRow );
+
+ // bevelEnabled
+
+ const bevelEnabledRow = new UIRow();
+ const bevelEnabled = new UICheckbox( parameters.bevelEnabled ).onChange( update );
+
+ bevelEnabledRow.add( new UIText( strings.getKey( 'sidebar/geometry/text_geometry/bevelenabled' ) ).setClass( 'Label' ) );
+ bevelEnabledRow.add( bevelEnabled );
+
+ container.add( bevelEnabledRow );
+
+ // bevelThickness
+
+ const bevelThicknessRow = new UIRow();
+ const bevelThickness = new UINumber( parameters.bevelThickness ).setPrecision( 3 ).setRange( 0, Infinity ).onChange( update );
+
+ bevelThicknessRow.add( new UIText( strings.getKey( 'sidebar/geometry/text_geometry/bevelthickness' ) ).setClass( 'Label' ) );
+ bevelThicknessRow.add( bevelThickness );
+
+ container.add( bevelThicknessRow );
+
+ // bevelSize
+
+ const bevelSizeRow = new UIRow();
+ const bevelSize = new UINumber( parameters.bevelSize ).setRange( 0, Infinity ).onChange( update );
+
+ bevelSizeRow.add( new UIText( strings.getKey( 'sidebar/geometry/text_geometry/bevelsize' ) ).setClass( 'Label' ) );
+ bevelSizeRow.add( bevelSize );
+
+ container.add( bevelSizeRow );
+
+ // bevelOffset
+
+ const bevelOffsetRow = new UIRow();
+ const bevelOffset = new UINumber( parameters.bevelOffset ).setRange( 0, Infinity ).onChange( update );
+
+ bevelOffsetRow.add( new UIText( strings.getKey( 'sidebar/geometry/text_geometry/bevelOffset' ) ).setClass( 'Label' ) );
+ bevelOffsetRow.add( bevelOffset );
+
+ container.add( bevelOffsetRow );
+
+
+ // bevelSegments
+
+ const bevelSegmentsRow = new UIRow();
+ const bevelSegments = new UIInteger( parameters.bevelSegments ).setRange( 0, Infinity ).onChange( update );
+
+ bevelSegmentsRow.add( new UIText( strings.getKey( 'sidebar/geometry/text_geometry/bevelseg' ) ).setClass( 'Label' ) );
+ bevelSegmentsRow.add( bevelSegments );
+
+ container.add( bevelSegmentsRow );
+
+ function update() {
+
+ const options = {
+
+ text: text.getValue(),
+ font: parameters.font,
+
+ size: size.getValue(),
+ depth: depth.getValue(),
+ curveSegments: curveSegments.getValue(),
+
+ bevelEnabled: bevelEnabled.getValue(),
+ bevelThickness: bevelThickness.getValue(),
+ bevelSize: bevelSize.getValue(),
+ bevelOffset: bevelOffset.getValue(),
+ bevelSegments: bevelSegments.getValue()
+
+ };
+
+ const geometry = new TextGeometry( options.text, options );
+
+ editor.execute( new SetGeometryCommand( editor, object, geometry ) );
+
+ }
+
+ return container;
+
+}
+
+export { GeometryParametersPanel };
diff --git a/editor/js/Strings.js b/editor/js/Strings.js
index 904c15d5995336..5a116bb30c6812 100644
--- a/editor/js/Strings.js
+++ b/editor/js/Strings.js
@@ -73,6 +73,7 @@ function Strings( config ) {
'menubar/add/mesh/icosahedron': 'بیست وجهی',
'menubar/add/mesh/octahedron': 'هشت وجهی',
'menubar/add/mesh/tetrahedron': 'چهار وجهی',
+ 'menubar/add/text': 'Text',
'menubar/add/mesh/torus': 'توروس (دونات)',
'menubar/add/mesh/tube': 'لوله',
'menubar/add/mesh/torusknot': 'torusknot',
@@ -242,6 +243,17 @@ function Strings( config ) {
'sidebar/geometry/ring_geometry/thetastart': 'شروع تتا',
'sidebar/geometry/ring_geometry/thetalength': 'طول تتا',
+ 'sidebar/geometry/text_geometry/text': 'Text',
+ 'sidebar/geometry/text_geometry/size': 'Font size',
+ 'sidebar/geometry/text_geometry/depth': 'Extrude depth',
+ 'sidebar/geometry/text_geometry/scale': 'Scale',
+ 'sidebar/geometry/text_geometry/curveseg': 'Curve segments',
+ 'sidebar/geometry/text_geometry/bevelenabled': 'Bevel enabled',
+ 'sidebar/geometry/text_geometry/bevelthickness': 'Bevel thickness',
+ 'sidebar/geometry/text_geometry/bevelsize': 'Bevel size',
+ 'sidebar/geometry/text_geometry/bevelOffset': 'Bevel offset',
+ 'sidebar/geometry/text_geometry/bevelseg': 'Bevel segments',
+
'sidebar/geometry/shape_geometry/curveSegments': 'بخش های منحنی',
'sidebar/geometry/shape_geometry/extrude': 'اکسترود کردن',
@@ -490,6 +502,7 @@ function Strings( config ) {
'menubar/add/mesh/icosahedron': 'Icosahedron',
'menubar/add/mesh/octahedron': 'Octahedron',
'menubar/add/mesh/tetrahedron': 'Tetrahedron',
+ 'menubar/add/text': 'Text',
'menubar/add/mesh/torus': 'Torus',
'menubar/add/mesh/tube': 'Tube',
'menubar/add/mesh/torusknot': 'TorusKnot',
@@ -659,6 +672,17 @@ function Strings( config ) {
'sidebar/geometry/ring_geometry/thetastart': 'Theta start',
'sidebar/geometry/ring_geometry/thetalength': 'Theta length',
+ 'sidebar/geometry/text_geometry/text': 'Text',
+ 'sidebar/geometry/text_geometry/size': 'Font size',
+ 'sidebar/geometry/text_geometry/depth': 'Extrude depth',
+ 'sidebar/geometry/text_geometry/scale': 'Scale',
+ 'sidebar/geometry/text_geometry/curveseg': 'Curve segments',
+ 'sidebar/geometry/text_geometry/bevelenabled': 'Bevel enabled',
+ 'sidebar/geometry/text_geometry/bevelthickness': 'Bevel thickness',
+ 'sidebar/geometry/text_geometry/bevelsize': 'Bevel size',
+ 'sidebar/geometry/text_geometry/bevelOffset': 'Bevel offset',
+ 'sidebar/geometry/text_geometry/bevelseg': 'Bevel segments',
+
'sidebar/geometry/shape_geometry/curveSegments': 'Curve Segments',
'sidebar/geometry/shape_geometry/extrude': 'Extrude',
@@ -908,6 +932,7 @@ function Strings( config ) {
'menubar/add/mesh/icosahedron': 'Icosaèdre',
'menubar/add/mesh/octahedron': 'Octaèdre',
'menubar/add/mesh/tetrahedron': 'Tétraèdre',
+ 'menubar/add/text': 'Text',
'menubar/add/mesh/torus': 'Torus',
'menubar/add/mesh/tube': 'Tube',
'menubar/add/mesh/torusknot': 'Noeud Torus',
@@ -1077,6 +1102,17 @@ function Strings( config ) {
'sidebar/geometry/ring_geometry/thetastart': 'Début Thêta',
'sidebar/geometry/ring_geometry/thetalength': 'Longueur Thêta',
+ 'sidebar/geometry/text_geometry/text': 'Text',
+ 'sidebar/geometry/text_geometry/size': 'Font size',
+ 'sidebar/geometry/text_geometry/depth': 'Extrude depth',
+ 'sidebar/geometry/text_geometry/scale': 'Scale',
+ 'sidebar/geometry/text_geometry/curveseg': 'Curve segments',
+ 'sidebar/geometry/text_geometry/bevelenabled': 'Bevel enabled',
+ 'sidebar/geometry/text_geometry/bevelthickness': 'Bevel thickness',
+ 'sidebar/geometry/text_geometry/bevelsize': 'Bevel size',
+ 'sidebar/geometry/text_geometry/bevelOffset': 'Bevel offset',
+ 'sidebar/geometry/text_geometry/bevelseg': 'Bevel segments',
+
'sidebar/geometry/shape_geometry/curveSegments': 'Segments de courbe',
'sidebar/geometry/shape_geometry/extrude': 'Extruder',
@@ -1326,6 +1362,7 @@ function Strings( config ) {
'menubar/add/mesh/icosahedron': '二十面体',
'menubar/add/mesh/octahedron': '八面体',
'menubar/add/mesh/tetrahedron': '四面体',
+ 'menubar/add/text': 'Text',
'menubar/add/mesh/torus': '圆环体',
'menubar/add/mesh/torusknot': '环面纽结体',
'menubar/add/mesh/tube': '管',
@@ -1495,6 +1532,17 @@ function Strings( config ) {
'sidebar/geometry/ring_geometry/thetastart': '弧度起点',
'sidebar/geometry/ring_geometry/thetalength': '弧度长度',
+ 'sidebar/geometry/text_geometry/text': 'Text',
+ 'sidebar/geometry/text_geometry/size': 'Font size',
+ 'sidebar/geometry/text_geometry/depth': 'Extrude depth',
+ 'sidebar/geometry/text_geometry/scale': 'Scale',
+ 'sidebar/geometry/text_geometry/curveseg': 'Curve segments',
+ 'sidebar/geometry/text_geometry/bevelenabled': 'Bevel enabled',
+ 'sidebar/geometry/text_geometry/bevelthickness': 'Bevel thickness',
+ 'sidebar/geometry/text_geometry/bevelsize': 'Bevel size',
+ 'sidebar/geometry/text_geometry/bevelOffset': 'Bevel offset',
+ 'sidebar/geometry/text_geometry/bevelseg': 'Bevel segments',
+
'sidebar/geometry/shape_geometry/curveSegments': '曲线段',
'sidebar/geometry/shape_geometry/extrude': '拉伸',
@@ -1744,6 +1792,7 @@ function Strings( config ) {
'menubar/add/mesh/icosahedron': '二十面体',
'menubar/add/mesh/octahedron': '八面体',
'menubar/add/mesh/tetrahedron': '四面体',
+ 'menubar/add/text': 'Text',
'menubar/add/mesh/torus': 'トーラス',
'menubar/add/mesh/tube': 'チューブ',
'menubar/add/mesh/torusknot': 'ノットトーラス',
@@ -1913,6 +1962,17 @@ function Strings( config ) {
'sidebar/geometry/ring_geometry/thetastart': '開始角度',
'sidebar/geometry/ring_geometry/thetalength': '角度の大きさ',
+ 'sidebar/geometry/text_geometry/text': 'Text',
+ 'sidebar/geometry/text_geometry/size': 'Font size',
+ 'sidebar/geometry/text_geometry/depth': 'Extrude depth',
+ 'sidebar/geometry/text_geometry/scale': 'Scale',
+ 'sidebar/geometry/text_geometry/curveseg': 'Curve segments',
+ 'sidebar/geometry/text_geometry/bevelenabled': 'Bevel enabled',
+ 'sidebar/geometry/text_geometry/bevelthickness': 'Bevel thickness',
+ 'sidebar/geometry/text_geometry/bevelsize': 'Bevel size',
+ 'sidebar/geometry/text_geometry/bevelOffset': 'Bevel offset',
+ 'sidebar/geometry/text_geometry/bevelseg': 'Bevel segments',
+
'sidebar/geometry/shape_geometry/curveSegments': '分割数',
'sidebar/geometry/shape_geometry/extrude': '押し出し',
@@ -2161,6 +2221,7 @@ function Strings( config ) {
'menubar/add/mesh/icosahedron': '이십면체',
'menubar/add/mesh/octahedron': '팔면체',
'menubar/add/mesh/tetrahedron': '사면체',
+ 'menubar/add/text': 'Text',
'menubar/add/mesh/torus': '토러스',
'menubar/add/mesh/tube': '튜브',
'menubar/add/mesh/torusknot': '토러스 매듭',
@@ -2330,6 +2391,17 @@ function Strings( config ) {
'sidebar/geometry/ring_geometry/thetastart': '시작 각도',
'sidebar/geometry/ring_geometry/thetalength': '각도 길이',
+ 'sidebar/geometry/text_geometry/text': 'Text',
+ 'sidebar/geometry/text_geometry/size': 'Font size',
+ 'sidebar/geometry/text_geometry/depth': 'Extrude depth',
+ 'sidebar/geometry/text_geometry/scale': 'Scale',
+ 'sidebar/geometry/text_geometry/curveseg': 'Curve segments',
+ 'sidebar/geometry/text_geometry/bevelenabled': 'Bevel enabled',
+ 'sidebar/geometry/text_geometry/bevelthickness': 'Bevel thickness',
+ 'sidebar/geometry/text_geometry/bevelsize': 'Bevel size',
+ 'sidebar/geometry/text_geometry/bevelOffset': 'Bevel offset',
+ 'sidebar/geometry/text_geometry/bevelseg': 'Bevel segments',
+
'sidebar/geometry/shape_geometry/curveSegments': '곡선 분할 수',
'sidebar/geometry/shape_geometry/extrude': '압출',
diff --git a/examples/jsm/geometries/TextGeometry.js b/examples/jsm/geometries/TextGeometry.js
index 14f0a61c9d999e..d440237a3bb276 100644
--- a/examples/jsm/geometries/TextGeometry.js
+++ b/examples/jsm/geometries/TextGeometry.js
@@ -2,6 +2,8 @@ import {
ExtrudeGeometry
} from 'three';
+import { Font } from '../loaders/FontLoader.js';
+
/**
* A class for generating text as a single geometry. It is constructed by providing a string of text, and a set of
* parameters consisting of a loaded font and extrude settings.
@@ -60,6 +62,22 @@ class TextGeometry extends ExtrudeGeometry {
}
+ toJSON() {
+
+ const data = super.toJSON();
+ return data;
+
+ }
+
+ static fromJSON( data ) {
+
+ const options = data.options;
+
+ options.font = new Font( options.font.data );
+ return new TextGeometry( options.text, options );
+
+ }
+
}
/**
diff --git a/examples/jsm/inspector/ui/List.js b/examples/jsm/inspector/ui/List.js
index aa058addbf9359..4f44c34a963d7f 100644
--- a/examples/jsm/inspector/ui/List.js
+++ b/examples/jsm/inspector/ui/List.js
@@ -8,7 +8,7 @@ export class List {
this.domElement = document.createElement( 'div' );
this.domElement.className = 'list-container';
this.domElement.style.padding = '10px';
- this.id = `list-${Math.random().toString( 36 ).substr( 2, 9 )}`;
+ this.id = `list-${Math.random().toString( 36 ).slice( 2, 11 )}`;
this.domElement.dataset.listId = this.id;
this.gridStyleElement = document.createElement( 'style' );
diff --git a/examples/jsm/lines/LineMaterial.js b/examples/jsm/lines/LineMaterial.js
index a229da8aad675a..60cd13dbc2fce8 100644
--- a/examples/jsm/lines/LineMaterial.js
+++ b/examples/jsm/lines/LineMaterial.js
@@ -647,7 +647,7 @@ class LineMaterial extends ShaderMaterial {
/**
* The size of the viewport, in screen pixels. This must be kept updated to make
- * screen-space rendering accurate.The `LineSegments2.onBeforeRender` callback
+ * screen-space rendering accurate. The `LineSegments2.onBeforeRender` callback
* performs the update for visible objects.
*
* @type {Vector2}
diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js
index dafa73a88cb1c1..0f220bef3e4714 100644
--- a/examples/jsm/loaders/SVGLoader.js
+++ b/examples/jsm/loaders/SVGLoader.js
@@ -3005,7 +3005,7 @@ class SVGLoader extends Loader {
}
- addVertex( tempV2_4, u, v );
+ addVertex( tempV2_3, u, v );
addVertex( p2, u, v );
addVertex( center, u, 0.5 );
diff --git a/examples/jsm/modifiers/TessellateModifier.js b/examples/jsm/modifiers/TessellateModifier.js
index 47f3bf15659575..dd9b883ee0f5d4 100644
--- a/examples/jsm/modifiers/TessellateModifier.js
+++ b/examples/jsm/modifiers/TessellateModifier.js
@@ -46,7 +46,7 @@ class TessellateModifier {
}
/**
- * Returns a new, modified version of the given geometry by applying a tesselation.
+ * Returns a new, modified version of the given geometry by applying a tessellation.
* Please note that the resulting geometry is always non-indexed.
*
* @param {BufferGeometry} geometry - The geometry to modify.
diff --git a/examples/jsm/transpiler/GLSLDecoder.js b/examples/jsm/transpiler/GLSLDecoder.js
index 3055a465880c01..5cc5652d7402d9 100644
--- a/examples/jsm/transpiler/GLSLDecoder.js
+++ b/examples/jsm/transpiler/GLSLDecoder.js
@@ -170,7 +170,7 @@ class Tokenizer {
skip( ...params ) {
- let remainingCode = this.source.substr( this.position );
+ let remainingCode = this.source.slice( this.position );
let i = params.length;
while ( i -- ) {
@@ -182,7 +182,7 @@ class Tokenizer {
this.position += skipLength;
- remainingCode = this.source.substr( this.position );
+ remainingCode = this.source.slice( this.position );
// re-skip, new remainingCode is generated
// maybe exist previous regexp non detected
diff --git a/examples/jsm/webxr/XRHandMeshModel.js b/examples/jsm/webxr/XRHandMeshModel.js
index 9af2a29b538457..515204bf176fb9 100644
--- a/examples/jsm/webxr/XRHandMeshModel.js
+++ b/examples/jsm/webxr/XRHandMeshModel.js
@@ -1,4 +1,5 @@
import { GLTFLoader } from '../loaders/GLTFLoader.js';
+import { clone } from '../utils/SkeletonUtils.js';
const DEFAULT_HAND_PROFILE_PATH = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles/generic-hand/';
@@ -20,8 +21,9 @@ class XRHandMeshModel {
* @param {XRHandedness} handedness - The handedness of the XR input source.
* @param {?Loader} [loader=null] - The loader. If not provided, an instance of `GLTFLoader` will be used to load models.
* @param {?Function} [onLoad=null] - A callback that is executed when a controller model has been loaded.
+ * @param {?Object} [customCache=null] - An optional shared cache object for storing and reusing loaded assets across instances.
*/
- constructor( handModel, controller, path, handedness, loader = null, onLoad = null ) {
+ constructor( handModel, controller, path, handedness, loader = null, onLoad = null, customCache = null ) {
/**
* The WebXR controller.
@@ -45,16 +47,11 @@ class XRHandMeshModel {
*/
this.bones = [];
- if ( loader === null ) {
+ const pathToUse = path || DEFAULT_HAND_PROFILE_PATH;
- loader = new GLTFLoader();
- loader.setPath( path || DEFAULT_HAND_PROFILE_PATH );
+ const processAsset = ( gltf ) => {
- }
-
- loader.load( `${handedness}.glb`, gltf => {
-
- const object = gltf.scene.children[ 0 ];
+ const object = clone( gltf.scene.children[ 0 ] );
this.handModel.add( object );
const mesh = object.getObjectByProperty( 'type', 'SkinnedMesh' );
@@ -110,7 +107,36 @@ class XRHandMeshModel {
if ( onLoad ) onLoad( object );
- } );
+ };
+
+ const assetUrl = `${pathToUse}${handedness}.glb`;
+
+ if ( customCache && customCache[ assetUrl ] ) {
+
+ processAsset( customCache[ assetUrl ] );
+
+ } else {
+
+ if ( loader === null ) {
+
+ loader = new GLTFLoader();
+ loader.setPath( pathToUse );
+
+ }
+
+ loader.load( `${handedness}.glb`, gltf => {
+
+ if ( customCache ) {
+
+ customCache[ assetUrl ] = gltf;
+
+ }
+
+ processAsset( gltf );
+
+ } );
+
+ }
}
diff --git a/examples/jsm/webxr/XRHandModelFactory.js b/examples/jsm/webxr/XRHandModelFactory.js
index 525bce4cdfa86d..0b3c8e13ce0518 100644
--- a/examples/jsm/webxr/XRHandModelFactory.js
+++ b/examples/jsm/webxr/XRHandModelFactory.js
@@ -118,6 +118,7 @@ class XRHandModelFactory {
* @default null
*/
this.path = null;
+ this._assetCache = {};
/**
* A callback that is executed when a hand model has been loaded.
@@ -173,7 +174,7 @@ class XRHandModelFactory {
} else if ( profile === 'mesh' ) {
- handModel.motionController = new XRHandMeshModel( handModel, controller, this.path, xrInputSource.handedness, this.gltfLoader, this.onLoad );
+ handModel.motionController = new XRHandMeshModel( handModel, controller, this.path, xrInputSource.handedness, this.gltfLoader, this.onLoad, this._assetCache );
}
diff --git a/src/extras/curves/CatmullRomCurve3.js b/src/extras/curves/CatmullRomCurve3.js
index e3b807b14fff91..a18985c3e16ee7 100644
--- a/src/extras/curves/CatmullRomCurve3.js
+++ b/src/extras/curves/CatmullRomCurve3.js
@@ -78,6 +78,7 @@ function CubicPoly() {
//
const tmp = /*@__PURE__*/ new Vector3();
+const tmp2 = /*@__PURE__*/ new Vector3();
const px = /*@__PURE__*/ new CubicPoly();
const py = /*@__PURE__*/ new CubicPoly();
const pz = /*@__PURE__*/ new CubicPoly();
@@ -202,8 +203,8 @@ class CatmullRomCurve3 extends Curve {
} else {
// extrapolate first point
- tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
- p0 = tmp;
+ tmp2.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
+ p0 = tmp2;
}
diff --git a/src/loaders/ObjectLoader.js b/src/loaders/ObjectLoader.js
index 50b0a657d52bee..b29f8e6fa863cc 100644
--- a/src/loaders/ObjectLoader.js
+++ b/src/loaders/ObjectLoader.js
@@ -65,6 +65,8 @@ import { Box3 } from '../math/Box3.js';
import { Sphere } from '../math/Sphere.js';
import { SphericalHarmonics3 } from '../math/SphericalHarmonics3.js';
+const _customGeometries = {};
+
/**
* A loader for loading a JSON resource in the [JSON Object/Scene format](https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4).
* The files are internally loaded via {@link FileLoader}.
@@ -275,6 +277,20 @@ class ObjectLoader extends Loader {
}
+ /**
+ * Registers the given geometry at the internal
+ * geometry library.
+ *
+ * @static
+ * @param {string} type - The geometry type.
+ * @param {BufferGeometry.constructor} geometryClass - The geometry class.
+ */
+ static registerGeometry( type, geometryClass ) {
+
+ _customGeometries[ type ] = geometryClass;
+
+ }
+
// internals
parseShapes( json ) {
@@ -355,9 +371,13 @@ class ObjectLoader extends Loader {
geometry = Geometries[ data.type ].fromJSON( data, shapes );
+ } else if ( data.type in _customGeometries ) {
+
+ geometry = _customGeometries[ data.type ].fromJSON( data, shapes );
+
} else {
- warn( `ObjectLoader: Unsupported geometry type "${ data.type }"` );
+ warn( `ObjectLoader: Unknown geometry type "${ data.type }". Use .registerGeometry() before starting the deserialization process.` );
}
diff --git a/src/math/Line3.js b/src/math/Line3.js
index df8377d7e9f61b..454863c495bf32 100644
--- a/src/math/Line3.js
+++ b/src/math/Line3.js
@@ -142,6 +142,9 @@ class Line3 {
_startEnd.subVectors( this.end, this.start );
const startEnd2 = _startEnd.dot( _startEnd );
+
+ if ( startEnd2 === 0 ) return 0;
+
const startEnd_startP = _startEnd.dot( _startP );
let t = startEnd_startP / startEnd2;
diff --git a/src/nodes/core/NodeUtils.js b/src/nodes/core/NodeUtils.js
index 67f69a0a0a8904..3425af6316dbb6 100644
--- a/src/nodes/core/NodeUtils.js
+++ b/src/nodes/core/NodeUtils.js
@@ -18,7 +18,7 @@ function cyrb53( value, seed = 0 ) {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
- if ( value instanceof Array ) {
+ if ( Array.isArray( value ) ) {
for ( let i = 0, val; i < value.length; i ++ ) {
diff --git a/test/unit/src/extras/curves/CatmullRomCurve3.tests.js b/test/unit/src/extras/curves/CatmullRomCurve3.tests.js
index 7ba54042b73dd4..12168610739bcc 100644
--- a/test/unit/src/extras/curves/CatmullRomCurve3.tests.js
+++ b/test/unit/src/extras/curves/CatmullRomCurve3.tests.js
@@ -387,6 +387,26 @@ export default QUnit.module( 'Extras', () => {
assert.deepEqual( points, expectedPoints, 'Correct points calculated' );
} );
+
+ QUnit.test( 'two points', ( assert ) => {
+
+ const curve = new CatmullRomCurve3( [
+ new Vector3( 0, 0, 0 ),
+ new Vector3( 10, 0, 0 )
+ ], false, 'catmullrom' );
+
+ const expectedPoints = [
+ new Vector3( 0, 0, 0 ),
+ new Vector3( 5, 0, 0 ),
+ new Vector3( 10, 0, 0 )
+ ];
+
+ const points = curve.getPoints( 2 );
+
+ assert.strictEqual( points.length, expectedPoints.length, 'Correct number of points' );
+ assert.deepEqual( points, expectedPoints, 'Correct points calculated' );
+
+ } );
} );
diff --git a/test/unit/src/math/Line3.tests.js b/test/unit/src/math/Line3.tests.js
index 2dc55d63defe49..78e6786ab994e2 100644
--- a/test/unit/src/math/Line3.tests.js
+++ b/test/unit/src/math/Line3.tests.js
@@ -161,6 +161,12 @@ export default QUnit.module( 'Maths', () => {
a.closestPointToPoint( one3.clone(), true, point );
assert.ok( point.distanceTo( one3.clone() ) < 0.0001, 'Passed!' );
+ // degenerate line (zero-length)
+ const b = new Line3( one3.clone(), one3.clone() );
+ assert.ok( b.closestPointToPointParameter( zero3.clone(), true ) == 0, 'Passed!' );
+ b.closestPointToPoint( zero3.clone(), true, point );
+ assert.ok( point.distanceTo( one3.clone() ) < 0.0001, 'Passed!' );
+
} );
QUnit.test( 'applyMatrix4', ( assert ) => {