UNPKG

@renderlayer/core

Version:
675 lines (669 loc) 20.5 kB
import { generateUUID, Vector3, Euler, Quaternion, Matrix4, Matrix3, Ray } from '@renderlayer/math'; class EventDispatcher { addEventListener(type, listener) { if (this._listeners === void 0) this._listeners = {}; const listeners = this._listeners; if (listeners[type] === void 0) { listeners[type] = []; } if (!listeners[type].includes(listener)) { listeners[type].push(listener); } } hasEventListener(type, listener) { if (this._listeners === void 0) return false; const listeners = this._listeners; return listeners[type] !== void 0 && listeners[type].includes(listener); } removeEventListener(type, listener) { if (this._listeners === void 0) return; const listeners = this._listeners; const listenerArray = listeners[type]; if (listenerArray !== void 0) { const index = listenerArray.indexOf(listener); if (index !== -1) { listenerArray.splice(index, 1); } } } dispatchEvent(event) { if (this._listeners === void 0) return; const listeners = this._listeners; const listenerArray = listeners[event.type]; if (listenerArray !== void 0) { event.target = this; const array = listenerArray.slice(0); for (let i = 0, l = array.length; i < l; i++) { array[i].call(this, event); } event.target = null; } } } class Layers { constructor() { this.mask = 1 | 0; } set(channel) { this.mask = (1 << channel | 0) >>> 0; } enable(channel) { this.mask |= 1 << channel | 0; } enableAll() { this.mask = 4294967295 | 0; } toggle(channel) { this.mask ^= 1 << channel | 0; } disable(channel) { this.mask &= ~(1 << channel | 0); } disableAll() { this.mask = 0; } test(layers) { return (this.mask & layers.mask) !== 0; } isEnabled(channel) { return (this.mask & (1 << channel | 0)) !== 0; } } 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(); this.isObject3D = true; Object.defineProperty(this, "id", { value: _object3DId++ }); this.uuid = generateUUID(); this.name = ""; this.type = "Object3D"; this.parent = null; this.children = []; 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); this.position = position; this.rotation = rotation; this.quaternion = quaternion; this.scale = scale; Object.defineProperties(this, { position: { writable: false }, rotation: { writable: false }, quaternion: { writable: false }, scale: { writable: false }, modelViewMatrix: { // enumerable: false, // configurable: false, // writable: false, value: new Matrix4() }, normalMatrix: { // enumerable: false, // configurable: false, // writable: false, value: new Matrix3() } }); function onRotationChange() { quaternion.setFromEuler(rotation, false); } function onQuaternionChange() { rotation.setFromQuaternion(quaternion, void 0, false); } rotation._onChange(onRotationChange); quaternion._onChange(onQuaternionChange); this.matrix = new Matrix4(); this.matrixWorld = new Matrix4(); this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; this.matrixWorldNeedsUpdate = false; this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; this.layers = new Layers(); this.visible = true; this.castShadow = false; this.receiveShadow = false; this.frustumCulled = true; this.renderOrder = 0; this.animations = []; this.userData = {}; } onBeforeRender() { } onAfterRender() { } 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) { this.quaternion.setFromAxisAngle(axis, angle); } setRotationFromEuler(euler) { this.quaternion.setFromEuler(euler, true); } setRotationFromMatrix(m) { this.quaternion.setFromRotationMatrix(m); } setRotationFromQuaternion(q) { this.quaternion.copy(q); } rotateOnAxis(axis, angle) { _q1.setFromAxisAngle(axis, angle); this.quaternion.multiply(_q1); return this; } rotateOnWorldAxis(axis, angle) { _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) { _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) { this.updateWorldMatrix(true, false); return vector.applyMatrix4(this.matrixWorld); } worldToLocal(vector) { this.updateWorldMatrix(true, false); return vector.applyMatrix4(_m1.copy(this.matrixWorld).invert()); } lookAt(x, y, z) { 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("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("Object3D.add: object not an instance of 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() { return this.remove(...this.children); } attach(object) { 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 !== void 0) { return object; } } return void 0; } getObjectsByProperty(name, value) { let result = []; if (this[name] === value) result.push(this); for (let i = 0, l = this.children.length; i < l; i++) { const childResult = this.children[i].getObjectsByProperty(name, value); if (childResult.length > 0) { result = result.concat(childResult); } } return result; } 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() { } 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; } const children = this.children; for (let i = 0, l = children.length; i < l; i++) { const child = children[i]; if (child.matrixWorldAutoUpdate === true || force === true) { child.updateMatrixWorld(force); } } } updateWorldMatrix(updateParents, updateChildren) { const parent = this.parent; if (updateParents === true && parent !== null && parent.matrixWorldAutoUpdate === true) { 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); } if (updateChildren === true) { const children = this.children; for (let i = 0, l = children.length; i < l; i++) { const child = children[i]; if (child.matrixWorldAutoUpdate === true) { child.updateWorldMatrix(false, true); } } } } toJSON(meta) { const isRootObject = meta === void 0 || typeof meta === "string"; const output = {}; if (isRootObject) { meta = { geometries: {}, materials: {}, textures: {}, images: {}, shapes: {}, skeletons: {}, animations: {}, nodes: {} }; output.metadata = { version: 4.5, type: "Object", generator: "Object3D.toJSON" }; } 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; 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] === void 0) { 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 !== void 0 && parameters.shapes !== void 0) { 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 !== void 0) { serialize(meta.skeletons, this.skeleton); object.skeleton = this.skeleton.uuid; } } if (this.material !== void 0) { 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; 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.rotation.copy(source.rotation, false); this.quaternion.copy(source.quaternion, false); this.scale.copy(source.scale); this.matrix.copy(source.matrix); this.matrixWorld.copy(source.matrixWorld); this.matrixAutoUpdate = source.matrixAutoUpdate; this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; 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; } } Object3D.DEFAULT_UP = /* @__PURE__ */ new Vector3(0, 1, 0); Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; class Raycaster { constructor(origin, direction, near = 0, far = Infinity) { this.ray = new Ray(origin, direction); this.near = near; this.far = far; this.camera = null; this.layers = new Layers(); this.params = { Mesh: {}, Line: { threshold: 1 }, LOD: {}, Points: { threshold: 1 }, Sprite: {} }; } set(origin, direction) { this.ray.set(origin, direction); } setFromCamera(coords, camera) { if (camera.isPerspectiveCamera) { this.ray.origin.setFromMatrixPosition(camera.matrixWorld); this.ray.direction.set(coords.x, coords.y, 0.5).unproject(camera).sub(this.ray.origin).normalize(); this.camera = camera; } else if (camera.isOrthographicCamera) { this.ray.origin.set(coords.x, coords.y, (camera.near + camera.far) / (camera.near - camera.far)).unproject(camera); this.ray.direction.set(0, 0, -1).transformDirection(camera.matrixWorld); this.camera = camera; } else { console.error("Raycaster: Unsupported camera type: " + camera.type); } } intersectObject(object, recursive = true, intersects = []) { intersectObject(object, this, intersects, recursive); intersects.sort(ascSort); return intersects; } intersectObjects(objects, recursive = true, intersects = []) { for (let i = 0, l = objects.length; i < l; i++) { intersectObject(objects[i], this, intersects, recursive); } intersects.sort(ascSort); return intersects; } } function ascSort(a, b) { return a.distance - b.distance; } function intersectObject(object, raycaster, intersects, recursive) { if (object.layers.test(raycaster.layers)) { object.raycast(raycaster, intersects); } if (recursive === true) { const children = object.children; for (let i = 0, l = children.length; i < l; i++) { intersectObject(children[i], raycaster, intersects, true); } } } export { EventDispatcher, Layers, Object3D, Raycaster };