Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Transform world scale dirty error #2408

Merged
merged 14 commits into from
Nov 27, 2024
155 changes: 105 additions & 50 deletions packages/core/src/Transform.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { MathUtil, Matrix, Matrix3x3, Quaternion, Vector3 } from "@galacean/engine-math";
import { BoolUpdateFlag } from "./BoolUpdateFlag";
import { deepClone, ignoreClone } from "./clone/CloneManager";
import { Component } from "./Component";
import { Entity } from "./Entity";
import { UpdateFlagManager } from "./UpdateFlagManager";
import { assignmentClone, deepClone, ignoreClone } from "./clone/CloneManager";

/**
* Used to implement transformation related functions.
Expand All @@ -27,12 +27,16 @@
private _rotationQuaternion: Quaternion = new Quaternion();
@deepClone
private _scale: Vector3 = new Vector3(1, 1, 1);
@assignmentClone
private _localUniformScaling: boolean = true;
@deepClone
private _worldPosition: Vector3 = new Vector3();
@deepClone
private _worldRotation: Vector3 = new Vector3();
@deepClone
private _worldRotationQuaternion: Quaternion = new Quaternion();
@assignmentClone
private _worldUniformScaling: boolean = true;
@deepClone
private _lossyWorldScale: Vector3 = new Vector3(1, 1, 1);
@deepClone
Expand Down Expand Up @@ -225,12 +229,13 @@

/**
* Local lossy scaling.
* @remarks The value obtained may not be correct under certain conditions(for example, the parent node has scaling,
* and the child node has a rotation), the scaling will be tilted. Vector3 cannot be used to correctly represent the scaling. Must use Matrix3x3.
* @remarks The value obtained may not be correct under certain conditions(for example, the parent node has non-uniform world scaling,
* and the child node has a rotation), the scaling will be tilted.
*/
get lossyWorldScale(): Vector3 {
if (this._isContainDirtyFlag(TransformModifyFlags.WorldScale)) {
if (this._getParentTransform()) {
// Vector3 cannot be used to correctly represent the scaling. Must use Matrix3x3
const scaleMat = this._getScaleMatrix();
const e = scaleMat.elements;
this._lossyWorldScale.set(e[0], e[4], e[8]);
Expand Down Expand Up @@ -258,20 +263,26 @@
if (this._localMatrix !== value) {
this._localMatrix.copyFrom(value);
}

const { _position: position, _rotationQuaternion: rotationQuaternion, _scale: scale } = this;

Check warning on line 266 in packages/core/src/Transform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/Transform.ts#L266

Added line #L266 was not covered by tests
// @ts-ignore
this._position._onValueChanged = this._rotationQuaternion._onValueChanged = this._scale._onValueChanged = null;
this._localMatrix.decompose(this._position, this._rotationQuaternion, this._scale);
position._onValueChanged = rotationQuaternion._onValueChanged = scale._onValueChanged = null;
this._localMatrix.decompose(position, rotationQuaternion, scale);

Check warning on line 269 in packages/core/src/Transform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/Transform.ts#L268-L269

Added lines #L268 - L269 were not covered by tests
// @ts-ignore
this._position._onValueChanged = this._onPositionChanged;
position._onValueChanged = this._onPositionChanged;

Check warning on line 271 in packages/core/src/Transform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/Transform.ts#L271

Added line #L271 was not covered by tests
// @ts-ignore
this._rotationQuaternion._onValueChanged = this._onRotationQuaternionChanged;
rotationQuaternion._onValueChanged = this._onRotationQuaternionChanged;

Check warning on line 273 in packages/core/src/Transform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/Transform.ts#L273

Added line #L273 was not covered by tests
// @ts-ignore
this._scale._onValueChanged = this._onScaleChanged;
scale._onValueChanged = this._onScaleChanged;

Check warning on line 275 in packages/core/src/Transform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/Transform.ts#L275

Added line #L275 was not covered by tests

this._setDirtyFlagTrue(TransformModifyFlags.LocalEuler);
this._setDirtyFlagFalse(TransformModifyFlags.LocalMatrix | TransformModifyFlags.LocalQuat);
this._updateAllWorldFlag();
const localUniformScaling = scale.x === scale.y && scale.y === scale.z;
if (this._localUniformScaling !== localUniformScaling) {
this._localUniformScaling = localUniformScaling;
this._updateAllWorldFlag(TransformModifyFlags.WmWpWeWqWsWus);

Check warning on line 282 in packages/core/src/Transform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/Transform.ts#L281-L282

Added lines #L281 - L282 were not covered by tests
} else {
this._updateAllWorldFlag(TransformModifyFlags.WmWpWeWqWs);

Check warning on line 284 in packages/core/src/Transform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/Transform.ts#L284

Added line #L284 was not covered by tests
}
}

/**
Expand Down Expand Up @@ -563,7 +574,7 @@
*/
_parentChange(): void {
this._isParentDirty = true;
this._updateAllWorldFlag();
this._updateAllWorldFlag(TransformModifyFlags.WmWpWeWqWsWus);
}

/**
Expand Down Expand Up @@ -603,9 +614,9 @@
private _updateWorldPositionFlag(): void {
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWp)) {
this._worldAssociatedChange(TransformModifyFlags.WmWp);
const nodeChildren = this._entity._children;
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
nodeChildren[i].transform?._updateWorldPositionFlag();
const children = this._entity._children;
for (let i = 0, n = children.length; i < n; i++) {
children[i].transform?._updateWorldPositionFlag();
}
}
}
Expand All @@ -615,14 +626,19 @@
* Get worldPosition: Will trigger the worldMatrix, local position update of itself and the worldMatrix update of all parent entities.
* Get worldRotationQuaternion: Will trigger the world rotation (in quaternion) update of itself and all parent entities.
* Get worldRotation: Will trigger the world rotation(in euler and quaternion) update of itself and world rotation(in quaternion) update of all parent entities.
* Get worldScale: Will trigger the scaling update of itself and all parent entities.
* In summary, any update of related variables will cause the dirty mark of one of the full process (worldMatrix or worldRotationQuaternion) to be false.
*/
private _updateWorldRotationFlag() {
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWeWq)) {
this._worldAssociatedChange(TransformModifyFlags.WmWeWq);
const nodeChildren = this._entity._children;
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
nodeChildren[i].transform?._updateWorldPositionAndRotationFlag(); // Rotation update of parent entity will trigger world position and rotation update of all child entity.
const parent = this._getParentTransform();
const parentWorldUniformScaling = parent ? parent._getWorldUniformScaling() : true;
let flags = parentWorldUniformScaling ? TransformModifyFlags.WmWeWq : TransformModifyFlags.WmWeWqWs;
if (!this._isContainDirtyFlags(flags)) {
this._worldAssociatedChange(flags);
flags = this._getWorldUniformScaling() ? TransformModifyFlags.WmWpWeWq : TransformModifyFlags.WmWpWeWqWs;
const children = this._entity._children;
for (let i = 0, n = children.length; i < n; i++) {
children[i].transform?._updateWorldPositionAndRotationFlag(flags); // Rotation update of parent entity will trigger world position, rotation and scale update of all child entity.
}
}
}
Expand All @@ -632,14 +648,17 @@
* Get worldPosition: Will trigger the worldMatrix, local position update of itself and the worldMatrix update of all parent entities.
* Get worldRotationQuaternion: Will trigger the world rotation (in quaternion) update of itself and all parent entities.
* Get worldRotation: Will trigger the world rotation(in euler and quaternion) update of itself and world rotation(in quaternion) update of all parent entities.
* Get worldScale: Will trigger the scaling update of itself and all parent entities.
* In summary, any update of related variables will cause the dirty mark of one of the full process (worldMatrix or worldRotationQuaternion) to be false.
*/
private _updateWorldPositionAndRotationFlag() {
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWpWeWq)) {
this._worldAssociatedChange(TransformModifyFlags.WmWpWeWq);
const nodeChildren = this._entity._children;
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
nodeChildren[i].transform?._updateWorldPositionAndRotationFlag();
* @param flags - Dirty flag
*/
private _updateWorldPositionAndRotationFlag(flags: TransformModifyFlags): void {
if (!this._isContainDirtyFlags(flags)) {
this._worldAssociatedChange(flags);
flags = this._getWorldUniformScaling() ? TransformModifyFlags.WmWpWeWq : TransformModifyFlags.WmWpWeWqWs;
const children = this._entity._children;
for (let i = 0, n = children.length; i < n; i++) {
children[i].transform?._updateWorldPositionAndRotationFlag(flags);
}
}
}
Expand All @@ -649,13 +668,15 @@
* Get worldPosition: Will trigger the worldMatrix, local position update of itself and the worldMatrix update of all parent entities.
* Get worldScale: Will trigger the scaling update of itself and all parent entities.
* In summary, any update of related variables will cause the dirty mark of one of the full process (worldMatrix) to be false.
*/
private _updateWorldScaleFlag() {
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWs)) {
this._worldAssociatedChange(TransformModifyFlags.WmWs);
const nodeChildren = this._entity._children;
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
nodeChildren[i].transform?._updateWorldPositionAndScaleFlag();
* @param flags - Dirty flag
*/
private _updateWorldScaleFlag(flags: TransformModifyFlags): void {
if (!this._isContainDirtyFlags(flags)) {
this._worldAssociatedChange(flags);
flags |= TransformModifyFlags.WorldPosition;
const children = this._entity._children;
for (let i = 0, n = children.length; i < n; i++) {
children[i].transform?._updateWorldPositionAndScaleFlag(flags);
}
}
}
Expand All @@ -665,26 +686,28 @@
* Get worldPosition: Will trigger the worldMatrix, local position update of itself and the worldMatrix update of all parent entities.
* Get worldScale: Will trigger the scaling update of itself and all parent entities.
* In summary, any update of related variables will cause the dirty mark of one of the full process (worldMatrix) to be false.
*/
private _updateWorldPositionAndScaleFlag(): void {
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWpWs)) {
this._worldAssociatedChange(TransformModifyFlags.WmWpWs);
const nodeChildren = this._entity._children;
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
nodeChildren[i].transform?._updateWorldPositionAndScaleFlag();
* @param flags - Dirty flag
*/
private _updateWorldPositionAndScaleFlag(flags: TransformModifyFlags): void {
if (!this._isContainDirtyFlags(flags)) {
this._worldAssociatedChange(flags);
const children = this._entity._children;
for (let i = 0, n = children.length; i < n; i++) {

Check warning on line 695 in packages/core/src/Transform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/Transform.ts#L693-L695

Added lines #L693 - L695 were not covered by tests
children[i].transform?._updateWorldPositionAndScaleFlag(flags);
}
}
}

/**
* Update all world transform property dirty flag, the principle is the same as above.
*/
private _updateAllWorldFlag(): void {
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWpWeWqWs)) {
this._worldAssociatedChange(TransformModifyFlags.WmWpWeWqWs);
const nodeChildren = this._entity._children;
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
nodeChildren[i].transform?._updateAllWorldFlag();
* @param flags - Dirty flag
*/
private _updateAllWorldFlag(flags: TransformModifyFlags): void {
if (!this._isContainDirtyFlags(flags)) {
this._worldAssociatedChange(flags);
const children = this._entity._children;
for (let i = 0, n = children.length; i < n; i++) {
children[i].transform?._updateAllWorldFlag(flags);
}
}
}
Expand Down Expand Up @@ -739,7 +762,7 @@

private _worldAssociatedChange(type: number): void {
this._dirtyFlag |= type;
this._updateFlagManager.dispatch(TransformModifyFlags.WorldMatrix);
this._updateFlagManager.dispatch(type);
}

private _rotateByQuat(rotateQuat: Quaternion, relativeToLocal: boolean): void {
Expand Down Expand Up @@ -828,8 +851,29 @@

@ignoreClone
private _onScaleChanged(): void {
const { x, y, z } = this._scale;
this._setDirtyFlagTrue(TransformModifyFlags.LocalMatrix);
this._updateWorldScaleFlag();
const localUniformScaling = x == y && y == z;
if (this._localUniformScaling !== localUniformScaling) {
this._localUniformScaling = localUniformScaling;
this._updateWorldScaleFlag(TransformModifyFlags.WmWsWus);
} else {
this._updateWorldScaleFlag(TransformModifyFlags.WmWs);
}
}

private _getWorldUniformScaling(): boolean {
if (this._isContainDirtyFlag(TransformModifyFlags.IsWorldUniformScaling)) {
const localUniformScaling = this._localUniformScaling;
if (localUniformScaling) {
const parent = this._getParentTransform();
this._worldUniformScaling = localUniformScaling && (parent ? parent._getWorldUniformScaling() : true);
} else {
this._worldUniformScaling = false;
}
this._setDirtyFlagFalse(TransformModifyFlags.IsWorldUniformScaling);
}
return this._worldUniformScaling;
}
}

Expand All @@ -846,16 +890,27 @@
LocalMatrix = 0x40,
WorldMatrix = 0x80,

/** This is an internal flag used to assist in determining the dispatch
* of world scaling dirty flags in the case of non-uniform scaling.
*/
IsWorldUniformScaling = 0x100,

/** WorldMatrix | WorldPosition */
WmWp = 0x84,
/** WorldMatrix | WorldEuler | WorldQuat */
WmWeWq = 0x98,
/** WorldMatrix | WorldEuler | WorldQuat | WorldScale*/
WmWeWqWs = 0xb8,
/** WorldMatrix | WorldPosition | WorldEuler | WorldQuat */
WmWpWeWq = 0x9c,
/** WorldMatrix | WorldScale */
WmWs = 0xa0,
/** WorldMatrix | WorldScale | WorldUniformScaling */
WmWsWus = 0x1a0,
/** WorldMatrix | WorldPosition | WorldScale */
WmWpWs = 0xa4,
/** WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale */
WmWpWeWqWs = 0xbc
WmWpWeWqWs = 0xbc,
/** WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale | WorldUniformScaling */
WmWpWeWqWsWus = 0x1bc
}
12 changes: 12 additions & 0 deletions tests/src/core/Transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ describe("Transform test", function () {
expect(transform.worldUp).to.deep.equal(new Vector3(0, 1, 0));
});

it("World Scale", () => {
const root = scene.createRootEntity();
root.transform.setScale(1, 2, 3);
const entity = root.createChild();
const transform = entity.transform;
transform.setScale(4, 5, 6);
transform.setRotation(0, 0, 0);
expect(transform.lossyWorldScale).to.deep.equal(new Vector3(4, 10, 18));
transform.setRotation(90, 0, 0);
expect(transform.lossyWorldScale).to.deep.equal(new Vector3(4, 15, 12));
});

it("Parent Dirty", () => {
const root1 = scene.createRootEntity();
const root2 = scene.createRootEntity();
Expand Down