UNPKG

gpu-curtains

Version:

gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.

319 lines (318 loc) 9.99 kB
import { Quat } from "../../math/Quat.mjs"; import { Vec3 } from "../../math/Vec3.mjs"; import { Mat4 } from "../../math/Mat4.mjs"; //#region src/core/objects3D/Object3D.ts let objectIndex = 0; const tempMatrix = new Mat4(); /** * Used to create an object with transformation properties such as position, scale, rotation and transform origin {@link Vec3 | vectors} and a {@link Quat | quaternion} in order to compute the {@link Object3D#modelMatrix | model matrix} and {@link Object3D#worldMatrix | world matrix}. * * If an {@link Object3D} does not have any {@link Object3D#parent | parent}, then its {@link Object3D#modelMatrix | model matrix} and {@link Object3D#worldMatrix | world matrix} are the same. * * The transformations {@link Vec3 | vectors} are reactive to changes, which mean that updating one of their components will automatically update the {@link Object3D#modelMatrix | model matrix} and {@link Object3D#worldMatrix | world matrix}. */ var Object3D = class { /** Parent {@link Object3D} in the scene graph, used to compute the {@link worldMatrix}. */ #parent; /** Whether this {@link Object3D} and all its {@link children} should be considered as visible. Default to `true`. */ #visible; /** Set to `false` if at least one of the {@link Object3D} parent is not visible. */ #parentVisibility; /** * Object3D constructor */ constructor() { this.#parent = null; this.children = []; this.matricesNeedUpdate = false; this.up = new Vec3(0, 1, 0); this.actualPosition = new Vec3(); Object.defineProperty(this, "object3DIndex", { value: objectIndex++ }); this.setMatrices(); this.setTransforms(); this.#visible = true; this.#parentVisibility = true; } /** * Get whether this {@link Object3D} is visible (if it is itself visible, and all its parents are visible as well). */ get visible() { return this.#visible && this.#parentVisibility; } /** * Set this {@link Object3D} visible property, and its children `parentVisibility` property. * @param value - New visibility value. */ set visible(value) { this.#visible = value; this.children.forEach((c) => c.parentVisibility = this.parentVisibility && value); } /** * Get whether all this {@link Object3D} parents are visible or not. Should not be used directly. */ get parentVisibility() { return this.#parentVisibility; } /** * Set to `false` if at least one of this {@link Object3D} parent is not visible, `true` otherwise. Should not be used directly. * @param value - New parent visibility value. */ set parentVisibility(value) { this.#parentVisibility = value; this.children.forEach((child) => child.parentVisibility = this.visible && value); } /** * Get the parent of this {@link Object3D} if any */ get parent() { return this.#parent; } /** * Set the parent of this {@link Object3D} * @param value - new parent to set, could be an {@link Object3D} or null */ set parent(value) { if (this.#parent && value && this.#parent.object3DIndex === value.object3DIndex) return; if (this.#parent) this.#parent.children = this.#parent.children.filter((child) => child.object3DIndex !== this.object3DIndex); if (value) this.shouldUpdateWorldMatrix(); this.#parent = value; this.#parent?.children.push(this); this.parentVisibility = this.#parent ? this.#parent.visible : true; } /** * Set our transforms properties and {@link Vec3#onChange | vectors onChange} callbacks */ setTransforms() { this.transforms = { origin: { model: new Vec3() }, quaternion: new Quat(), rotation: new Vec3(), position: { world: new Vec3() }, scale: new Vec3(1) }; this.rotation.onChange(() => this.applyRotation()); this.position.onChange(() => this.applyPosition()); this.scale.onChange(() => this.applyScale()); this.transformOrigin.onChange(() => this.applyTransformOrigin()); } /** * Get our rotation {@link Vec3 | vector} */ get rotation() { return this.transforms.rotation; } /** * Set our rotation {@link Vec3 | vector} * @param value - new rotation {@link Vec3 | vector} */ set rotation(value) { this.transforms.rotation = value; this.applyRotation(); } /** * Get our {@link Quat | quaternion} */ get quaternion() { return this.transforms.quaternion; } /** * Set our {@link Quat | quaternion} * @param value - new {@link Quat | quaternion} */ set quaternion(value) { this.transforms.quaternion = value; } /** * Get our position {@link Vec3 | vector} */ get position() { return this.transforms.position.world; } /** * Set our position {@link Vec3 | vector} * @param value - new position {@link Vec3 | vector} */ set position(value) { this.transforms.position.world = value; } /** * Get our scale {@link Vec3 | vector} */ get scale() { return this.transforms.scale; } /** * Set our scale {@link Vec3 | vector} * @param value - new scale {@link Vec3 | vector} */ set scale(value) { this.transforms.scale = value; this.applyScale(); } /** * Get our transform origin {@link Vec3 | vector} */ get transformOrigin() { return this.transforms.origin.model; } /** * Set our transform origin {@link Vec3 | vector} * @param value - new transform origin {@link Vec3 | vector} */ set transformOrigin(value) { this.transforms.origin.model = value; } /** * Apply our rotation and tell our {@link modelMatrix | model matrix} to update */ applyRotation() { this.quaternion.setFromVec3(this.rotation); this.shouldUpdateModelMatrix(); } /** * Tell our {@link modelMatrix | model matrix} to update */ applyPosition() { this.shouldUpdateModelMatrix(); } /** * Tell our {@link modelMatrix | model matrix} to update */ applyScale() { this.shouldUpdateModelMatrix(); } /** * Tell our {@link modelMatrix | model matrix} to update */ applyTransformOrigin() { this.shouldUpdateModelMatrix(); } /** * Set our {@link modelMatrix | model matrix} and {@link worldMatrix | world matrix} */ setMatrices() { this.matrices = { model: { matrix: new Mat4(), shouldUpdate: true, onUpdate: () => this.updateModelMatrix() }, world: { matrix: new Mat4(), shouldUpdate: true, onUpdate: () => this.updateWorldMatrix() } }; } /** * Get our {@link Mat4 | model matrix} */ get modelMatrix() { return this.matrices.model.matrix; } /** * Set our {@link Mat4 | model matrix} * @param value - new {@link Mat4 | model matrix} */ set modelMatrix(value) { this.matrices.model.matrix = value; this.shouldUpdateModelMatrix(); } /** * Set our {@link modelMatrix | model matrix} shouldUpdate flag to true (tell it to update) */ shouldUpdateModelMatrix() { this.matrices.model.shouldUpdate = true; this.shouldUpdateWorldMatrix(); } /** * Get our {@link Mat4 | world matrix} */ get worldMatrix() { return this.matrices.world.matrix; } /** * Set our {@link Mat4 | world matrix} * @param value - new {@link Mat4 | world matrix} */ set worldMatrix(value) { this.matrices.world.matrix = value; this.shouldUpdateWorldMatrix(); } /** * Set our {@link worldMatrix | world matrix} shouldUpdate flag to true (tell it to update) */ shouldUpdateWorldMatrix() { this.matrices.world.shouldUpdate = true; } /** * Rotate this {@link Object3D} so it looks at the {@link Vec3 | target}. * @param target - {@link Vec3} to look at. Default to `new Vec3()`. */ lookAt(target = new Vec3()) { this.updateModelMatrix(); this.updateWorldMatrix(true, false); if (this.actualPosition.x === 0 && this.actualPosition.y !== 0 && this.actualPosition.z === 0) this.up.set(0, 0, 1); else this.up.set(0, 1, 0); this.applyLookAt(target, this.actualPosition); } /** * Apply a look at rotation based on a target, a position and our {@link up} vectors. * @param target - {@link Vec3} target to look at. * @param position - {@link Vec3} position from which to look at. */ applyLookAt(target, position) { const rotation = tempMatrix.lookAt(target, position, this.up); this.quaternion.setFromRotationMatrix(rotation); this.shouldUpdateModelMatrix(); } /** * Update our {@link modelMatrix | model matrix} */ updateModelMatrix() { this.modelMatrix = this.modelMatrix.composeFromOrigin(this.position, this.quaternion, this.scale, this.transformOrigin); this.shouldUpdateWorldMatrix(); } /** * Update our {@link worldMatrix | model matrix}. * @param updateParents - Whether to update the {@link parent} {@link worldMatrix} beforehand. Default to `false`. * @param updateChildren - Whether to update the {@link children} {@link worldMatrix} afterward. Default to `true`. */ updateWorldMatrix(updateParents = false, updateChildren = true) { if (!this.parent) this.worldMatrix.copy(this.modelMatrix); else { if (updateParents) this.parent.updateWorldMatrix(true, false); this.worldMatrix.multiplyMatrices(this.parent.worldMatrix, this.modelMatrix); } this.worldMatrix.getTranslation(this.actualPosition); if (updateChildren) for (let i = 0, l = this.children.length; i < l; i++) this.children[i].shouldUpdateWorldMatrix(); } /** * Check whether at least one of the matrix should be updated */ shouldUpdateMatrices() { this.matricesNeedUpdate = !!Object.values(this.matrices).find((matrix) => matrix.shouldUpdate); } /** * Check at each render whether we should update our matrices, and update them if needed */ updateMatrixStack() { this.shouldUpdateMatrices(); if (this.matricesNeedUpdate) { for (const matrixName in this.matrices) if (this.matrices[matrixName].shouldUpdate) { this.matrices[matrixName].onUpdate(); this.matrices[matrixName].shouldUpdate = false; } } for (let i = 0, l = this.children.length; i < l; i++) this.children[i].updateMatrixStack(); } /** * Destroy this {@link Object3D}. Removes its parent and set its children free. */ destroy() { for (let i = 0, l = this.children.length; i < l; i++) if (this.children[i]) this.children[i].parent = null; this.parent = null; } }; //#endregion export { Object3D };