mirror of
https://github.com/galacean/engine.git
synced 2026-06-20 22:06:02 +08:00
Fix Transform world scale dirty error (#2408)
* fix: transform world scale dirty error
This commit is contained in:
@@ -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.
|
||||
@@ -27,12 +27,16 @@ export class Transform extends Component {
|
||||
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
|
||||
@@ -225,12 +229,13 @@ export class Transform extends Component {
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
@@ -258,20 +263,26 @@ export class Transform extends Component {
|
||||
if (this._localMatrix !== value) {
|
||||
this._localMatrix.copyFrom(value);
|
||||
}
|
||||
|
||||
const { _position: position, _rotationQuaternion: rotationQuaternion, _scale: scale } = this;
|
||||
// @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);
|
||||
// @ts-ignore
|
||||
this._position._onValueChanged = this._onPositionChanged;
|
||||
position._onValueChanged = this._onPositionChanged;
|
||||
// @ts-ignore
|
||||
this._rotationQuaternion._onValueChanged = this._onRotationQuaternionChanged;
|
||||
rotationQuaternion._onValueChanged = this._onRotationQuaternionChanged;
|
||||
// @ts-ignore
|
||||
this._scale._onValueChanged = this._onScaleChanged;
|
||||
scale._onValueChanged = this._onScaleChanged;
|
||||
|
||||
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);
|
||||
} else {
|
||||
this._updateAllWorldFlag(TransformModifyFlags.WmWpWeWqWs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -563,7 +574,7 @@ export class Transform extends Component {
|
||||
*/
|
||||
_parentChange(): void {
|
||||
this._isParentDirty = true;
|
||||
this._updateAllWorldFlag();
|
||||
this._updateAllWorldFlag(TransformModifyFlags.WmWpWeWqWsWus);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -603,9 +614,9 @@ export class Transform extends Component {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -615,14 +626,19 @@ export class Transform extends Component {
|
||||
* 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -632,14 +648,17 @@ export class Transform extends Component {
|
||||
* 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.
|
||||
* @param flags - Dirty flag
|
||||
*/
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,13 +668,15 @@ export class Transform extends Component {
|
||||
* 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.
|
||||
* @param flags - Dirty flag
|
||||
*/
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -665,26 +686,28 @@ export class Transform extends Component {
|
||||
* 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.
|
||||
* @param flags - Dirty flag
|
||||
*/
|
||||
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();
|
||||
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++) {
|
||||
children[i].transform?._updateWorldPositionAndScaleFlag(flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all world transform property dirty flag, the principle is the same as above.
|
||||
* @param flags - Dirty flag
|
||||
*/
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -739,7 +762,7 @@ export class Transform extends Component {
|
||||
|
||||
private _worldAssociatedChange(type: number): void {
|
||||
this._dirtyFlag |= type;
|
||||
this._updateFlagManager.dispatch(TransformModifyFlags.WorldMatrix);
|
||||
this._updateFlagManager.dispatch(type);
|
||||
}
|
||||
|
||||
private _rotateByQuat(rotateQuat: Quaternion, relativeToLocal: boolean): void {
|
||||
@@ -828,8 +851,29 @@ export class Transform extends Component {
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -846,16 +890,27 @@ export enum TransformModifyFlags {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user