Skip to content

Commit b91d531

Browse files
Fix Transform world scale dirty error (galacean#2408)
* fix: transform world scale dirty error
1 parent f4cdbf7 commit b91d531

File tree

2 files changed

+117
-50
lines changed

2 files changed

+117
-50
lines changed

packages/core/src/Transform.ts

+105-50
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { MathUtil, Matrix, Matrix3x3, Quaternion, Vector3 } from "@galacean/engine-math";
22
import { BoolUpdateFlag } from "./BoolUpdateFlag";
3-
import { deepClone, ignoreClone } from "./clone/CloneManager";
43
import { Component } from "./Component";
54
import { Entity } from "./Entity";
65
import { UpdateFlagManager } from "./UpdateFlagManager";
6+
import { assignmentClone, deepClone, ignoreClone } from "./clone/CloneManager";
77

88
/**
99
* Used to implement transformation related functions.
@@ -27,12 +27,16 @@ export class Transform extends Component {
2727
private _rotationQuaternion: Quaternion = new Quaternion();
2828
@deepClone
2929
private _scale: Vector3 = new Vector3(1, 1, 1);
30+
@assignmentClone
31+
private _localUniformScaling: boolean = true;
3032
@deepClone
3133
private _worldPosition: Vector3 = new Vector3();
3234
@deepClone
3335
private _worldRotation: Vector3 = new Vector3();
3436
@deepClone
3537
private _worldRotationQuaternion: Quaternion = new Quaternion();
38+
@assignmentClone
39+
private _worldUniformScaling: boolean = true;
3640
@deepClone
3741
private _lossyWorldScale: Vector3 = new Vector3(1, 1, 1);
3842
@deepClone
@@ -225,12 +229,13 @@ export class Transform extends Component {
225229

226230
/**
227231
* Local lossy scaling.
228-
* @remarks The value obtained may not be correct under certain conditions(for example, the parent node has scaling,
229-
* and the child node has a rotation), the scaling will be tilted. Vector3 cannot be used to correctly represent the scaling. Must use Matrix3x3.
232+
* @remarks The value obtained may not be correct under certain conditions(for example, the parent node has non-uniform world scaling,
233+
* and the child node has a rotation), the scaling will be tilted.
230234
*/
231235
get lossyWorldScale(): Vector3 {
232236
if (this._isContainDirtyFlag(TransformModifyFlags.WorldScale)) {
233237
if (this._getParentTransform()) {
238+
// Vector3 cannot be used to correctly represent the scaling. Must use Matrix3x3
234239
const scaleMat = this._getScaleMatrix();
235240
const e = scaleMat.elements;
236241
this._lossyWorldScale.set(e[0], e[4], e[8]);
@@ -258,20 +263,26 @@ export class Transform extends Component {
258263
if (this._localMatrix !== value) {
259264
this._localMatrix.copyFrom(value);
260265
}
261-
266+
const { _position: position, _rotationQuaternion: rotationQuaternion, _scale: scale } = this;
262267
// @ts-ignore
263-
this._position._onValueChanged = this._rotationQuaternion._onValueChanged = this._scale._onValueChanged = null;
264-
this._localMatrix.decompose(this._position, this._rotationQuaternion, this._scale);
268+
position._onValueChanged = rotationQuaternion._onValueChanged = scale._onValueChanged = null;
269+
this._localMatrix.decompose(position, rotationQuaternion, scale);
265270
// @ts-ignore
266-
this._position._onValueChanged = this._onPositionChanged;
271+
position._onValueChanged = this._onPositionChanged;
267272
// @ts-ignore
268-
this._rotationQuaternion._onValueChanged = this._onRotationQuaternionChanged;
273+
rotationQuaternion._onValueChanged = this._onRotationQuaternionChanged;
269274
// @ts-ignore
270-
this._scale._onValueChanged = this._onScaleChanged;
275+
scale._onValueChanged = this._onScaleChanged;
271276

272277
this._setDirtyFlagTrue(TransformModifyFlags.LocalEuler);
273278
this._setDirtyFlagFalse(TransformModifyFlags.LocalMatrix | TransformModifyFlags.LocalQuat);
274-
this._updateAllWorldFlag();
279+
const localUniformScaling = scale.x === scale.y && scale.y === scale.z;
280+
if (this._localUniformScaling !== localUniformScaling) {
281+
this._localUniformScaling = localUniformScaling;
282+
this._updateAllWorldFlag(TransformModifyFlags.WmWpWeWqWsWus);
283+
} else {
284+
this._updateAllWorldFlag(TransformModifyFlags.WmWpWeWqWs);
285+
}
275286
}
276287

277288
/**
@@ -563,7 +574,7 @@ export class Transform extends Component {
563574
*/
564575
_parentChange(): void {
565576
this._isParentDirty = true;
566-
this._updateAllWorldFlag();
577+
this._updateAllWorldFlag(TransformModifyFlags.WmWpWeWqWsWus);
567578
}
568579

569580
/**
@@ -603,9 +614,9 @@ export class Transform extends Component {
603614
private _updateWorldPositionFlag(): void {
604615
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWp)) {
605616
this._worldAssociatedChange(TransformModifyFlags.WmWp);
606-
const nodeChildren = this._entity._children;
607-
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
608-
nodeChildren[i].transform?._updateWorldPositionFlag();
617+
const children = this._entity._children;
618+
for (let i = 0, n = children.length; i < n; i++) {
619+
children[i].transform?._updateWorldPositionFlag();
609620
}
610621
}
611622
}
@@ -615,14 +626,19 @@ export class Transform extends Component {
615626
* Get worldPosition: Will trigger the worldMatrix, local position update of itself and the worldMatrix update of all parent entities.
616627
* Get worldRotationQuaternion: Will trigger the world rotation (in quaternion) update of itself and all parent entities.
617628
* Get worldRotation: Will trigger the world rotation(in euler and quaternion) update of itself and world rotation(in quaternion) update of all parent entities.
629+
* Get worldScale: Will trigger the scaling update of itself and all parent entities.
618630
* In summary, any update of related variables will cause the dirty mark of one of the full process (worldMatrix or worldRotationQuaternion) to be false.
619631
*/
620632
private _updateWorldRotationFlag() {
621-
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWeWq)) {
622-
this._worldAssociatedChange(TransformModifyFlags.WmWeWq);
623-
const nodeChildren = this._entity._children;
624-
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
625-
nodeChildren[i].transform?._updateWorldPositionAndRotationFlag(); // Rotation update of parent entity will trigger world position and rotation update of all child entity.
633+
const parent = this._getParentTransform();
634+
const parentWorldUniformScaling = parent ? parent._getWorldUniformScaling() : true;
635+
let flags = parentWorldUniformScaling ? TransformModifyFlags.WmWeWq : TransformModifyFlags.WmWeWqWs;
636+
if (!this._isContainDirtyFlags(flags)) {
637+
this._worldAssociatedChange(flags);
638+
flags = this._getWorldUniformScaling() ? TransformModifyFlags.WmWpWeWq : TransformModifyFlags.WmWpWeWqWs;
639+
const children = this._entity._children;
640+
for (let i = 0, n = children.length; i < n; i++) {
641+
children[i].transform?._updateWorldPositionAndRotationFlag(flags); // Rotation update of parent entity will trigger world position, rotation and scale update of all child entity.
626642
}
627643
}
628644
}
@@ -632,14 +648,17 @@ export class Transform extends Component {
632648
* Get worldPosition: Will trigger the worldMatrix, local position update of itself and the worldMatrix update of all parent entities.
633649
* Get worldRotationQuaternion: Will trigger the world rotation (in quaternion) update of itself and all parent entities.
634650
* Get worldRotation: Will trigger the world rotation(in euler and quaternion) update of itself and world rotation(in quaternion) update of all parent entities.
651+
* Get worldScale: Will trigger the scaling update of itself and all parent entities.
635652
* In summary, any update of related variables will cause the dirty mark of one of the full process (worldMatrix or worldRotationQuaternion) to be false.
636-
*/
637-
private _updateWorldPositionAndRotationFlag() {
638-
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWpWeWq)) {
639-
this._worldAssociatedChange(TransformModifyFlags.WmWpWeWq);
640-
const nodeChildren = this._entity._children;
641-
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
642-
nodeChildren[i].transform?._updateWorldPositionAndRotationFlag();
653+
* @param flags - Dirty flag
654+
*/
655+
private _updateWorldPositionAndRotationFlag(flags: TransformModifyFlags): void {
656+
if (!this._isContainDirtyFlags(flags)) {
657+
this._worldAssociatedChange(flags);
658+
flags = this._getWorldUniformScaling() ? TransformModifyFlags.WmWpWeWq : TransformModifyFlags.WmWpWeWqWs;
659+
const children = this._entity._children;
660+
for (let i = 0, n = children.length; i < n; i++) {
661+
children[i].transform?._updateWorldPositionAndRotationFlag(flags);
643662
}
644663
}
645664
}
@@ -649,13 +668,15 @@ export class Transform extends Component {
649668
* Get worldPosition: Will trigger the worldMatrix, local position update of itself and the worldMatrix update of all parent entities.
650669
* Get worldScale: Will trigger the scaling update of itself and all parent entities.
651670
* In summary, any update of related variables will cause the dirty mark of one of the full process (worldMatrix) to be false.
652-
*/
653-
private _updateWorldScaleFlag() {
654-
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWs)) {
655-
this._worldAssociatedChange(TransformModifyFlags.WmWs);
656-
const nodeChildren = this._entity._children;
657-
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
658-
nodeChildren[i].transform?._updateWorldPositionAndScaleFlag();
671+
* @param flags - Dirty flag
672+
*/
673+
private _updateWorldScaleFlag(flags: TransformModifyFlags): void {
674+
if (!this._isContainDirtyFlags(flags)) {
675+
this._worldAssociatedChange(flags);
676+
flags |= TransformModifyFlags.WorldPosition;
677+
const children = this._entity._children;
678+
for (let i = 0, n = children.length; i < n; i++) {
679+
children[i].transform?._updateWorldPositionAndScaleFlag(flags);
659680
}
660681
}
661682
}
@@ -665,26 +686,28 @@ export class Transform extends Component {
665686
* Get worldPosition: Will trigger the worldMatrix, local position update of itself and the worldMatrix update of all parent entities.
666687
* Get worldScale: Will trigger the scaling update of itself and all parent entities.
667688
* In summary, any update of related variables will cause the dirty mark of one of the full process (worldMatrix) to be false.
668-
*/
669-
private _updateWorldPositionAndScaleFlag(): void {
670-
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWpWs)) {
671-
this._worldAssociatedChange(TransformModifyFlags.WmWpWs);
672-
const nodeChildren = this._entity._children;
673-
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
674-
nodeChildren[i].transform?._updateWorldPositionAndScaleFlag();
689+
* @param flags - Dirty flag
690+
*/
691+
private _updateWorldPositionAndScaleFlag(flags: TransformModifyFlags): void {
692+
if (!this._isContainDirtyFlags(flags)) {
693+
this._worldAssociatedChange(flags);
694+
const children = this._entity._children;
695+
for (let i = 0, n = children.length; i < n; i++) {
696+
children[i].transform?._updateWorldPositionAndScaleFlag(flags);
675697
}
676698
}
677699
}
678700

679701
/**
680702
* Update all world transform property dirty flag, the principle is the same as above.
681-
*/
682-
private _updateAllWorldFlag(): void {
683-
if (!this._isContainDirtyFlags(TransformModifyFlags.WmWpWeWqWs)) {
684-
this._worldAssociatedChange(TransformModifyFlags.WmWpWeWqWs);
685-
const nodeChildren = this._entity._children;
686-
for (let i: number = 0, n: number = nodeChildren.length; i < n; i++) {
687-
nodeChildren[i].transform?._updateAllWorldFlag();
703+
* @param flags - Dirty flag
704+
*/
705+
private _updateAllWorldFlag(flags: TransformModifyFlags): void {
706+
if (!this._isContainDirtyFlags(flags)) {
707+
this._worldAssociatedChange(flags);
708+
const children = this._entity._children;
709+
for (let i = 0, n = children.length; i < n; i++) {
710+
children[i].transform?._updateAllWorldFlag(flags);
688711
}
689712
}
690713
}
@@ -739,7 +762,7 @@ export class Transform extends Component {
739762

740763
private _worldAssociatedChange(type: number): void {
741764
this._dirtyFlag |= type;
742-
this._updateFlagManager.dispatch(TransformModifyFlags.WorldMatrix);
765+
this._updateFlagManager.dispatch(type);
743766
}
744767

745768
private _rotateByQuat(rotateQuat: Quaternion, relativeToLocal: boolean): void {
@@ -828,8 +851,29 @@ export class Transform extends Component {
828851

829852
@ignoreClone
830853
private _onScaleChanged(): void {
854+
const { x, y, z } = this._scale;
831855
this._setDirtyFlagTrue(TransformModifyFlags.LocalMatrix);
832-
this._updateWorldScaleFlag();
856+
const localUniformScaling = x == y && y == z;
857+
if (this._localUniformScaling !== localUniformScaling) {
858+
this._localUniformScaling = localUniformScaling;
859+
this._updateWorldScaleFlag(TransformModifyFlags.WmWsWus);
860+
} else {
861+
this._updateWorldScaleFlag(TransformModifyFlags.WmWs);
862+
}
863+
}
864+
865+
private _getWorldUniformScaling(): boolean {
866+
if (this._isContainDirtyFlag(TransformModifyFlags.IsWorldUniformScaling)) {
867+
const localUniformScaling = this._localUniformScaling;
868+
if (localUniformScaling) {
869+
const parent = this._getParentTransform();
870+
this._worldUniformScaling = localUniformScaling && (parent ? parent._getWorldUniformScaling() : true);
871+
} else {
872+
this._worldUniformScaling = false;
873+
}
874+
this._setDirtyFlagFalse(TransformModifyFlags.IsWorldUniformScaling);
875+
}
876+
return this._worldUniformScaling;
833877
}
834878
}
835879

@@ -846,16 +890,27 @@ export enum TransformModifyFlags {
846890
LocalMatrix = 0x40,
847891
WorldMatrix = 0x80,
848892

893+
/** This is an internal flag used to assist in determining the dispatch
894+
* of world scaling dirty flags in the case of non-uniform scaling.
895+
*/
896+
IsWorldUniformScaling = 0x100,
897+
849898
/** WorldMatrix | WorldPosition */
850899
WmWp = 0x84,
851900
/** WorldMatrix | WorldEuler | WorldQuat */
852901
WmWeWq = 0x98,
902+
/** WorldMatrix | WorldEuler | WorldQuat | WorldScale*/
903+
WmWeWqWs = 0xb8,
853904
/** WorldMatrix | WorldPosition | WorldEuler | WorldQuat */
854905
WmWpWeWq = 0x9c,
855906
/** WorldMatrix | WorldScale */
856907
WmWs = 0xa0,
908+
/** WorldMatrix | WorldScale | WorldUniformScaling */
909+
WmWsWus = 0x1a0,
857910
/** WorldMatrix | WorldPosition | WorldScale */
858911
WmWpWs = 0xa4,
859912
/** WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale */
860-
WmWpWeWqWs = 0xbc
913+
WmWpWeWqWs = 0xbc,
914+
/** WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale | WorldUniformScaling */
915+
WmWpWeWqWsWus = 0x1bc
861916
}

tests/src/core/Transform.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ describe("Transform test", function () {
2626
expect(transform.worldUp).to.deep.equal(new Vector3(0, 1, 0));
2727
});
2828

29+
it("World Scale", () => {
30+
const root = scene.createRootEntity();
31+
root.transform.setScale(1, 2, 3);
32+
const entity = root.createChild();
33+
const transform = entity.transform;
34+
transform.setScale(4, 5, 6);
35+
transform.setRotation(0, 0, 0);
36+
expect(transform.lossyWorldScale).to.deep.equal(new Vector3(4, 10, 18));
37+
transform.setRotation(90, 0, 0);
38+
expect(transform.lossyWorldScale).to.deep.equal(new Vector3(4, 15, 12));
39+
});
40+
2941
it("Parent Dirty", () => {
3042
const root1 = scene.createRootEntity();
3143
const root2 = scene.createRootEntity();

0 commit comments

Comments
 (0)