UNPKG

three

Version:

JavaScript 3D library

1,595 lines (1,149 loc) 38 kB
import { Quaternion } from '../math/Quaternion.js'; import { Vector3 } from '../math/Vector3.js'; import { Matrix4 } from '../math/Matrix4.js'; import { EventDispatcher } from './EventDispatcher.js'; import { Euler } from '../math/Euler.js'; import { Layers } from './Layers.js'; import { Matrix3 } from '../math/Matrix3.js'; import { generateUUID } from '../math/MathUtils.js'; let _object3DId = 0; const _v1 = /*@__PURE__*/ new Vector3(); const _q1 = /*@__PURE__*/ new Quaternion(); const _m1 = /*@__PURE__*/ new Matrix4(); const _target = /*@__PURE__*/ new Vector3(); const _position = /*@__PURE__*/ new Vector3(); const _scale = /*@__PURE__*/ new Vector3(); const _quaternion = /*@__PURE__*/ new Quaternion(); const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); /** * Fires when the object has been added to its parent object. * * @event Object3D#added * @type {Object} */ const _addedEvent = { type: 'added' }; /** * Fires when the object has been removed from its parent object. * * @event Object3D#removed * @type {Object} */ const _removedEvent = { type: 'removed' }; /** * Fires when a new child object has been added. * * @event Object3D#childadded * @type {Object} */ const _childaddedEvent = { type: 'childadded', child: null }; /** * Fires when a new child object has been added. * * @event Object3D#childremoved * @type {Object} */ const _childremovedEvent = { type: 'childremoved', child: null }; /** * This is the base class for most objects in three.js and provides a set of * properties and methods for manipulating objects in 3D space. * * @augments EventDispatcher */ class Object3D extends EventDispatcher { /** * Constructs a new 3D object. */ constructor() { super(); /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isObject3D = true; /** * The ID of the 3D object. * * @name Object3D#id * @type {number} * @readonly */ Object.defineProperty( this, 'id', { value: _object3DId ++ } ); /** * The UUID of the 3D object. * * @type {string} * @readonly */ this.uuid = generateUUID(); /** * The name of the 3D object. * * @type {string} */ this.name = ''; /** * The type property is used for detecting the object type * in context of serialization/deserialization. * * @type {string} * @readonly */ this.type = 'Object3D'; /** * A reference to the parent object. * * @type {?Object3D} * @default null */ this.parent = null; /** * An array holding the child 3D objects of this instance. * * @type {Array<Object3D>} */ this.children = []; /** * Defines the `up` direction of the 3D object which influences * the orientation via methods like {@link Object3D#lookAt}. * * The default values for all 3D objects is defined by `Object3D.DEFAULT_UP`. * * @type {Vector3} */ this.up = Object3D.DEFAULT_UP.clone(); const position = new Vector3(); const rotation = new Euler(); const quaternion = new Quaternion(); const scale = new Vector3( 1, 1, 1 ); function onRotationChange() { quaternion.setFromEuler( rotation, false ); } function onQuaternionChange() { rotation.setFromQuaternion( quaternion, undefined, false ); } rotation._onChange( onRotationChange ); quaternion._onChange( onQuaternionChange ); Object.defineProperties( this, { /** * Represents the object's local position. * * @name Object3D#position * @type {Vector3} * @default (0,0,0) */ position: { configurable: true, enumerable: true, value: position }, /** * Represents the object's local rotation as Euler angles, in radians. * * @name Object3D#rotation * @type {Euler} * @default (0,0,0) */ rotation: { configurable: true, enumerable: true, value: rotation }, /** * Represents the object's local rotation as Quaternions. * * @name Object3D#quaternion * @type {Quaternion} */ quaternion: { configurable: true, enumerable: true, value: quaternion }, /** * Represents the object's local scale. * * @name Object3D#scale * @type {Vector3} * @default (1,1,1) */ scale: { configurable: true, enumerable: true, value: scale }, /** * Represents the object's model-view matrix. * * @name Object3D#modelViewMatrix * @type {Matrix4} */ modelViewMatrix: { value: new Matrix4() }, /** * Represents the object's normal matrix. * * @name Object3D#normalMatrix * @type {Matrix3} */ normalMatrix: { value: new Matrix3() } } ); /** * Represents the object's transformation matrix in local space. * * @type {Matrix4} */ this.matrix = new Matrix4(); /** * Represents the object's transformation matrix in world space. * If the 3D object has no parent, then it's identical to the local transformation matrix * * @type {Matrix4} */ this.matrixWorld = new Matrix4(); /** * When set to `true`, the engine automatically computes the local matrix from position, * rotation and scale every frame. * * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_AUTO_UPDATE`. * * @type {boolean} * @default true */ this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; /** * When set to `true`, the engine automatically computes the world matrix from the current local * matrix and the object's transformation hierarchy. * * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE`. * * @type {boolean} * @default true */ this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer /** * When set to `true`, it calculates the world matrix in that frame and resets this property * to `false`. * * @type {boolean} * @default false */ this.matrixWorldNeedsUpdate = false; /** * The layer membership of the 3D object. The 3D object is only visible if it has * at least one layer in common with the camera in use. This property can also be * used to filter out unwanted objects in ray-intersection tests when using {@link Raycaster}. * * @type {Layers} */ this.layers = new Layers(); /** * When set to `true`, the 3D object gets rendered. * * @type {boolean} * @default true */ this.visible = true; /** * When set to `true`, the 3D object gets rendered into shadow maps. * * @type {boolean} * @default false */ this.castShadow = false; /** * When set to `true`, the 3D object is affected by shadows in the scene. * * @type {boolean} * @default false */ this.receiveShadow = false; /** * When set to `true`, the 3D object is honored by view frustum culling. * * @type {boolean} * @default true */ this.frustumCulled = true; /** * This value allows the default rendering order of scene graph objects to be * overridden although opaque and transparent objects remain sorted independently. * When this property is set for an instance of {@link Group},all descendants * objects will be sorted and rendered together. Sorting is from lowest to highest * render order. * * @type {number} * @default 0 */ this.renderOrder = 0; /** * An array holding the animation clips of the 3D object. * * @type {Array<AnimationClip>} */ this.animations = []; /** * An object that can be used to store custom data about the 3D object. It * should not hold references to functions as these will not be cloned. * * @type {Object} */ this.userData = {}; } /** * A callback that is executed immediately before a 3D object is rendered to a shadow map. * * @param {Renderer|WebGLRenderer} renderer - The renderer. * @param {Object3D} object - The 3D object. * @param {Camera} camera - The camera that is used to render the scene. * @param {Camera} shadowCamera - The shadow camera. * @param {BufferGeometry} geometry - The 3D object's geometry. * @param {Material} depthMaterial - The depth material. * @param {Object} group - The geometry group data. */ onBeforeShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} /** * A callback that is executed immediately after a 3D object is rendered to a shadow map. * * @param {Renderer|WebGLRenderer} renderer - The renderer. * @param {Object3D} object - The 3D object. * @param {Camera} camera - The camera that is used to render the scene. * @param {Camera} shadowCamera - The shadow camera. * @param {BufferGeometry} geometry - The 3D object's geometry. * @param {Material} depthMaterial - The depth material. * @param {Object} group - The geometry group data. */ onAfterShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} /** * A callback that is executed immediately before a 3D object is rendered. * * @param {Renderer|WebGLRenderer} renderer - The renderer. * @param {Object3D} object - The 3D object. * @param {Camera} camera - The camera that is used to render the scene. * @param {BufferGeometry} geometry - The 3D object's geometry. * @param {Material} material - The 3D object's material. * @param {Object} group - The geometry group data. */ onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} /** * A callback that is executed immediately after a 3D object is rendered. * * @param {Renderer|WebGLRenderer} renderer - The renderer. * @param {Object3D} object - The 3D object. * @param {Camera} camera - The camera that is used to render the scene. * @param {BufferGeometry} geometry - The 3D object's geometry. * @param {Material} material - The 3D object's material. * @param {Object} group - The geometry group data. */ onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} /** * Applies the given transformation matrix to the object and updates the object's position, * rotation and scale. * * @param {Matrix4} matrix - The transformation matrix. */ applyMatrix4( matrix ) { if ( this.matrixAutoUpdate ) this.updateMatrix(); this.matrix.premultiply( matrix ); this.matrix.decompose( this.position, this.quaternion, this.scale ); } /** * Applies a rotation represented by given the quaternion to the 3D object. * * @param {Quaternion} q - The quaternion. * @return {Object3D} A reference to this instance. */ applyQuaternion( q ) { this.quaternion.premultiply( q ); return this; } /** * Sets the given rotation represented as an axis/angle couple to the 3D object. * * @param {Vector3} axis - The (normalized) axis vector. * @param {number} angle - The angle in radians. */ setRotationFromAxisAngle( axis, angle ) { // assumes axis is normalized this.quaternion.setFromAxisAngle( axis, angle ); } /** * Sets the given rotation represented as Euler angles to the 3D object. * * @param {Euler} euler - The Euler angles. */ setRotationFromEuler( euler ) { this.quaternion.setFromEuler( euler, true ); } /** * Sets the given rotation represented as rotation matrix to the 3D object. * * @param {Matrix4} m - Although a 4x4 matrix is expected, the upper 3x3 portion must be * a pure rotation matrix (i.e, unscaled). */ setRotationFromMatrix( m ) { // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) this.quaternion.setFromRotationMatrix( m ); } /** * Sets the given rotation represented as a Quaternion to the 3D object. * * @param {Quaternion} q - The Quaternion */ setRotationFromQuaternion( q ) { // assumes q is normalized this.quaternion.copy( q ); } /** * Rotates the 3D object along an axis in local space. * * @param {Vector3} axis - The (normalized) axis vector. * @param {number} angle - The angle in radians. * @return {Object3D} A reference to this instance. */ rotateOnAxis( axis, angle ) { // rotate object on axis in object space // axis is assumed to be normalized _q1.setFromAxisAngle( axis, angle ); this.quaternion.multiply( _q1 ); return this; } /** * Rotates the 3D object along an axis in world space. * * @param {Vector3} axis - The (normalized) axis vector. * @param {number} angle - The angle in radians. * @return {Object3D} A reference to this instance. */ rotateOnWorldAxis( axis, angle ) { // rotate object on axis in world space // axis is assumed to be normalized // method assumes no rotated parent _q1.setFromAxisAngle( axis, angle ); this.quaternion.premultiply( _q1 ); return this; } /** * Rotates the 3D object around its X axis in local space. * * @param {number} angle - The angle in radians. * @return {Object3D} A reference to this instance. */ rotateX( angle ) { return this.rotateOnAxis( _xAxis, angle ); } /** * Rotates the 3D object around its Y axis in local space. * * @param {number} angle - The angle in radians. * @return {Object3D} A reference to this instance. */ rotateY( angle ) { return this.rotateOnAxis( _yAxis, angle ); } /** * Rotates the 3D object around its Z axis in local space. * * @param {number} angle - The angle in radians. * @return {Object3D} A reference to this instance. */ rotateZ( angle ) { return this.rotateOnAxis( _zAxis, angle ); } /** * Translate the 3D object by a distance along the given axis in local space. * * @param {Vector3} axis - The (normalized) axis vector. * @param {number} distance - The distance in world units. * @return {Object3D} A reference to this instance. */ translateOnAxis( axis, distance ) { // translate object by distance along axis in object space // axis is assumed to be normalized _v1.copy( axis ).applyQuaternion( this.quaternion ); this.position.add( _v1.multiplyScalar( distance ) ); return this; } /** * Translate the 3D object by a distance along its X-axis in local space. * * @param {number} distance - The distance in world units. * @return {Object3D} A reference to this instance. */ translateX( distance ) { return this.translateOnAxis( _xAxis, distance ); } /** * Translate the 3D object by a distance along its Y-axis in local space. * * @param {number} distance - The distance in world units. * @return {Object3D} A reference to this instance. */ translateY( distance ) { return this.translateOnAxis( _yAxis, distance ); } /** * Translate the 3D object by a distance along its Z-axis in local space. * * @param {number} distance - The distance in world units. * @return {Object3D} A reference to this instance. */ translateZ( distance ) { return this.translateOnAxis( _zAxis, distance ); } /** * Converts the given vector from this 3D object's local space to world space. * * @param {Vector3} vector - The vector to convert. * @return {Vector3} The converted vector. */ localToWorld( vector ) { this.updateWorldMatrix( true, false ); return vector.applyMatrix4( this.matrixWorld ); } /** * Converts the given vector from this 3D object's word space to local space. * * @param {Vector3} vector - The vector to convert. * @return {Vector3} The converted vector. */ worldToLocal( vector ) { this.updateWorldMatrix( true, false ); return vector.applyMatrix4( _m1.copy( this.matrixWorld ).invert() ); } /** * Rotates the object to face a point in world space. * * This method does not support objects having non-uniformly-scaled parent(s). * * @param {number|Vector3} x - The x coordinate in world space. Alternatively, a vector representing a position in world space * @param {number} [y] - The y coordinate in world space. * @param {number} [z] - The z coordinate in world space. */ lookAt( x, y, z ) { // This method does not support objects having non-uniformly-scaled parent(s) if ( x.isVector3 ) { _target.copy( x ); } else { _target.set( x, y, z ); } const parent = this.parent; this.updateWorldMatrix( true, false ); _position.setFromMatrixPosition( this.matrixWorld ); if ( this.isCamera || this.isLight ) { _m1.lookAt( _position, _target, this.up ); } else { _m1.lookAt( _target, _position, this.up ); } this.quaternion.setFromRotationMatrix( _m1 ); if ( parent ) { _m1.extractRotation( parent.matrixWorld ); _q1.setFromRotationMatrix( _m1 ); this.quaternion.premultiply( _q1.invert() ); } } /** * Adds the given 3D object as a child to this 3D object. An arbitrary number of * objects may be added. Any current parent on an object passed in here will be * removed, since an object can have at most one parent. * * @fires Object3D#added * @fires Object3D#childadded * @param {Object3D} object - The 3D object to add. * @return {Object3D} A reference to this instance. */ add( object ) { if ( arguments.length > 1 ) { for ( let i = 0; i < arguments.length; i ++ ) { this.add( arguments[ i ] ); } return this; } if ( object === this ) { console.error( 'THREE.Object3D.add: object can\'t be added as a child of itself.', object ); return this; } if ( object && object.isObject3D ) { object.removeFromParent(); object.parent = this; this.children.push( object ); object.dispatchEvent( _addedEvent ); _childaddedEvent.child = object; this.dispatchEvent( _childaddedEvent ); _childaddedEvent.child = null; } else { console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object ); } return this; } /** * Removes the given 3D object as child from this 3D object. * An arbitrary number of objects may be removed. * * @fires Object3D#removed * @fires Object3D#childremoved * @param {Object3D} object - The 3D object to remove. * @return {Object3D} A reference to this instance. */ remove( object ) { if ( arguments.length > 1 ) { for ( let i = 0; i < arguments.length; i ++ ) { this.remove( arguments[ i ] ); } return this; } const index = this.children.indexOf( object ); if ( index !== - 1 ) { object.parent = null; this.children.splice( index, 1 ); object.dispatchEvent( _removedEvent ); _childremovedEvent.child = object; this.dispatchEvent( _childremovedEvent ); _childremovedEvent.child = null; } return this; } /** * Removes this 3D object from its current parent. * * @fires Object3D#removed * @fires Object3D#childremoved * @return {Object3D} A reference to this instance. */ removeFromParent() { const parent = this.parent; if ( parent !== null ) { parent.remove( this ); } return this; } /** * Removes all child objects. * * @fires Object3D#removed * @fires Object3D#childremoved * @return {Object3D} A reference to this instance. */ clear() { return this.remove( ... this.children ); } /** * Adds the given 3D object as a child of this 3D object, while maintaining the object's world * transform. This method does not support scene graphs having non-uniformly-scaled nodes(s). * * @fires Object3D#added * @fires Object3D#childadded * @param {Object3D} object - The 3D object to attach. * @return {Object3D} A reference to this instance. */ attach( object ) { // adds object as a child of this, while maintaining the object's world transform // Note: This method does not support scene graphs having non-uniformly-scaled nodes(s) this.updateWorldMatrix( true, false ); _m1.copy( this.matrixWorld ).invert(); if ( object.parent !== null ) { object.parent.updateWorldMatrix( true, false ); _m1.multiply( object.parent.matrixWorld ); } object.applyMatrix4( _m1 ); object.removeFromParent(); object.parent = this; this.children.push( object ); object.updateWorldMatrix( false, true ); object.dispatchEvent( _addedEvent ); _childaddedEvent.child = object; this.dispatchEvent( _childaddedEvent ); _childaddedEvent.child = null; return this; } /** * Searches through the 3D object and its children, starting with the 3D object * itself, and returns the first with a matching ID. * * @param {number} id - The id. * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. */ getObjectById( id ) { return this.getObjectByProperty( 'id', id ); } /** * Searches through the 3D object and its children, starting with the 3D object * itself, and returns the first with a matching name. * * @param {string} name - The name. * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. */ getObjectByName( name ) { return this.getObjectByProperty( 'name', name ); } /** * Searches through the 3D object and its children, starting with the 3D object * itself, and returns the first with a matching property value. * * @param {string} name - The name of the property. * @param {any} value - The value. * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. */ getObjectByProperty( name, value ) { if ( this[ name ] === value ) return this; for ( let i = 0, l = this.children.length; i < l; i ++ ) { const child = this.children[ i ]; const object = child.getObjectByProperty( name, value ); if ( object !== undefined ) { return object; } } return undefined; } /** * Searches through the 3D object and its children, starting with the 3D object * itself, and returns all 3D objects with a matching property value. * * @param {string} name - The name of the property. * @param {any} value - The value. * @param {Array<Object3D>} result - The method stores the result in this array. * @return {Array<Object3D>} The found 3D objects. */ getObjectsByProperty( name, value, result = [] ) { if ( this[ name ] === value ) result.push( this ); const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { children[ i ].getObjectsByProperty( name, value, result ); } return result; } /** * Returns a vector representing the position of the 3D object in world space. * * @param {Vector3} target - The target vector the result is stored to. * @return {Vector3} The 3D object's position in world space. */ getWorldPosition( target ) { this.updateWorldMatrix( true, false ); return target.setFromMatrixPosition( this.matrixWorld ); } /** * Returns a Quaternion representing the position of the 3D object in world space. * * @param {Quaternion} target - The target Quaternion the result is stored to. * @return {Quaternion} The 3D object's rotation in world space. */ getWorldQuaternion( target ) { this.updateWorldMatrix( true, false ); this.matrixWorld.decompose( _position, target, _scale ); return target; } /** * Returns a vector representing the scale of the 3D object in world space. * * @param {Vector3} target - The target vector the result is stored to. * @return {Vector3} The 3D object's scale in world space. */ getWorldScale( target ) { this.updateWorldMatrix( true, false ); this.matrixWorld.decompose( _position, _quaternion, target ); return target; } /** * Returns a vector representing the ("look") direction of the 3D object in world space. * * @param {Vector3} target - The target vector the result is stored to. * @return {Vector3} The 3D object's direction in world space. */ getWorldDirection( target ) { this.updateWorldMatrix( true, false ); const e = this.matrixWorld.elements; return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize(); } /** * Abstract method to get intersections between a casted ray and this * 3D object. Renderable 3D objects such as {@link Mesh}, {@link Line} or {@link Points} * implement this method in order to use raycasting. * * @abstract * @param {Raycaster} raycaster - The raycaster. * @param {Array<Object>} intersects - An array holding the result of the method. */ raycast( /* raycaster, intersects */ ) {} /** * Executes the callback on this 3D object and all descendants. * * Note: Modifying the scene graph inside the callback is discouraged. * * @param {Function} callback - A callback function that allows to process the current 3D object. */ traverse( callback ) { callback( this ); const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { children[ i ].traverse( callback ); } } /** * Like {@link Object3D#traverse}, but the callback will only be executed for visible 3D objects. * Descendants of invisible 3D objects are not traversed. * * Note: Modifying the scene graph inside the callback is discouraged. * * @param {Function} callback - A callback function that allows to process the current 3D object. */ traverseVisible( callback ) { if ( this.visible === false ) return; callback( this ); const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { children[ i ].traverseVisible( callback ); } } /** * Like {@link Object3D#traverse}, but the callback will only be executed for all ancestors. * * Note: Modifying the scene graph inside the callback is discouraged. * * @param {Function} callback - A callback function that allows to process the current 3D object. */ traverseAncestors( callback ) { const parent = this.parent; if ( parent !== null ) { callback( parent ); parent.traverseAncestors( callback ); } } /** * Updates the transformation matrix in local space by computing it from the current * position, rotation and scale values. */ updateMatrix() { this.matrix.compose( this.position, this.quaternion, this.scale ); this.matrixWorldNeedsUpdate = true; } /** * Updates the transformation matrix in world space of this 3D objects and its descendants. * * To ensure correct results, this method also recomputes the 3D object's transformation matrix in * local space. The computation of the local and world matrix can be controlled with the * {@link Object3D#matrixAutoUpdate} and {@link Object3D#matrixWorldAutoUpdate} flags which are both * `true` by default. Set these flags to `false` if you need more control over the update matrix process. * * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even * when {@link Object3D#matrixWorldAutoUpdate} is set to `false`. */ updateMatrixWorld( force ) { if ( this.matrixAutoUpdate ) this.updateMatrix(); if ( this.matrixWorldNeedsUpdate || force ) { if ( this.matrixWorldAutoUpdate === true ) { if ( this.parent === null ) { this.matrixWorld.copy( this.matrix ); } else { this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); } } this.matrixWorldNeedsUpdate = false; force = true; } // make sure descendants are updated if required const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { const child = children[ i ]; child.updateMatrixWorld( force ); } } /** * An alternative version of {@link Object3D#updateMatrixWorld} with more control over the * update of ancestor and descendant nodes. * * @param {boolean} [updateParents=false] Whether ancestor nodes should be updated or not. * @param {boolean} [updateChildren=false] Whether descendant nodes should be updated or not. */ updateWorldMatrix( updateParents, updateChildren ) { const parent = this.parent; if ( updateParents === true && parent !== null ) { parent.updateWorldMatrix( true, false ); } if ( this.matrixAutoUpdate ) this.updateMatrix(); if ( this.matrixWorldAutoUpdate === true ) { if ( this.parent === null ) { this.matrixWorld.copy( this.matrix ); } else { this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); } } // make sure descendants are updated if ( updateChildren === true ) { const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { const child = children[ i ]; child.updateWorldMatrix( false, true ); } } } /** * Serializes the 3D object into JSON. * * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. * @return {Object} A JSON object representing the serialized 3D object. * @see {@link ObjectLoader#parse} */ toJSON( meta ) { // meta is a string when called from JSON.stringify const isRootObject = ( meta === undefined || typeof meta === 'string' ); const output = {}; // meta is a hash used to collect geometries, materials. // not providing it implies that this is the root object // being serialized. if ( isRootObject ) { // initialize meta obj meta = { geometries: {}, materials: {}, textures: {}, images: {}, shapes: {}, skeletons: {}, animations: {}, nodes: {} }; output.metadata = { version: 4.6, type: 'Object', generator: 'Object3D.toJSON' }; } // standard Object3D serialization const object = {}; object.uuid = this.uuid; object.type = this.type; if ( this.name !== '' ) object.name = this.name; if ( this.castShadow === true ) object.castShadow = true; if ( this.receiveShadow === true ) object.receiveShadow = true; if ( this.visible === false ) object.visible = false; if ( this.frustumCulled === false ) object.frustumCulled = false; if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder; if ( Object.keys( this.userData ).length > 0 ) object.userData = this.userData; object.layers = this.layers.mask; object.matrix = this.matrix.toArray(); object.up = this.up.toArray(); if ( this.matrixAutoUpdate === false ) object.matrixAutoUpdate = false; // object specific properties if ( this.isInstancedMesh ) { object.type = 'InstancedMesh'; object.count = this.count; object.instanceMatrix = this.instanceMatrix.toJSON(); if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON(); } if ( this.isBatchedMesh ) { object.type = 'BatchedMesh'; object.perObjectFrustumCulled = this.perObjectFrustumCulled; object.sortObjects = this.sortObjects; object.drawRanges = this._drawRanges; object.reservedRanges = this._reservedRanges; object.visibility = this._visibility; object.active = this._active; object.bounds = this._bounds.map( bound => ( { boxInitialized: bound.boxInitialized, boxMin: bound.box.min.toArray(), boxMax: bound.box.max.toArray(), sphereInitialized: bound.sphereInitialized, sphereRadius: bound.sphere.radius, sphereCenter: bound.sphere.center.toArray() } ) ); object.maxInstanceCount = this._maxInstanceCount; object.maxVertexCount = this._maxVertexCount; object.maxIndexCount = this._maxIndexCount; object.geometryInitialized = this._geometryInitialized; object.geometryCount = this._geometryCount; object.matricesTexture = this._matricesTexture.toJSON( meta ); if ( this._colorsTexture !== null ) object.colorsTexture = this._colorsTexture.toJSON( meta ); if ( this.boundingSphere !== null ) { object.boundingSphere = { center: object.boundingSphere.center.toArray(), radius: object.boundingSphere.radius }; } if ( this.boundingBox !== null ) { object.boundingBox = { min: object.boundingBox.min.toArray(), max: object.boundingBox.max.toArray() }; } } // function serialize( library, element ) { if ( library[ element.uuid ] === undefined ) { library[ element.uuid ] = element.toJSON( meta ); } return element.uuid; } if ( this.isScene ) { if ( this.background ) { if ( this.background.isColor ) { object.background = this.background.toJSON(); } else if ( this.background.isTexture ) { object.background = this.background.toJSON( meta ).uuid; } } if ( this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true ) { object.environment = this.environment.toJSON( meta ).uuid; } } else if ( this.isMesh || this.isLine || this.isPoints ) { object.geometry = serialize( meta.geometries, this.geometry ); const parameters = this.geometry.parameters; if ( parameters !== undefined && parameters.shapes !== undefined ) { const shapes = parameters.shapes; if ( Array.isArray( shapes ) ) { for ( let i = 0, l = shapes.length; i < l; i ++ ) { const shape = shapes[ i ]; serialize( meta.shapes, shape ); } } else { serialize( meta.shapes, shapes ); } } } if ( this.isSkinnedMesh ) { object.bindMode = this.bindMode; object.bindMatrix = this.bindMatrix.toArray(); if ( this.skeleton !== undefined ) { serialize( meta.skeletons, this.skeleton ); object.skeleton = this.skeleton.uuid; } } if ( this.material !== undefined ) { if ( Array.isArray( this.material ) ) { const uuids = []; for ( let i = 0, l = this.material.length; i < l; i ++ ) { uuids.push( serialize( meta.materials, this.material[ i ] ) ); } object.material = uuids; } else { object.material = serialize( meta.materials, this.material ); } } // if ( this.children.length > 0 ) { object.children = []; for ( let i = 0; i < this.children.length; i ++ ) { object.children.push( this.children[ i ].toJSON( meta ).object ); } } // if ( this.animations.length > 0 ) { object.animations = []; for ( let i = 0; i < this.animations.length; i ++ ) { const animation = this.animations[ i ]; object.animations.push( serialize( meta.animations, animation ) ); } } if ( isRootObject ) { const geometries = extractFromCache( meta.geometries ); const materials = extractFromCache( meta.materials ); const textures = extractFromCache( meta.textures ); const images = extractFromCache( meta.images ); const shapes = extractFromCache( meta.shapes ); const skeletons = extractFromCache( meta.skeletons ); const animations = extractFromCache( meta.animations ); const nodes = extractFromCache( meta.nodes ); if ( geometries.length > 0 ) output.geometries = geometries; if ( materials.length > 0 ) output.materials = materials; if ( textures.length > 0 ) output.textures = textures; if ( images.length > 0 ) output.images = images; if ( shapes.length > 0 ) output.shapes = shapes; if ( skeletons.length > 0 ) output.skeletons = skeletons; if ( animations.length > 0 ) output.animations = animations; if ( nodes.length > 0 ) output.nodes = nodes; } output.object = object; return output; // extract data from the cache hash // remove metadata on each item // and return as array function extractFromCache( cache ) { const values = []; for ( const key in cache ) { const data = cache[ key ]; delete data.metadata; values.push( data ); } return values; } } /** * Returns a new 3D object with copied values from this instance. * * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are also cloned. * @return {Object3D} A clone of this instance. */ clone( recursive ) { return new this.constructor().copy( this, recursive ); } /** * Copies the values of the given 3D object to this instance. * * @param {Object3D} source - The 3D object to copy. * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are cloned. * @return {Object3D} A reference to this instance. */ copy( source, recursive = true ) { this.name = source.name; this.up.copy( source.up ); this.position.copy( source.position ); this.rotation.order = source.rotation.order; this.quaternion.copy( source.quaternion ); this.scale.copy( source.scale ); this.matrix.copy( source.matrix ); this.matrixWorld.copy( source.matrixWorld ); this.matrixAutoUpdate = source.matrixAutoUpdate; this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; this.layers.mask = source.layers.mask; this.visible = source.visible; this.castShadow = source.castShadow; this.receiveShadow = source.receiveShadow; this.frustumCulled = source.frustumCulled; this.renderOrder = source.renderOrder; this.animations = source.animations.slice(); this.userData = JSON.parse( JSON.stringify( source.userData ) ); if ( recursive === true ) { for ( let i = 0; i < source.children.length; i ++ ) { const child = source.children[ i ]; this.add( child.clone() ); } } return this; } } /** * The default up direction for objects, also used as the default * position for {@link DirectionalLight} and {@link HemisphereLight}. * * @static * @type {Vector3} * @default (0,1,0) */ Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); /** * The default setting for {@link Object3D#matrixAutoUpdate} for * newly created 3D objects. * * @static * @type {boolean} * @default true */ Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; /** * The default setting for {@link Object3D#matrixWorldAutoUpdate} for * newly created 3D objects. * * @static * @type {boolean} * @default true */ Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; export { Object3D };