@renderlayer/core
Version:
639 lines (633 loc) • 19.6 kB
JavaScript
import { Vector3, generateUUID, Euler, Quaternion, Matrix4, Matrix3, Ray } from '@renderlayer/math';
class EventDispatcher {
// https://github.com/mrdoob/eventdispatcher.js/
#listeners = /* @__PURE__ */ new Map();
addEventListener(type, listener) {
const listeners = this.#listeners;
if (!listeners.has(type)) {
listeners.set(type, []);
}
if (!listeners.get(type).includes(listener)) {
listeners.get(type).push(listener);
}
}
hasEventListener(type, listener) {
const listeners = this.#listeners;
return listeners.has(type) && listeners.get(type).includes(listener);
}
removeEventListener(type, listener) {
const listeners = this.#listeners;
const listenerArray = listeners.get(type);
if (listenerArray !== void 0) {
const index = listenerArray.indexOf(listener);
if (index !== -1) {
listenerArray.splice(index, 1);
}
}
}
dispatchEvent(event) {
const listeners = this.#listeners;
const listenerArray = listeners.get(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 {
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;
}
}
class Object3D extends EventDispatcher {
static DEFAULT_UP = /* @__PURE__ */ new Vector3(0, 1, 0);
static DEFAULT_MATRIX_AUTO_UPDATE = true;
static DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true;
#id = _object3DId++;
uuid = generateUUID();
name = "";
type = "Object3D";
parent = null;
children = [];
up = Object3D.DEFAULT_UP.clone();
#position = new Vector3();
#rotation = new Euler();
#quaternion = new Quaternion();
#scale = new Vector3(1, 1, 1);
#modelViewMatrix = new Matrix4();
#normalMatrix = new Matrix3();
matrix = new Matrix4();
matrixWorld = new Matrix4();
matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE;
matrixWorldNeedsUpdate = false;
matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE;
// checked by the renderer
layers = new Layers();
visible = true;
castShadow = false;
receiveShadow = false;
frustumCulled = true;
renderOrder = 0;
animations = [];
userData = {};
constructor() {
super();
this.rotation._onChange(() => {
this.quaternion.setFromEuler(this.rotation, false);
});
this.quaternion._onChange(() => {
this.rotation.setFromQuaternion(this.quaternion, void 0, false);
});
}
get isObject3D() {
return true;
}
get id() {
return this.#id;
}
get position() {
return this.#position;
}
get rotation() {
return this.#rotation;
}
get quaternion() {
return this.#quaternion;
}
get scale() {
return this.#scale;
}
get modelViewMatrix() {
return this.#modelViewMatrix;
}
get normalMatrix() {
return this.#normalMatrix;
}
onBeforeRender() {
}
onAfterRender() {
}
applyMatrix4(matrix) {
if (this.matrixAutoUpdate) this.updateMatrix();
this.matrix.premultiply(matrix);
this.matrix.decompose(this.position, this.quaternion, this.scale);
}
applyQuaternion(quaternion) {
this.quaternion.premultiply(quaternion);
return this;
}
setRotationFromAxisAngle(axis, angle) {
this.quaternion.setFromAxisAngle(axis, angle);
}
setRotationFromEuler(euler) {
this.quaternion.setFromEuler(euler, true);
}
setRotationFromMatrix(matrix) {
this.quaternion.setFromRotationMatrix(matrix);
}
setRotationFromQuaternion(quaternion) {
this.quaternion.copy(quaternion);
}
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 (const animation of this.animations) {
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 (const child of source.children) {
this.add(child.clone());
}
}
return this;
}
}
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 Raycaster {
ray;
near = 0;
far = Infinity;
camera = null;
layers = new Layers();
params = {
Mesh: {},
Line: { threshold: 1 },
LOD: {},
Points: { threshold: 1 },
Sprite: {}
};
constructor(origin, direction, near = 0, far = Infinity) {
this.ray = new Ray(origin, direction);
this.near = near;
this.far = far;
}
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 };