Skip to content

Commit

Permalink
fixed #17606: node.setWorldScale(0, 0, 0) causes NaN issue in scale a…
Browse files Browse the repository at this point in the history
…nd worldMatrix properties (#17607)
  • Loading branch information
dumganhar committed Sep 13, 2024
1 parent 3e78462 commit 11f3bef
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 18 deletions.
51 changes: 41 additions & 10 deletions cocos/scene-graph/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2092,11 +2092,11 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable {
child._mat.m14 = child._pos.z;
}
if (dirtyBits & TransformBit.RS) {
Mat4.fromRTS(child._mat, child._lrot, child._lpos, child._lscale);
Mat4.fromSRT(child._mat, child._lrot, child._lpos, child._lscale);
Mat4.multiply(child._mat, cur._mat, child._mat);

const rotTmp = dirtyBits & TransformBit.ROTATION ? child._rot : null;
Mat4.toRTS(child._mat, rotTmp, null, child._scale);
Mat4.toSRT(child._mat, rotTmp, null, child._scale);
}
} else {
if (dirtyBits & TransformBit.POSITION) {
Expand Down Expand Up @@ -2565,24 +2565,55 @@ export class Node extends CCObject implements ISchedulable, CustomSerializable {

Vec3.copy(worldScale, v3_a);
}

let rotationFlag = TransformBit.NONE;
if (parent) {
v3_a.x = worldScale.x / Vec3.set(v3_b, this._mat.m00, this._mat.m01, this._mat.m02).length();
v3_a.y = worldScale.y / Vec3.set(v3_b, this._mat.m04, this._mat.m05, this._mat.m06).length();
v3_a.z = worldScale.z / Vec3.set(v3_b, this._mat.m08, this._mat.m09, this._mat.m10).length();
const xScale = Vec3.set(v3_b, this._mat.m00, this._mat.m01, this._mat.m02).length();
const yScale = Vec3.set(v3_b, this._mat.m04, this._mat.m05, this._mat.m06).length();
const zScale = Vec3.set(v3_b, this._mat.m08, this._mat.m09, this._mat.m10).length();
if (xScale === 0) {
v3_a.x = worldScale.x;
this._mat.m00 = 1;
rotationFlag = TransformBit.ROTATION;
} else {
v3_a.x = worldScale.x / xScale;
}

if (yScale === 0) {
v3_a.y = worldScale.y;
this._mat.m05 = 1;
rotationFlag = TransformBit.ROTATION;
} else {
v3_a.y = worldScale.y / yScale;
}

if (zScale === 0) {
v3_a.z = worldScale.z;
this._mat.m10 = 1;
rotationFlag = TransformBit.ROTATION;
} else {
v3_a.z = worldScale.z / zScale;
}

Mat4.scale(m4_1, this._mat, v3_a);
Mat4.multiply(m4_2, Mat4.invert(m4_2, parent._mat), m4_1);
Mat3.fromQuat(m3_1, Quat.conjugate(qt_1, this._lrot));
Mat3.multiplyMat4(m3_1, m3_1, m4_2);
this._lscale.x = Vec3.set(v3_a, m3_1.m00, m3_1.m01, m3_1.m02).length();
this._lscale.y = Vec3.set(v3_a, m3_1.m03, m3_1.m04, m3_1.m05).length();
this._lscale.z = Vec3.set(v3_a, m3_1.m06, m3_1.m07, m3_1.m08).length();

const localScale = this._lscale;
localScale.x = Vec3.set(v3_a, m3_1.m00, m3_1.m01, m3_1.m02).length();
localScale.y = Vec3.set(v3_a, m3_1.m03, m3_1.m04, m3_1.m05).length();
localScale.z = Vec3.set(v3_a, m3_1.m06, m3_1.m07, m3_1.m08).length();
if (localScale.x === 0 || localScale.y === 0 || localScale.z === 0) {
rotationFlag = TransformBit.ROTATION;
}
} else {
Vec3.copy(this._lscale, worldScale);
}

this.invalidateChildren(TransformBit.SCALE);
this.invalidateChildren(TransformBit.SCALE | rotationFlag);
if (this._eventMask & TRANSFORM_ON) {
this.emit(NodeEventType.TRANSFORM_CHANGED, TransformBit.SCALE);
this.emit(NodeEventType.TRANSFORM_CHANGED, TransformBit.SCALE | rotationFlag);
}
}

Expand Down
32 changes: 29 additions & 3 deletions native/cocos/core/scene-graph/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -611,14 +611,36 @@ void Node::setWorldScale(float x, float y, float z) {
return;
}

TransformBit rotationFlag = TransformBit::NONE;
if (_parent != nullptr) {
updateWorldTransform(); // ensure reentryability
Vec3 oldWorldScale = _worldScale;
_worldScale.set(x, y, z);
Mat3 localRS;
Mat3 localRotInv;
Mat4 worldMatrixTmp = _worldMatrix;
Vec3 rescaleFactor = _worldScale / oldWorldScale;
Vec3 rescaleFactor;

if (oldWorldScale.x == 0) {
oldWorldScale.x = 1;
worldMatrixTmp.m[0] = 1.F;
rotationFlag = TransformBit::ROTATION;
}

if (oldWorldScale.y == 0) {
oldWorldScale.y = 1;
worldMatrixTmp.m[5] = 1.F;
rotationFlag = TransformBit::ROTATION;
}

if (oldWorldScale.z == 0) {
oldWorldScale.z = 1;
worldMatrixTmp.m[10] = 1.F;
rotationFlag = TransformBit::ROTATION;
}

rescaleFactor = _worldScale / oldWorldScale;

// apply new world scale to temp world matrix
worldMatrixTmp.scale(rescaleFactor); // need opt
// get temp local matrix
Expand All @@ -633,16 +655,20 @@ void Node::setWorldScale(float x, float y, float z) {
_localScale.x = Vec3{localRS.m[0], localRS.m[1], localRS.m[2]}.length();
_localScale.y = Vec3{localRS.m[3], localRS.m[4], localRS.m[5]}.length();
_localScale.z = Vec3{localRS.m[6], localRS.m[7], localRS.m[8]}.length();

if (_localScale.x == 0 || _localScale.y == 0 || _localScale.z == 0) {
rotationFlag = TransformBit::ROTATION;
}
} else {
_worldScale.set(x, y, z);
_localScale = _worldScale;
}

notifyLocalScaleUpdated();

invalidateChildren(TransformBit::SCALE);
invalidateChildren(TransformBit::SCALE | rotationFlag);
if (_eventMask & TRANSFORM_ON) {
emit<TransformChanged>(TransformBit::SCALE);
emit<TransformChanged>(TransformBit::SCALE | rotationFlag);
}
}

Expand Down
138 changes: 133 additions & 5 deletions native/tests/unit-test/src/node_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/*
#include "core/Director.h"

//#include "core/Director.h"
#include "core/Root.h"
#include "core/platform/event-manager/Events.h"
#include "core/scene-graph/SceneGraphModuleHeader.h"
#include "core/scene-graph/Node.h"
//#include "core/platform/event-manager/Events.h"
//#include "core/scene-graph/SceneGraphModuleHeader.h"
#include "gtest/gtest.h"
#include "renderer/GFXDeviceManager.h"
#include "renderer/gfx-base/GFXDef.h"
Expand All @@ -38,6 +39,8 @@ using namespace cc::gfx;

namespace {

/*
class MyCallbackTarget {
public:
};
Expand Down Expand Up @@ -97,5 +100,130 @@ TEST(NodeTest, activeInHierarchyChanged) {
// xwx FIXME: gfx-validator Assert
destroyCocos();
}
} // namespace
*/

TEST(NodeTest, setWorldScale000_and_rotation) {
cc::IntrusivePtr<cc::Node> parent(new Node());

parent->setScale(2, 2, 2);

cc::IntrusivePtr<cc::Node> son(new Node());
son->setRotationFromEuler(10, 0, 0);
son->setParent(parent);
son->updateWorldTransform();
EXPECT_TRUE(son->getScale() == Vec3::ONE);
EXPECT_TRUE(son->getWorldScale().approxEquals(Vec3(2.f, 2.f, 2.f)));
EXPECT_TRUE(son->getWorldMatrix().approxEquals(Mat4(
2, 0, 0, 0,
0, 1.969615506024416, 0.34729635533386066, 0,
0, -0.34729635533386066, 1.969615506024416, 0,
0, 0, 0, 1
)));

son->setWorldScale(0, 0, 0);
EXPECT_TRUE(son->getScale() == Vec3::ZERO);
EXPECT_TRUE(son->getWorldScale() == Vec3::ZERO);
EXPECT_TRUE(son->getWorldMatrix().approxEquals(Mat4(
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 1
)));

son->setWorldScale(2, 2, 2);
EXPECT_TRUE(son->getScale().approxEquals(Vec3::ONE));
EXPECT_TRUE(son->getWorldScale().approxEquals(Vec3(2.f, 2.f, 2.f)));
EXPECT_TRUE(son->getWorldMatrix().approxEquals(Mat4(
2, 0, 0, 0,
0, 1.969615506024416, 0.34729635533386066, 0,
0, -0.34729635533386066, 1.969615506024416, 0,
0, 0, 0, 1
)));

son->setWorldScale(1, 1, 1);
EXPECT_TRUE(son->getScale().approxEquals(Vec3(0.5f, 0.5f, 0.5f)));
EXPECT_TRUE(son->getWorldScale().approxEquals(Vec3::ONE));
EXPECT_TRUE(son->getWorldMatrix().approxEquals(Mat4(
1, 0, 0, 0,
0, 0.984807753012208, 0.17364817766693033, 0,
0, -0.17364817766693033, 0.984807753012208, 0,
0, 0, 0, 1
)));
}

TEST(NodeTest, setWorldScale0yz_and_rotation) {
cc::IntrusivePtr<cc::Node> parent(new Node());

parent->setScale(2, 2, 2);

cc::IntrusivePtr<cc::Node> son(new Node());
son->setRotationFromEuler(10, 10, 10);
son->setParent(parent);
son->updateWorldTransform();
EXPECT_TRUE(son->getScale().approxEquals(Vec3::ONE));
EXPECT_TRUE(son->getWorldScale().approxEquals(Vec3(2, 2, 2)));
EXPECT_TRUE(son->getWorldMatrix().approxEquals(Mat4(
1.9396926207859084, 0.3472963553338607, -0.3420201433256687, 0,
-0.2765167096193736, 1.9396926207859084, 0.40141131793955337, 0,
0.40141131793955337, -0.3420201433256687, 1.9292203542855129, 0,
0, 0, 0, 1
)));

son->setWorldScale(0, 2, 2);
EXPECT_TRUE(son->getScale().approxEquals(Vec3(0, 1, 1)));
EXPECT_TRUE(son->getWorldScale().approxEquals(Vec3(0, 2, 2)));
EXPECT_TRUE(son->getWorldMatrix().approxEquals(Mat4(
0, 0, 0, 0,
-0.2765167096193736, 1.9396926207859084, 0.40141131793955337, 0,
0.40141131793955337, -0.3420201433256687, 1.9292203542855129, 0,
0, 0, 0, 1
)));

EXPECT_TRUE(son->getRotation().approxEquals(Quaternion(0.09406091491321403, 0.09406091491321403, 0.07892647901187543, 0.9879654343559627)));
EXPECT_TRUE(son->getWorldRotation().approxEquals(Quaternion(0, 0, 0, 1))); // Could not decompose rotation in Mat4.toSRT since there is a axis is zero, so the rotation will be reset to unit quaternion.

son->setRotationFromEuler(20, 20, 20);
EXPECT_TRUE(son->getRotation().approxEquals(Quaternion(0.1981076317236749, 0.1981076317236749, 0.1387164571097902, 0.9498760324550678)));
EXPECT_TRUE(son->getWorldRotation().approxEquals(Quaternion(0, 0, 0, 1)));

son->setRotationFromEuler(10, 10, 10);
EXPECT_TRUE(son->getRotation().approxEquals(Quaternion(0.09406091491321403, 0.09406091491321403, 0.07892647901187543, 0.9879654343559627)));
EXPECT_TRUE(son->getWorldRotation().approxEquals(Quaternion(0, 0, 0, 1)));

son->setWorldScale(1, 1, 1);
EXPECT_TRUE(son->getScale().approxEquals(Vec3(0.5, 0.5, 0.5)));
EXPECT_TRUE(son->getWorldScale().approxEquals(Vec3(1, 1, 1)));
EXPECT_TRUE(son->getWorldMatrix().approxEquals(Mat4(
0.9698463103929542, 0.17364817766693036, -0.17101007166283436, 0,
-0.1382583548096868, 0.9698463103929542, 0.20070565896977668, 0,
0.20070565896977668, -0.17101007166283436, 0.9646101771427564, 0,
0, 0, 0, 1
)));

EXPECT_TRUE(son->getRotation().approxEquals(Quaternion(0.09406091491321403, 0.09406091491321403, 0.07892647901187543, 0.9879654343559627)));
EXPECT_TRUE(son->getWorldRotation().approxEquals(Quaternion(0.09406091491321403, 0.09406091491321403, 0.07892647901187543, 0.9879654343559627)));

son->setWorldScale(2, 0, 0);
EXPECT_TRUE(son->getScale().approxEquals(Vec3(1, 0, 0)));
EXPECT_TRUE(son->getWorldScale().approxEquals(Vec3(2, 0, 0)));
EXPECT_TRUE(son->getWorldMatrix().approxEquals(Mat4(
1.9396926207859084, 0.3472963553338607, -0.3420201433256687, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 1
)));

son->setWorldScale(2, 2, 2);
EXPECT_TRUE(son->getScale().approxEquals(Vec3(1, 1, 1)));
EXPECT_TRUE(son->getWorldScale().approxEquals(Vec3(2, 2, 2)));
EXPECT_TRUE(son->getWorldMatrix().approxEquals(Mat4(
1.9396926207859084, 0.3472963553338607, -0.3420201433256687, 0,
-0.2765167096193736, 1.9396926207859084, 0.40141131793955337, 0,
0.40141131793955337, -0.3420201433256687, 1.9292203542855129, 0,
0, 0, 0, 1
)));
}

} // namespace

Loading

0 comments on commit 11f3bef

Please sign in to comment.