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 ) => {