Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

122 changes: 104 additions & 18 deletions src/core/math/mat4.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,74 @@ class Mat4 {
return this;
}

/**
* Multiplies two affine transformation matrices that are known to have unit scale.
* This is an optimized version of mulAffine2 for transforms containing only rotation
* and translation (no scale). Both input matrices must have unit scale for correct results.
*
* @param {Mat4} lhs - The 4x4 matrix used as the first multiplicand of the operation (parent transform).
* @param {Mat4} rhs - The 4x4 matrix used as the second multiplicand of the operation (child transform).
* @returns {Mat4} Self for chaining.
* @private
*/
mulAffine2NoScale(lhs, rhs) {
const a = lhs.data;
const b = rhs.data;
const r = this.data;

const a00 = a[0];
const a01 = a[1];
const a02 = a[2];
const a10 = a[4];
const a11 = a[5];
const a12 = a[6];
const a20 = a[8];
const a21 = a[9];
const a22 = a[10];
const a30 = a[12];
const a31 = a[13];
const a32 = a[14];

// For unit scale transforms, the 3x3 rotation matrix multiplication
// is the same as mulAffine2, but we can be confident that no scale
// is being compounded through the hierarchy
let b0, b1, b2;

b0 = b[0];
b1 = b[1];
b2 = b[2];
r[0] = a00 * b0 + a10 * b1 + a20 * b2;
r[1] = a01 * b0 + a11 * b1 + a21 * b2;
r[2] = a02 * b0 + a12 * b1 + a22 * b2;
r[3] = 0;

b0 = b[4];
b1 = b[5];
b2 = b[6];
r[4] = a00 * b0 + a10 * b1 + a20 * b2;
r[5] = a01 * b0 + a11 * b1 + a21 * b2;
r[6] = a02 * b0 + a12 * b1 + a22 * b2;
r[7] = 0;

b0 = b[8];
b1 = b[9];
b2 = b[10];
r[8] = a00 * b0 + a10 * b1 + a20 * b2;
r[9] = a01 * b0 + a11 * b1 + a21 * b2;
r[10] = a02 * b0 + a12 * b1 + a22 * b2;
r[11] = 0;

b0 = b[12];
b1 = b[13];
b2 = b[14];
r[12] = a00 * b0 + a10 * b1 + a20 * b2 + a30;
r[13] = a01 * b0 + a11 * b1 + a21 * b2 + a31;
r[14] = a02 * b0 + a12 * b1 + a22 * b2 + a32;
r[15] = 1;

return this;
}

/**
* Multiplies the current instance by the specified 4x4 matrix.
*
Expand Down Expand Up @@ -965,10 +1033,6 @@ class Mat4 {
const qz = r.z;
const qw = r.w;

const sx = s.x;
const sy = s.y;
const sz = s.z;

const x2 = qx + qx;
const y2 = qy + qy;
const z2 = qz + qz;
Expand All @@ -984,20 +1048,42 @@ class Mat4 {

const m = this.data;

m[0] = (1 - (yy + zz)) * sx;
m[1] = (xy + wz) * sx;
m[2] = (xz - wy) * sx;
m[3] = 0;

m[4] = (xy - wz) * sy;
m[5] = (1 - (xx + zz)) * sy;
m[6] = (yz + wx) * sy;
m[7] = 0;

m[8] = (xz + wy) * sz;
m[9] = (yz - wx) * sz;
m[10] = (1 - (xx + yy)) * sz;
m[11] = 0;
// Fast path for unit scale - skip 9 scale multiplications
if (s.x === 1 && s.y === 1 && s.z === 1) {
m[0] = 1 - (yy + zz);
m[1] = xy + wz;
m[2] = xz - wy;
m[3] = 0;

m[4] = xy - wz;
m[5] = 1 - (xx + zz);
m[6] = yz + wx;
m[7] = 0;

m[8] = xz + wy;
m[9] = yz - wx;
m[10] = 1 - (xx + yy);
m[11] = 0;
} else {
const sx = s.x;
const sy = s.y;
const sz = s.z;

m[0] = (1 - (yy + zz)) * sx;
m[1] = (xy + wz) * sx;
m[2] = (xz - wy) * sx;
m[3] = 0;

m[4] = (xy - wz) * sy;
m[5] = (1 - (xx + zz)) * sy;
m[6] = (yz + wx) * sy;
m[7] = 0;

m[8] = (xz + wy) * sz;
m[9] = (yz - wx) * sz;
m[10] = (1 - (xx + yy)) * sz;
m[11] = 0;
}

m[12] = t.x;
m[13] = t.y;
Expand Down
55 changes: 54 additions & 1 deletion src/scene/graph-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,24 @@ class GraphNode extends EventHandler {
*/
_worldScaleSign = 0;

/**
* Tracks whether this node's localScale is exactly (1, 1, 1). Used to optimize transform
* calculations by skipping scale multiplications.
*
* @type {boolean}
* @private
*/
_hasUnitScale = true;

/**
* Tracks whether this node and all of its ancestors up to the root have unit scale.
* Enables significant optimizations in hierarchy transformation calculations.
*
* @type {boolean}
* @private
*/
_ancestorsHaveUnitScale = true;

/**
* @type {Mat3}
* @private
Expand Down Expand Up @@ -1137,6 +1155,15 @@ class GraphNode extends EventHandler {
this.localScale.set(x, y, z);
}

// Update unit scale flag
const hadUnitScale = this._hasUnitScale;
this._hasUnitScale = (this.localScale.x === 1 && this.localScale.y === 1 && this.localScale.z === 1);

// If unit scale status changed, propagate to children
if (hadUnitScale !== this._hasUnitScale) {
this._updateAncestorsHaveUnitScale();
}

if (!this._dirtyLocal) {
this._dirtifyLocal();
}
Expand Down Expand Up @@ -1185,6 +1212,21 @@ class GraphNode extends EventHandler {
this._aabbVer++;
}

/** @private */
_updateAncestorsHaveUnitScale() {
// Recalculate whether this node and all ancestors have unit scale
const newValue = this._hasUnitScale && (!this._parent || this._parent._ancestorsHaveUnitScale);

if (this._ancestorsHaveUnitScale !== newValue) {
this._ancestorsHaveUnitScale = newValue;

// Propagate change to all children
for (let i = 0; i < this._children.length; i++) {
this._children[i]._updateAncestorsHaveUnitScale();
}
}
}

/**
* Sets the world space position of the specified graph node.
*
Expand Down Expand Up @@ -1457,6 +1499,9 @@ class GraphNode extends EventHandler {
// The graph depth of the child and all of its descendants will now change
node._updateGraphDepth();

// Update unit scale flags for the child and its descendants
node._updateAncestorsHaveUnitScale();

// The child (plus subhierarchy) will need world transforms to be recalculated
node._dirtifyWorld();
// node might be already marked as dirty, in that case the whole chain stays frozen, so let's enforce unfreeze
Expand Down Expand Up @@ -1504,6 +1549,9 @@ class GraphNode extends EventHandler {
// Clear parent
child._parent = null;

// Update unit scale flags for the removed child and its descendants
child._updateAncestorsHaveUnitScale();

// NOTE: see PR #4047 - this fix is removed for now as it breaks other things
// notify the child hierarchy it has been removed from the parent,
// which marks them as not enabled in hierarchy
Expand Down Expand Up @@ -1569,7 +1617,12 @@ class GraphNode extends EventHandler {
this.worldTransform.setTRS(scaleCompensatePos, scaleCompensateRot, scale);

} else {
this.worldTransform.mulAffine2(this._parent.worldTransform, this.localTransform);
// Use optimized matrix multiplication for unit scale chains
if (this._hasUnitScale && this._parent._ancestorsHaveUnitScale) {
this.worldTransform.mulAffine2NoScale(this._parent.worldTransform, this.localTransform);
} else {
this.worldTransform.mulAffine2(this._parent.worldTransform, this.localTransform);
}
}
}

Expand Down