@openhps/core
Version:
Open Hybrid Positioning System - Core component
1,283 lines (1,182 loc) • 39.9 kB
JavaScript
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 = [];
/**
* Custom depth material to be used when rendering to the depth map. Can only be used
* in context of meshes. When shadow-casting with a {@link DirectionalLight} or {@link SpotLight},
* if you are modifying vertex positions in the vertex shader you must specify a custom depth
* material for proper shadows.
*
* Only relevant in context of {@link WebGLRenderer}.
*
* @type {(Material|undefined)}
* @default undefined
*/
this.customDepthMaterial = undefined;
/**
* Same as {@link Object3D#customDepthMaterial}, but used with {@link PointLight}.
*
* Only relevant in context of {@link WebGLRenderer}.
*
* @type {(Material|undefined)}
* @default undefined
*/
this.customDistanceMaterial = undefined;
/**
* 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 };