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 * as MathUtils 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); const _addedEvent = { type: 'added' }; const _removedEvent = { type: 'removed' }; class Object3D extends EventDispatcher { constructor() { super(); Object.defineProperty(this, 'id', { value: _object3DId++ }); this.uuid = MathUtils.generateUUID(); this.name = ''; this.type = 'Object3D'; this.parent = null; this.children = []; this.up = Object3D.DefaultUp.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, { position: { configurable: true, enumerable: true, value: position, }, rotation: { configurable: true, enumerable: true, value: rotation, }, quaternion: { configurable: true, enumerable: true, value: quaternion, }, scale: { configurable: true, enumerable: true, value: scale, }, modelViewMatrix: { value: new Matrix4(), }, normalMatrix: { value: new Matrix3(), }, }); this.matrix = new Matrix4(); this.matrixWorld = new Matrix4(); this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate; this.matrixWorldNeedsUpdate = false; this.layers = new Layers(); this.visible = true; this.castShadow = false; this.receiveShadow = false; this.frustumCulled = true; this.renderOrder = 0; this.animations = []; this.userData = {}; } onBeforeRender(/* renderer, scene, camera, geometry, material, group */) {} onAfterRender(/* renderer, scene, camera, geometry, material, group */) {} applyMatrix4(matrix) { if (this.matrixAutoUpdate) this.updateMatrix(); this.matrix.premultiply(matrix); this.matrix.decompose(this.position, this.quaternion, this.scale); } applyQuaternion(q) { this.quaternion.premultiply(q); return this; } setRotationFromAxisAngle(axis, angle) { // assumes axis is normalized this.quaternion.setFromAxisAngle(axis, angle); } setRotationFromEuler(euler) { this.quaternion.setFromEuler(euler, true); } setRotationFromMatrix(m) { // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) this.quaternion.setFromRotationMatrix(m); } setRotationFromQuaternion(q) { // assumes q is normalized this.quaternion.copy(q); } 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; } 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; } rotateX(angle) { return this.rotateOnAxis(_xAxis, angle); } rotateY(angle) { return this.rotateOnAxis(_yAxis, angle); } rotateZ(angle) { return this.rotateOnAxis(_zAxis, angle); } 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; } translateX(distance) { return this.translateOnAxis(_xAxis, distance); } translateY(distance) { return this.translateOnAxis(_yAxis, distance); } translateZ(distance) { return this.translateOnAxis(_zAxis, distance); } localToWorld(vector) { return vector.applyMatrix4(this.matrixWorld); } worldToLocal(vector) { return vector.applyMatrix4(_m1.copy(this.matrixWorld).invert()); } 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()); } } 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) { if (object.parent !== null) { object.parent.remove(object); } object.parent = this; this.children.push(object); object.dispatchEvent(_addedEvent); } else { console.error('THREE.Object3D.add: object not an instance of THREE.Object3D.', object); } return this; } 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); } return this; } removeFromParent() { const parent = this.parent; if (parent !== null) { parent.remove(this); } return this; } clear() { for (let i = 0; i < this.children.length; i++) { const object = this.children[i]; object.parent = null; object.dispatchEvent(_removedEvent); } this.children.length = 0; return this; } 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); this.add(object); object.updateWorldMatrix(false, true); return this; } getObjectById(id) { return this.getObjectByProperty('id', id); } getObjectByName(name) { return this.getObjectByProperty('name', name); } 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; } getWorldPosition(target) { this.updateWorldMatrix(true, false); return target.setFromMatrixPosition(this.matrixWorld); } getWorldQuaternion(target) { this.updateWorldMatrix(true, false); this.matrixWorld.decompose(_position, target, _scale); return target; } getWorldScale(target) { this.updateWorldMatrix(true, false); this.matrixWorld.decompose(_position, _quaternion, target); return target; } getWorldDirection(target) { this.updateWorldMatrix(true, false); const e = this.matrixWorld.elements; return target.set(e[8], e[9], e[10]).normalize(); } raycast(/* raycaster, intersects */) {} traverse(callback) { callback(this); const children = this.children; for (let i = 0, l = children.length; i < l; i++) { children[i].traverse(callback); } } 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); } } traverseAncestors(callback) { const parent = this.parent; if (parent !== null) { callback(parent); parent.traverseAncestors(callback); } } updateMatrix() { this.matrix.compose(this.position, this.quaternion, this.scale); this.matrixWorldNeedsUpdate = true; } updateMatrixWorld(force) { if (this.matrixAutoUpdate) this.updateMatrix(); if (this.matrixWorldNeedsUpdate || force) { if (this.parent === null) { this.matrixWorld.copy(this.matrix); } else { this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix); } this.matrixWorldNeedsUpdate = false; force = true; } // update children const children = this.children; for (let i = 0, l = children.length; i < l; i++) { children[i].updateMatrixWorld(force); } } updateWorldMatrix(updateParents, updateChildren) { const parent = this.parent; if (updateParents === true && parent !== null) { parent.updateWorldMatrix(true, false); } if (this.matrixAutoUpdate) this.updateMatrix(); if (this.parent === null) { this.matrixWorld.copy(this.matrix); } else { this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix); } // update children if (updateChildren === true) { const children = this.children; for (let i = 0, l = children.length; i < l; i++) { children[i].updateWorldMatrix(false, true); } } } 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: {}, }; output.metadata = { version: 4.5, 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 (JSON.stringify(this.userData) !== '{}') object.userData = this.userData; object.layers = this.layers.mask; object.matrix = this.matrix.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(); } // 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) { 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); 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; } 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; } } clone(recursive) { return new this.constructor().copy(this, recursive); } 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.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.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; } } Object3D.DefaultUp = new Vector3(0, 1, 0); Object3D.DefaultMatrixAutoUpdate = true; Object3D.prototype.isObject3D = true; export { Object3D };