Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmake/FetchDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ set(PROFILER_IN_DEBUG_AND_RELEASE OFF CACHE BOOL "" FORCE)
set(ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
FetchContent_Declare(JoltPhysics
GIT_REPOSITORY https://github.com/jrouwe/JoltPhysics
GIT_TAG 0ec24eb93ad8ebe01f7f095b544af8e122713123 # No release available yet with VS 2026 fixes yet
GIT_SHALLOW TRUE
GIT_TAG db654de2a6098fd1ad78cb9a3e70f6a8a61c00b5 # No release available yet with VS 2026 fixes yet
GIT_SHALLOW FALSE
SOURCE_SUBDIR "Build"
)

Expand Down
19 changes: 19 additions & 0 deletions sample/source/scenes/GraphicsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,25 @@ void GraphicsTest::Load(ecs::Ecs world, ModuleProvider modules)
.tag = "Main Camera"
});

// Grandchildren test
auto emptyChild = world.Emplace<Entity>(
{
.position = Vector3::Zero(),
.rotation = Quaternion::Identity(),
.scale = Vector3::One(),
.parent = cameraHandle,
.tag = "Camera child"
});

world.Emplace<Entity>(
{
.position = Vector3::Zero(),
.rotation = Quaternion::Identity(),
.scale = Vector3::One(),
.parent = emptyChild,
.tag = "Camera grand child"
});

auto& camera = world.Emplace<SceneNavigationCamera>(cameraHandle);
world.Emplace<FrameLogic>(cameraHandle, InvokeFreeComponent<SceneNavigationCamera>{});
ncGraphics->SetCamera(&camera);
Expand Down
15 changes: 9 additions & 6 deletions source/ncengine/ecs/NcEcsImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,26 +99,29 @@ void EcsModule::UpdateWorldSpaceMatrices()
stack.emplace_back(&transform, hierarchy.children);
while (!stack.empty())
{
auto& children = stack.back().children;
if (children.empty())
if (stack.back().children.empty())
{
stack.pop_back();
continue;
}

if (!children.front().IsStatic())
if (!stack.back().children.front().IsStatic())
{
auto& child = world.Get<Transform>(children.front());
auto& child = world.Get<Transform>(stack.back().children.front());
dirty = dirty || child.IsDirty();
if (dirty)
child.UpdateWorldMatrix(stack.back().transform->TransformationMatrix());

auto& childHierarchy = world.Get<Hierarchy>(children.front());
auto& childHierarchy = world.Get<Hierarchy>(stack.back().children.front());
if (!childHierarchy.children.empty())
{
auto currentIndex = stack.size() - 1; // Save index before push
stack.emplace_back(&child, childHierarchy.children);
stack[currentIndex].children = stack[currentIndex].children.subspan(1); // Advance using index
}
}

children = children.subspan(1);
stack.back().children = stack.back().children.subspan(1);
}
}
#else // debug update
Expand Down
69 changes: 65 additions & 4 deletions test/ncengine/ecs/Transform_unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,58 @@ void EcsModule::RunFrameLogic()
void EcsModule::UpdateWorldSpaceMatrices()
{
auto world = Ecs{*m_registry};

struct ParentInfo
{
Transform* transform;
std::span<Entity> children;
};

auto stack = std::vector<ParentInfo>{};
for (auto entity : world.GetAll<Entity>())
{
if (entity.IsStatic())
continue;

auto& hierarchy = world.Get<Hierarchy>(entity);
if (hierarchy.parent.Valid()) // process root nodes first
continue;

auto& transform = world.Get<Transform>(entity);
if (!hierarchy.parent.Valid())
auto dirty = transform.IsDirty();
if (dirty)
transform.UpdateWorldMatrix();
else

if (hierarchy.children.empty())
continue;

stack.emplace_back(&transform, hierarchy.children);
while (!stack.empty())
{
auto& parentTransform = world.Get<Transform>(hierarchy.parent);
transform.UpdateWorldMatrix(parentTransform.TransformationMatrix());
if (stack.back().children.empty())
{
stack.pop_back();
continue;
}

if (!stack.back().children.front().IsStatic())
{
auto& child = world.Get<Transform>(stack.back().children.front());
dirty = dirty || child.IsDirty();
if (dirty)
child.UpdateWorldMatrix(stack.back().transform->TransformationMatrix());

auto& childHierarchy = world.Get<Hierarchy>(stack.back().children.front());
if (!childHierarchy.children.empty())
{
auto currentIndex = stack.size() - 1; // Save index before push
stack.emplace_back(&child, childHierarchy.children);
stack[currentIndex].children = stack[currentIndex].children.subspan(1); // Advance using index
continue;
}
}

stack.back().children = stack.back().children.subspan(1);
}
}
}
Expand Down Expand Up @@ -597,6 +639,25 @@ TEST_F(Transform_unit_tests, RotateAxisAngleOverload_CalledOnParent_OnlyWorldRot
EXPECT_EQ(tChild.LocalScale(), Vector3::One());
}

TEST_F(Transform_unit_tests, RunFrameLogic_HasGrandchild_ChildrenAndGrandChildrenHaveDefaultLocals)
{
auto parent = world.Emplace<Entity>(EntityInfo{.position = testPos1, .scale = testScale1});
auto child = world.Emplace<Entity>(EntityInfo{.parent = parent});
auto grandChild = world.Emplace<Entity>(EntityInfo{.parent = child});
auto& tChild = world.Get<Transform>(child);
auto& tGrandChild = world.Get<Transform>(grandChild);

ecsModule.RunFrameLogic();

EXPECT_EQ(tChild.LocalPosition(), Vector3::Zero());
EXPECT_EQ(tChild.LocalRotation(), Quaternion::Identity());
EXPECT_EQ(tChild.LocalScale(), Vector3::One());

EXPECT_EQ(tGrandChild.LocalPosition(), Vector3::Zero());
EXPECT_EQ(tGrandChild.LocalRotation(), Quaternion::Identity());
EXPECT_EQ(tGrandChild.LocalScale(), Vector3::One());
}

int main(int argc, char ** argv)
{
::testing::InitGoogleTest(&argc, argv);
Expand Down