oktaeder/src/data/Node.ts
2023-08-15 22:18:08 +02:00

322 lines
6.7 KiB
TypeScript

/*!
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Camera, Light, Material, Matrix4x4, Mesh, Quaternion, QuaternionObject, Vector3, Vector3Object } from ".";
export interface NodeProps {
readonly name?: string;
readonly translation?: Vector3Object;
readonly rotation?: QuaternionObject;
readonly scale?: Vector3Object;
readonly camera?: Camera | null;
readonly light?: Light | null;
readonly mesh?: Mesh | null;
readonly materials?: Material[];
readonly children?: Node[];
}
export class Node {
declare readonly type: "Node";
_name: string;
_translation: Vector3;
_rotation: Quaternion;
_scale: Vector3;
/** unique */
_camera: Camera | null;
/** unique */
_light: Light | null;
/** shared */
_mesh: Mesh | null;
/** shared */
_materials: Material[];
/** unique */
_children: Node[];
/** backreference */
_parent: Node | null;
_localMatrixNeedsUpdate: boolean;
_localMatrix: Matrix4x4;
_worldMatrixNeedsUpdate: boolean;
_worldMatrix: Matrix4x4;
constructor({
name = "",
translation,
rotation,
scale,
camera = null,
light = null,
mesh = null,
materials = [],
children = [],
}: NodeProps) {
this._name = name;
this._translation = translation !== undefined ? Vector3.fromObject(translation) : Vector3.zero();
this._rotation = rotation !== undefined ? Quaternion.fromObject(rotation) : Quaternion.identity();
this._scale = scale !== undefined ? Vector3.fromObject(scale) : Vector3.one();
this._camera = camera;
this._light = light;
this._mesh = mesh;
this._materials = materials;
this._children = children;
this._parent = null;
this._localMatrixNeedsUpdate = true;
this._localMatrix = new Matrix4x4(
NaN, NaN, NaN, NaN,
NaN, NaN, NaN, NaN,
NaN, NaN, NaN, NaN,
NaN, NaN, NaN, NaN,
);
this._worldMatrixNeedsUpdate = true;
this._worldMatrix = new Matrix4x4(
NaN, NaN, NaN, NaN,
NaN, NaN, NaN, NaN,
NaN, NaN, NaN, NaN,
NaN, NaN, NaN, NaN,
);
if (this._camera !== null) {
this._camera._node = this;
}
if (this._light !== null) {
this._light._node = this;
}
if (this._children !== null) {
for (const child of this._children) {
child._parent = this;
}
}
}
set name(value: string) { this._name = value; }
get name(): string { return this._name; }
setTranslation(value: Vector3Object): Node {
this._translation.setObject(value);
this._localMatrixNeedsUpdate = true;
this._setWorldMatrixNeedsUpdateRecursive(true);
return this;
}
getTranslation(res: Vector3): Vector3 {
return res.setObject(this._translation);
}
setRotation(value: QuaternionObject): Node {
this._rotation.setObject(value);
this._localMatrixNeedsUpdate = true;
this._setWorldMatrixNeedsUpdateRecursive(true);
return this;
}
getRotation(res: Quaternion): Quaternion {
return res.setObject(this._rotation);
}
setScale(value: Vector3Object): Node {
this._scale.setObject(value);
this._localMatrixNeedsUpdate = true;
this._setWorldMatrixNeedsUpdateRecursive(true);
return this;
}
getScale(res: Vector3): Vector3 {
return res.setObject(this._scale);
}
set camera(value: Camera | null) {
if (value !== null) {
this.attachCamera(value);
} else {
this.detachCamera();
}
}
get camera(): Camera | null { return this._camera; }
attachCamera(camera: Camera): Node {
if (this._camera !== null) {
this._camera._node = null;
}
this._camera = camera;
if (camera._node !== null) {
camera._node._camera = null;
}
camera._node = this;
this._camera = camera;
return this;
}
detachCamera(): Node {
if (this._camera === null) {
return this;
}
this._camera._node = null;
this._camera = null;
return this;
}
set light(value: Light | null) {
if (value !== null) {
this.attachLight(value);
} else {
this.detachLight();
}
}
get light(): Light | null { return this._light; }
attachLight(light: Light): Node {
if (this._light !== null) {
this._light._node = null;
}
this._light = light;
if (light._node !== null) {
light._node._light = null;
}
light._node = this;
this._light = light;
return this;
}
detachLight(): Node {
if (this._light === null) {
return this;
}
this._light._node = null;
this._light = null;
return this;
}
set mesh(value: Mesh | null) { this._mesh = value; }
get mesh(): Mesh | null { return this._mesh; }
setMaterials(value: readonly Material[]): Node {
this._materials.length = 0;
this._materials.push(...value);
return this;
}
getMaterials(res: Material[]): Material[] {
res.length = 0;
res.push(...this._materials);
return res;
}
// TODO children
set parent(value: Node | null) {
if (value !== null) {
this.attach(value);
} else {
this.detach();
}
}
get parent(): Node | null { return this._parent; }
attach(node: Node): Node {
if (this._parent !== null) {
this._parent._children.splice(this._parent._children.indexOf(this), 1);
}
node._children.push(this);
this._parent = node;
this._setWorldMatrixNeedsUpdateRecursive(true);
return this;
}
detach(): Node {
if (this._parent === null) {
return this;
}
this._parent._children.splice(this._parent._children.indexOf(this), 1);
this._parent = null;
this._setWorldMatrixNeedsUpdateRecursive(true);
return this;
}
getLocalMatrix(res: Matrix4x4): Matrix4x4 {
this._updateLocalMatrix();
res.setObject(this._localMatrix);
return res;
}
getWorldMatrix(res: Matrix4x4): Matrix4x4 {
this._updateWorldMatrix();
res.setObject(this._worldMatrix);
return res;
}
_updateLocalMatrix(): Node {
if (!this._localMatrixNeedsUpdate) {
return this;
}
this._localMatrix.setTranslationRotationScale(this._translation, this._rotation, this._scale);
this._localMatrixNeedsUpdate = false;
return this;
}
_updateWorldMatrix(): Node {
if (!this._worldMatrixNeedsUpdate) {
return this;
}
this._updateLocalMatrix();
if (this._parent !== null) {
this._parent.getWorldMatrix(this._worldMatrix);
this._worldMatrix.premulMatrix(this._localMatrix);
} else {
this._worldMatrix.setObject(this._localMatrix);
}
return this;
}
_setWorldMatrixNeedsUpdateRecursive(value: boolean): Node {
this._localMatrixNeedsUpdate = true;
for (const child of this._children) {
child._setWorldMatrixNeedsUpdateRecursive(value);
}
return this;
}
}
Object.defineProperty(Node.prototype, "type", { value: "Node" });
export function isNode(value: unknown): value is Node {
return Boolean(value) && (value as Node).type === "Node";
}
export function* preOrder(nodes: Iterable<Node>): Generator<Node, void, undefined> {
for (const node of nodes) {
yield node;
yield* node._children;
}
}