Skip to content

Commit f69a3cf

Browse files
Radient: normalize transforms on update
1 parent a3a0b75 commit f69a3cf

4 files changed

Lines changed: 124 additions & 22 deletions

File tree

Radient/include/Math/RadientMath.hpp

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,34 @@ inline RadientQuaternion ToRadientQuaternion(const QuaternionF& Value)
182182
return RadientQuaternion{Q.q.x, Q.q.y, Q.q.z, Q.q.w};
183183
}
184184

185+
inline RadientTransform NormalizeTransform(const RadientTransform& Transform)
186+
{
187+
RadientQuaternion Rotation = Transform.Rotation;
188+
189+
const float LengthSq = Rotation.x * Rotation.x + Rotation.y * Rotation.y + Rotation.z * Rotation.z + Rotation.w * Rotation.w;
190+
if (LengthSq > 0.f)
191+
{
192+
const float InvLength = 1.f / std::sqrt(LengthSq);
193+
Rotation.x *= InvLength;
194+
Rotation.y *= InvLength;
195+
Rotation.z *= InvLength;
196+
Rotation.w *= InvLength;
197+
}
198+
else
199+
{
200+
Rotation = {};
201+
}
202+
203+
return {
204+
Transform.Position,
205+
Rotation,
206+
Transform.Scale,
207+
};
208+
}
209+
185210
inline RadientMatrix4x4 TransformToMatrix(const RadientTransform& Transform)
186211
{
212+
// Rotation is expected to be normalized when the transform is stored.
187213
const float sx = Transform.Scale.x;
188214
const float sy = Transform.Scale.y;
189215
const float sz = Transform.Scale.z;
@@ -192,12 +218,10 @@ inline RadientMatrix4x4 TransformToMatrix(const RadientTransform& Transform)
192218
const float ty = Transform.Position.y;
193219
const float tz = Transform.Position.z;
194220

195-
const QuaternionF Q = ToQuaternion(Transform.Rotation);
196-
197-
const float x = Q.q.x;
198-
const float y = Q.q.y;
199-
const float z = Q.q.z;
200-
const float w = Q.q.w;
221+
const float x = Transform.Rotation.x;
222+
const float y = Transform.Rotation.y;
223+
const float z = Transform.Rotation.z;
224+
const float w = Transform.Rotation.w;
201225

202226
const float sx2 = sx + sx;
203227
const float sy2 = sy + sy;

Radient/src/Scene/RadientSceneState.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -412,13 +412,14 @@ RADIENT_STATUS RadientSceneState::CreateEntity(const RadientEntityDesc& Desc, Ra
412412
m_NextEntityID == std::numeric_limits<RadientEntityID>::max())
413413
return RADIENT_STATUS_INVALID_OPERATION;
414414

415-
Entity = m_NextEntityID++;
416-
const entt::entity E = m_Registry.create();
415+
Entity = m_NextEntityID++;
416+
const entt::entity E = m_Registry.create();
417+
const RadientTransform LocalTransform = RadientMath::NormalizeTransform(Desc.Transform);
417418

418419
m_Registry.emplace<EntityComponent>(E, EntityComponent{Entity, Desc.Name != nullptr ? Desc.Name : ""});
419420
m_Registry.emplace<EntityStateComponent>(E, EntityStateComponent{Desc.Flags});
420421
m_Registry.emplace<HierarchyComponent>(E);
421-
m_Registry.emplace<LocalTransformComponent>(E, LocalTransformComponent{Desc.Transform});
422+
m_Registry.emplace<LocalTransformComponent>(E, LocalTransformComponent{LocalTransform});
422423
m_Registry.emplace<WorldTransformComponent>(E);
423424
m_Registry.emplace<EffectiveVisibilityComponent>(E);
424425
m_Registry.emplace<RenderableMeshStateComponent>(E);
@@ -557,11 +558,12 @@ RADIENT_STATUS RadientSceneState::SetLocalTransform(RadientEntityID Entity, cons
557558
if (E == entt::null)
558559
return RADIENT_STATUS_NOT_FOUND;
559560

560-
LocalTransformComponent& LocalTransform = m_Registry.get<LocalTransformComponent>(E);
561-
if (LocalTransform.Transform == Transform)
561+
const RadientTransform NormalizedTransform = RadientMath::NormalizeTransform(Transform);
562+
LocalTransformComponent& LocalTransform = m_Registry.get<LocalTransformComponent>(E);
563+
if (LocalTransform.Transform == NormalizedTransform)
562564
return RADIENT_STATUS_NO_CHANGE;
563565

564-
LocalTransform.Transform = Transform;
566+
LocalTransform.Transform = NormalizedTransform;
565567
MarkDirty(E, DIRTY_FLAG_TRANSFORM);
566568
Touch(CHANGE_FLAG_TRANSFORMS);
567569
return RADIENT_STATUS_OK;

Tests/RadientTest/src/RadientMathTest.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
#include "Math/RadientMath.hpp"
3131

32+
#include <cmath>
33+
3234
using namespace Diligent;
3335
using namespace Diligent::Testing;
3436

@@ -133,6 +135,55 @@ TEST(RadientMathTest, ToQuaternion)
133135
EXPECT_EQ(NormalizedQuat.q.w, 1.f);
134136
}
135137

138+
TEST(RadientMathTest, NormalizeTransformNormalizesRotation)
139+
{
140+
RadientTransform Transform;
141+
Transform.Position = {1.f, 2.f, 3.f};
142+
Transform.Rotation = {1.f, 2.f, 3.f, 4.f};
143+
Transform.Scale = {5.f, 6.f, 7.f};
144+
145+
const RadientTransform NormalizedTransform = RadientMath::NormalizeTransform(Transform);
146+
const float InvLength = 1.f / std::sqrt(30.f);
147+
148+
EXPECT_EQ(NormalizedTransform.Position, Transform.Position);
149+
EXPECT_NEAR(NormalizedTransform.Rotation.x, Transform.Rotation.x * InvLength, EPSILON);
150+
EXPECT_NEAR(NormalizedTransform.Rotation.y, Transform.Rotation.y * InvLength, EPSILON);
151+
EXPECT_NEAR(NormalizedTransform.Rotation.z, Transform.Rotation.z * InvLength, EPSILON);
152+
EXPECT_NEAR(NormalizedTransform.Rotation.w, Transform.Rotation.w * InvLength, EPSILON);
153+
EXPECT_EQ(NormalizedTransform.Scale, Transform.Scale);
154+
}
155+
156+
TEST(RadientMathTest, NormalizeTransformKeepsUnitRotation)
157+
{
158+
RadientTransform Transform;
159+
Transform.Position = {-1.f, -2.f, -3.f};
160+
Transform.Rotation = {0.f, 0.f, 0.6f, 0.8f};
161+
Transform.Scale = {0.5f, 1.5f, 2.5f};
162+
163+
const RadientTransform NormalizedTransform = RadientMath::NormalizeTransform(Transform);
164+
165+
EXPECT_EQ(NormalizedTransform.Position, Transform.Position);
166+
EXPECT_NEAR(NormalizedTransform.Rotation.x, Transform.Rotation.x, EPSILON);
167+
EXPECT_NEAR(NormalizedTransform.Rotation.y, Transform.Rotation.y, EPSILON);
168+
EXPECT_NEAR(NormalizedTransform.Rotation.z, Transform.Rotation.z, EPSILON);
169+
EXPECT_NEAR(NormalizedTransform.Rotation.w, Transform.Rotation.w, EPSILON);
170+
EXPECT_EQ(NormalizedTransform.Scale, Transform.Scale);
171+
}
172+
173+
TEST(RadientMathTest, NormalizeTransformTreatsZeroRotationAsIdentity)
174+
{
175+
RadientTransform Transform;
176+
Transform.Position = {8.f, 9.f, 10.f};
177+
Transform.Rotation = {0.f, 0.f, 0.f, 0.f};
178+
Transform.Scale = {2.f, 3.f, 4.f};
179+
180+
const RadientTransform NormalizedTransform = RadientMath::NormalizeTransform(Transform);
181+
182+
EXPECT_EQ(NormalizedTransform.Position, Transform.Position);
183+
EXPECT_EQ(NormalizedTransform.Rotation, RadientQuaternion{});
184+
EXPECT_EQ(NormalizedTransform.Scale, Transform.Scale);
185+
}
186+
136187
TEST(RadientMathTest, RoundTripsMatrixType)
137188
{
138189
// Matrix layout conversion should be lossless in both directions.

Tests/RadientTest/src/RadientSceneStateTest.cpp

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ void ExpectTransformEq(const RadientTransform& Transform, const RadientTransform
6262
EXPECT_EQ(Transform.Scale.z, Reference.Scale.z);
6363
}
6464

65+
void ExpectTransformNear(const RadientTransform& Transform, const RadientTransform& Reference)
66+
{
67+
EXPECT_NEAR(Transform.Position.x, Reference.Position.x, EPSILON);
68+
EXPECT_NEAR(Transform.Position.y, Reference.Position.y, EPSILON);
69+
EXPECT_NEAR(Transform.Position.z, Reference.Position.z, EPSILON);
70+
71+
EXPECT_NEAR(Transform.Rotation.x, Reference.Rotation.x, EPSILON);
72+
EXPECT_NEAR(Transform.Rotation.y, Reference.Rotation.y, EPSILON);
73+
EXPECT_NEAR(Transform.Rotation.z, Reference.Rotation.z, EPSILON);
74+
EXPECT_NEAR(Transform.Rotation.w, Reference.Rotation.w, EPSILON);
75+
76+
EXPECT_NEAR(Transform.Scale.x, Reference.Scale.x, EPSILON);
77+
EXPECT_NEAR(Transform.Scale.y, Reference.Scale.y, EPSILON);
78+
EXPECT_NEAR(Transform.Scale.z, Reference.Scale.z, EPSILON);
79+
}
80+
6581
void ExpectMatrixNear(const RadientMatrix4x4& Matrix, const RadientMatrix4x4& Reference)
6682
{
6783
for (Uint32 i = 0; i < 16; ++i)
@@ -1143,41 +1159,50 @@ TEST(RadientSceneStateTest, GetLocalTransform)
11431159
EXPECT_EQ(State.GetLocalTransform(Entity, Transform), RADIENT_STATUS_OK);
11441160
ExpectTransformEq(Transform, RadientTransform{});
11451161

1146-
// Creation with an initial transform should preserve the exact TRS values.
1162+
// Creation with an initial transform should normalize rotation once before
1163+
// storing local TRS values.
11471164
RadientTransform InitialTransform;
11481165
InitialTransform.Position = {1.f, 2.f, 3.f};
1149-
InitialTransform.Rotation.z = 0.70710678f;
1150-
InitialTransform.Rotation.w = 0.70710678f;
1166+
InitialTransform.Rotation.z = 2.f;
1167+
InitialTransform.Rotation.w = 2.f;
11511168
InitialTransform.Scale = {2.f, 3.f, 4.f};
11521169

1170+
RadientTransform ExpectedInitialTransform = InitialTransform;
1171+
ExpectedInitialTransform.Rotation.z = 0.70710678f;
1172+
ExpectedInitialTransform.Rotation.w = 0.70710678f;
1173+
11531174
RadientEntityDesc EntityDesc;
11541175
EntityDesc.Transform = InitialTransform;
11551176

11561177
RadientEntityID TransformedEntity = InvalidRadientEntityID;
11571178
EXPECT_EQ(State.CreateEntity(EntityDesc, TransformedEntity), RADIENT_STATUS_OK);
11581179
EXPECT_EQ(State.GetLocalTransform(TransformedEntity, Transform), RADIENT_STATUS_OK);
1159-
ExpectTransformEq(Transform, InitialTransform);
1180+
ExpectTransformNear(Transform, ExpectedInitialTransform);
11601181

11611182
RadientTransform UpdatedTransform;
11621183
UpdatedTransform.Position = {4.f, 5.f, 6.f};
1163-
UpdatedTransform.Rotation.x = 0.70710678f;
1164-
UpdatedTransform.Rotation.w = 0.70710678f;
1184+
UpdatedTransform.Rotation.x = 2.f;
1185+
UpdatedTransform.Rotation.w = 2.f;
11651186
UpdatedTransform.Scale = {5.f, 6.f, 7.f};
11661187

1188+
RadientTransform ExpectedUpdatedTransform = UpdatedTransform;
1189+
ExpectedUpdatedTransform.Rotation.x = 0.70710678f;
1190+
ExpectedUpdatedTransform.Rotation.w = 0.70710678f;
1191+
11671192
EXPECT_EQ(State.SetLocalTransform(TransformedEntity, UpdatedTransform), RADIENT_STATUS_OK);
11681193
EXPECT_EQ(State.GetLocalTransform(TransformedEntity, Transform), RADIENT_STATUS_OK);
1169-
ExpectTransformEq(Transform, UpdatedTransform);
1194+
ExpectTransformNear(Transform, ExpectedUpdatedTransform);
11701195

1171-
// Setting the same transform again should report no change.
1196+
// Setting an equivalent non-unit transform again should report no change.
11721197
EXPECT_EQ(State.SetLocalTransform(TransformedEntity, UpdatedTransform), RADIENT_STATUS_NO_CHANGE);
11731198
EXPECT_EQ(State.GetLocalTransform(TransformedEntity, Transform), RADIENT_STATUS_OK);
1174-
ExpectTransformEq(Transform, UpdatedTransform);
1199+
ExpectTransformNear(Transform, ExpectedUpdatedTransform);
11751200

11761201
EXPECT_EQ(State.GetLocalTransform(Entity, Transform), RADIENT_STATUS_OK);
11771202
ExpectTransformEq(Transform, RadientTransform{});
11781203

11791204
EXPECT_EQ(State.DestroyEntity(TransformedEntity), RADIENT_STATUS_OK);
1180-
Transform = UpdatedTransform;
1205+
Transform = ExpectedUpdatedTransform;
11811206
// Destroyed entities should report not found and reset output transform.
11821207
EXPECT_EQ(State.GetLocalTransform(TransformedEntity, Transform), RADIENT_STATUS_NOT_FOUND);
11831208
ExpectTransformEq(Transform, RadientTransform{});

0 commit comments

Comments
 (0)