src/viewer/scene/model/SceneModelTransform.js
import {math} from "../math/index.js";
const angleAxis = math.vec4(4);
const q1 = math.vec4();
const q2 = math.vec4();
const xAxis = math.vec3([1, 0, 0]);
const yAxis = math.vec3([0, 1, 0]);
const zAxis = math.vec3([0, 0, 1]);
const veca = math.vec3(3);
const vecb = math.vec3(3);
const identityMat = math.identityMat4();
/**
* A dynamically-updatable transform within a {@link SceneModel}.
*
* * Can be composed into hierarchies
* * Shared by multiple {@link SceneModelMesh}es
* * Created with {@link SceneModel#createTransform}
* * Stored by ID in {@link SceneModel#transforms}
* * Referenced by {@link SceneModelMesh#transform}
*/
export class SceneModelTransform {
/**
* @private
*/
constructor(cfg) {
this._model = cfg.model;
/**
* Unique ID of this SceneModelTransform.
*
* The SceneModelTransform is registered against this ID in {@link SceneModel#transforms}.
*/
this.id = cfg.id;
this._parentTransform = cfg.parent;
this._childTransforms = [];
this._meshes = [];
this._scale = new Float32Array([1,1,1]);
this._quaternion = math.identityQuaternion(new Float32Array(4));
this._rotation = new Float32Array(3);
this._position = new Float32Array(3);
this._localMatrix = math.identityMat4(new Float32Array(16));
this._worldMatrix = math.identityMat4(new Float32Array(16));
this._localMatrixDirty = true;
this._worldMatrixDirty = true;
if (cfg.matrix) {
this.matrix = cfg.matrix;
} else {
this.scale = cfg.scale;
this.position = cfg.position;
if (cfg.quaternion) {
} else {
this.rotation = cfg.rotation;
}
}
if (cfg.parent) {
cfg.parent._addChildTransform(this);
}
}
_addChildTransform(childTransform) {
this._childTransforms.push(childTransform);
childTransform._parentTransform = this;
childTransform._transformDirty();
childTransform._setSubtreeAABBsDirty(this);
}
_addMesh(mesh) {
this._meshes.push(mesh);
mesh.transform = this;
// childTransform._setWorldMatrixDirty();
// childTransform._setAABBDirty();
}
/**
* The optional parent SceneModelTransform.
*
* @type {SceneModelTransform}
*/
get parentTransform() {
return this._parentTransform;
}
/**
* The {@link SceneModelMesh}es transformed by this SceneModelTransform.
*
* @returns {[]}
*/
get meshes() {
return this._meshes;
}
/**
* Sets the SceneModelTransform's local translation.
*
* Default value is ````[0,0,0]````.
*
* @type {Number[]}
*/
set position(value) {
this._position.set(value || [0, 0, 0]);
this._setLocalMatrixDirty();
this._model.glRedraw();
}
/**
* Gets the SceneModelTransform's translation.
*
* Default value is ````[0,0,0]````.
*
* @type {Number[]}
*/
get position() {
return this._position;
}
/**
* Sets the SceneModelTransform's rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.
*
* Default value is ````[0,0,0]````.
*
* @type {Number[]}
*/
set rotation(value) {
this._rotation.set(value || [0, 0, 0]);
math.eulerToQuaternion(this._rotation, "XYZ", this._quaternion);
this._setLocalMatrixDirty();
this._model.glRedraw();
}
/**
* Gets the SceneModelTransform's rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.
*
* Default value is ````[0,0,0]````.
*
* @type {Number[]}
*/
get rotation() {
return this._rotation;
}
/**
* Sets the SceneModelTransform's rotation quaternion.
*
* Default value is ````[0,0,0,1]````.
*
* @type {Number[]}
*/
set quaternion(value) {
this._quaternion.set(value || [0, 0, 0, 1]);
math.quaternionToEuler(this._quaternion, "XYZ", this._rotation);
this._setLocalMatrixDirty();
this._model.glRedraw();
}
/**
* Gets the SceneModelTransform's rotation quaternion.
*
* Default value is ````[0,0,0,1]````.
*
* @type {Number[]}
*/
get quaternion() {
return this._quaternion;
}
/**
* Sets the SceneModelTransform's scale.
*
* Default value is ````[1,1,1]````.
*
* @type {Number[]}
*/
set scale(value) {
this._scale.set(value || [1, 1, 1]);
this._setLocalMatrixDirty();
this._model.glRedraw();
}
/**
* Gets the SceneModelTransform's scale.
*
* Default value is ````[1,1,1]````.
*
* @type {Number[]}
*/
get scale() {
return this._scale;
}
/**
* Sets the SceneModelTransform's transform matrix.
*
* Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.
*
* @type {Number[]}
*/
set matrix(value) {
if (!this._localMatrix) {
this._localMatrix = math.identityMat4();
}
this._localMatrix.set(value || identityMat);
math.decomposeMat4(this._localMatrix, this._position, this._quaternion, this._scale);
this._localMatrixDirty = false;
this._transformDirty();
this._model.glRedraw();
}
/**
* Gets the SceneModelTransform's transform matrix.
*
* Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````.
*
* @type {Number[]}
*/
get matrix() {
if (this._localMatrixDirty) {
if (!this._localMatrix) {
this._localMatrix = math.identityMat4();
}
math.composeMat4(this._position, this._quaternion, this._scale, this._localMatrix);
this._localMatrixDirty = false;
}
return this._localMatrix;
}
/**
* Gets the SceneModelTransform's World matrix.
*
* @property worldMatrix
* @type {Number[]}
*/
get worldMatrix() {
if (this._worldMatrixDirty) {
this._buildWorldMatrix();
}
return this._worldMatrix;
}
/**
* Rotates the SceneModelTransform about the given axis by the given increment.
*
* @param {Number[]} axis Local axis about which to rotate.
* @param {Number} angle Angle increment in degrees.
*/
rotate(axis, angle) {
angleAxis[0] = axis[0];
angleAxis[1] = axis[1];
angleAxis[2] = axis[2];
angleAxis[3] = angle * math.DEGTORAD;
math.angleAxisToQuaternion(angleAxis, q1);
math.mulQuaternions(this.quaternion, q1, q2);
this.quaternion = q2;
this._setLocalMatrixDirty();
this._model.glRedraw();
return this;
}
/**
* Rotates the SceneModelTransform about the given World-space axis by the given increment.
*
* @param {Number[]} axis Local axis about which to rotate.
* @param {Number} angle Angle increment in degrees.
*/
rotateOnWorldAxis(axis, angle) {
angleAxis[0] = axis[0];
angleAxis[1] = axis[1];
angleAxis[2] = axis[2];
angleAxis[3] = angle * math.DEGTORAD;
math.angleAxisToQuaternion(angleAxis, q1);
math.mulQuaternions(q1, this.quaternion, q1);
//this.quaternion.premultiply(q1);
return this;
}
/**
* Rotates the SceneModelTransform about the local X-axis by the given increment.
*
* @param {Number} angle Angle increment in degrees.
*/
rotateX(angle) {
return this.rotate(xAxis, angle);
}
/**
* Rotates the SceneModelTransform about the local Y-axis by the given increment.
*
* @param {Number} angle Angle increment in degrees.
*/
rotateY(angle) {
return this.rotate(yAxis, angle);
}
/**
* Rotates the SceneModelTransform about the local Z-axis by the given increment.
*
* @param {Number} angle Angle increment in degrees.
*/
rotateZ(angle) {
return this.rotate(zAxis, angle);
}
/**
* Translates the SceneModelTransform along the local axis by the given increment.
*
* @param {Number[]} axis Normalized local space 3D vector along which to translate.
* @param {Number} distance Distance to translate along the vector.
*/
translate(axis) {
this._position[0] += axis[0];
this._position[1] += axis[1];
this._position[2] += axis[2];
this._setLocalMatrixDirty();
this._model.glRedraw();
return this;
}
/**
* Translates the SceneModelTransform along the local X-axis by the given increment.
*
* @param {Number} distance Distance to translate along the X-axis.
*/
translateX(distance) {
this._position[0] += distance;
this._setLocalMatrixDirty();
this._model.glRedraw();
return this;
}
/**
* Translates the SceneModelTransform along the local Y-axis by the given increment.
*
* @param {Number} distance Distance to translate along the Y-axis.
*/
translateY(distance) {
this._position[1] += distance;
this._setLocalMatrixDirty();
this._model.glRedraw();
return this;
}
/**
* Translates the SceneModelTransform along the local Z-axis by the given increment.
*
* @param {Number} distance Distance to translate along the Z-axis.
*/
translateZ(distance) {
this._position[2] += distance;
this._setLocalMatrixDirty();
this._model.glRedraw();
return this;
}
_setLocalMatrixDirty() {
this._localMatrixDirty = true;
this._transformDirty();
}
_transformDirty() {
this._worldMatrixDirty = true;
for (let i = 0, len = this._childTransforms.length; i < len; i++) {
const childTransform = this._childTransforms[i];
childTransform._transformDirty();
if (childTransform._meshes && childTransform._meshes.length > 0) {
const meshes = childTransform._meshes;
for (let j =0, lenj = meshes.length; j < lenj; j++) {
meshes[j]._transformDirty();
}
}
}
if (this._meshes && this._meshes.length > 0) {
const meshes = this._meshes;
for (let j =0, lenj = meshes.length; j < lenj; j++) {
meshes[j]._transformDirty();
}
}
}
_buildWorldMatrix() {
const localMatrix = this.matrix;
if (!this._parentTransform) {
for (let i = 0, len = localMatrix.length; i < len; i++) {
this._worldMatrix[i] = localMatrix[i];
}
} else {
math.mulMat4(this._parentTransform.worldMatrix, localMatrix, this._worldMatrix);
}
this._worldMatrixDirty = false;
}
_setSubtreeAABBsDirty(sceneTransform) {
sceneTransform._aabbDirty = true;
if (sceneTransform._childTransforms) {
for (let i = 0, len = sceneTransform._childTransforms.length; i < len; i++) {
this._setSubtreeAABBsDirty(sceneTransform._childTransforms[i]);
}
}
}
}