diff --git a/editor/src/Core/PlaySessionController.h b/editor/src/Core/PlaySessionController.h index ced093ad..650d69b0 100644 --- a/editor/src/Core/PlaySessionController.h +++ b/editor/src/Core/PlaySessionController.h @@ -29,6 +29,7 @@ public: bool StepPlay(IEditorContext& context); ::XCEngine::Components::Scene* GetRuntimeScene() const { return m_runtimeLoop.GetScene(); } + ::XCEngine::Physics::PhysicsWorld* GetRuntimePhysicsWorld() const { return m_runtimeLoop.GetPhysicsWorld(); } private: void ResetRuntimeInputBridge(); diff --git a/engine/include/XCEngine/Scene/RuntimeLoop.h b/engine/include/XCEngine/Scene/RuntimeLoop.h index 20a6f88c..18e13758 100644 --- a/engine/include/XCEngine/Scene/RuntimeLoop.h +++ b/engine/include/XCEngine/Scene/RuntimeLoop.h @@ -32,6 +32,7 @@ public: bool IsRunning() const { return m_sceneRuntime.IsRunning(); } bool IsPaused() const { return m_paused; } Scene* GetScene() const { return m_sceneRuntime.GetScene(); } + Physics::PhysicsWorld* GetPhysicsWorld() const { return m_sceneRuntime.GetPhysicsWorld(); } float GetFixedAccumulator() const { return m_fixedAccumulator; } private: diff --git a/tests/editor/test_play_session_controller.cpp b/tests/editor/test_play_session_controller.cpp index e7db9a77..692b68a4 100644 --- a/tests/editor/test_play_session_controller.cpp +++ b/tests/editor/test_play_session_controller.cpp @@ -5,8 +5,12 @@ #include "Core/PlaySessionController.h" #include "Commands/EntityCommands.h" +#include +#include +#include #include #include +#include namespace XCEngine::Editor { namespace { @@ -274,5 +278,68 @@ TEST_F(PlaySessionControllerTest, RuntimeSceneUndoRedoRebindsPlaySessionBeforeSt EXPECT_EQ(m_context.GetSceneManager().GetEntity(runtimeEntityId), nullptr); } +TEST_F(PlaySessionControllerTest, PlayModeCreatesRuntimePhysicsWorldAndRestoresEditorSnapshotAfterSimulation) { + if (!::XCEngine::Physics::PhysicsWorld::IsPhysXAvailable()) { + GTEST_SKIP() << "PhysX is not available in this configuration."; + } + + auto* ground = m_context.GetSceneManager().CreateEntity("Ground"); + ASSERT_NE(ground, nullptr); + ground->GetTransform()->SetLocalPosition(Math::Vector3(0.0f, -1.0f, 0.0f)); + auto* groundCollider = ground->AddComponent<::XCEngine::Components::BoxColliderComponent>(); + ASSERT_NE(groundCollider, nullptr); + groundCollider->SetSize(Math::Vector3(10.0f, 1.0f, 10.0f)); + + auto* ball = m_context.GetSceneManager().CreateEntity("Ball"); + ASSERT_NE(ball, nullptr); + const uint64_t ballId = ball->GetID(); + ball->GetTransform()->SetLocalPosition(Math::Vector3(0.0f, 5.0f, 0.0f)); + auto* rigidbody = ball->AddComponent<::XCEngine::Components::RigidbodyComponent>(); + ASSERT_NE(rigidbody, nullptr); + rigidbody->SetUseGravity(true); + rigidbody->SetLinearDamping(0.0f); + rigidbody->SetAngularDamping(0.0f); + auto* sphereCollider = ball->AddComponent<::XCEngine::Components::SphereColliderComponent>(); + ASSERT_NE(sphereCollider, nullptr); + sphereCollider->SetRadius(0.5f); + + const bool dirtyStateBeforePlay = m_context.GetSceneManager().IsSceneDirty(); + + ASSERT_TRUE(m_controller.StartPlay(m_context)); + ASSERT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Play); + + ::XCEngine::Physics::PhysicsWorld* physicsWorld = m_controller.GetRuntimePhysicsWorld(); + ASSERT_NE(physicsWorld, nullptr); + EXPECT_TRUE(physicsWorld->IsInitialized()); + EXPECT_EQ(physicsWorld->GetTrackedRigidbodyCount(), 1u); + EXPECT_EQ(physicsWorld->GetTrackedColliderCount(), 2u); + + auto* runtimeBall = m_context.GetSceneManager().GetEntity(ballId); + ASSERT_NE(runtimeBall, nullptr); + const float initialRuntimeY = runtimeBall->GetTransform()->GetLocalPosition().y; + + for (int i = 0; i < 10; ++i) { + m_controller.Update(m_context, 0.05f); + } + + runtimeBall = m_context.GetSceneManager().GetEntity(ballId); + ASSERT_NE(runtimeBall, nullptr); + const float simulatedRuntimeY = runtimeBall->GetTransform()->GetLocalPosition().y; + EXPECT_LT(simulatedRuntimeY, initialRuntimeY); + EXPECT_EQ(m_context.GetSceneManager().IsSceneDirty(), dirtyStateBeforePlay); + + ASSERT_TRUE(m_controller.StopPlay(m_context)); + EXPECT_EQ(m_context.GetRuntimeMode(), EditorRuntimeMode::Edit); + EXPECT_EQ(m_controller.GetRuntimePhysicsWorld(), nullptr); + EXPECT_EQ(m_context.GetSceneManager().IsSceneDirty(), dirtyStateBeforePlay); + + auto* restoredBall = m_context.GetSceneManager().GetEntity(ballId); + ASSERT_NE(restoredBall, nullptr); + const Math::Vector3 restoredPosition = restoredBall->GetTransform()->GetLocalPosition(); + EXPECT_NEAR(restoredPosition.x, 0.0f, 1e-4f); + EXPECT_NEAR(restoredPosition.y, 5.0f, 1e-4f); + EXPECT_NEAR(restoredPosition.z, 0.0f, 1e-4f); +} + } // namespace } // namespace XCEngine::Editor