feat(physics): sync runtime physx body state
This commit is contained in:
@@ -295,20 +295,61 @@ void ApplyDynamicBodyProperties(
|
||||
}
|
||||
}
|
||||
|
||||
void SyncActorBindingPose(ActorBinding& binding) {
|
||||
void SyncShapeBindingState(
|
||||
const Components::GameObject& actorOwner,
|
||||
ShapeBinding& shapeBinding) {
|
||||
if (!shapeBinding.shape || !shapeBinding.collider) {
|
||||
return;
|
||||
}
|
||||
|
||||
shapeBinding.shape->setLocalPose(
|
||||
BuildShapeLocalPose(actorOwner, *shapeBinding.collider));
|
||||
shapeBinding.shape->setGeometry(BuildColliderGeometry(*shapeBinding.collider).any());
|
||||
shapeBinding.shape->setFlag(physx::PxShapeFlag::eSCENE_QUERY_SHAPE, true);
|
||||
|
||||
const bool isTrigger = shapeBinding.collider->IsTrigger();
|
||||
shapeBinding.shape->setFlag(physx::PxShapeFlag::eSIMULATION_SHAPE, !isTrigger);
|
||||
shapeBinding.shape->setFlag(physx::PxShapeFlag::eTRIGGER_SHAPE, isTrigger);
|
||||
shapeBinding.shape->userData = shapeBinding.collider;
|
||||
shapeBinding.sourceGameObject = shapeBinding.collider->GetGameObject();
|
||||
|
||||
if (shapeBinding.material) {
|
||||
shapeBinding.material->setStaticFriction(
|
||||
std::max(0.0f, shapeBinding.collider->GetStaticFriction()));
|
||||
shapeBinding.material->setDynamicFriction(
|
||||
std::max(0.0f, shapeBinding.collider->GetDynamicFriction()));
|
||||
shapeBinding.material->setRestitution(
|
||||
std::clamp(shapeBinding.collider->GetRestitution(), 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
bool BindingNeedsActorRebuild(const ActorBinding& binding) {
|
||||
if (!binding.actor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool shouldUseDynamicActor =
|
||||
binding.rigidbody != nullptr &&
|
||||
binding.rigidbody->GetBodyType() != PhysicsBodyType::Static;
|
||||
const bool usesDynamicActor = binding.dynamicActor != nullptr;
|
||||
return shouldUseDynamicActor != usesDynamicActor;
|
||||
}
|
||||
|
||||
void SyncActorBindingState(ActorBinding& binding) {
|
||||
if (!binding.actor || !binding.owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ShapeBinding& shapeBinding : binding.shapes) {
|
||||
if (shapeBinding.shape && shapeBinding.collider) {
|
||||
shapeBinding.shape->setLocalPose(
|
||||
BuildShapeLocalPose(*binding.owner, *shapeBinding.collider));
|
||||
}
|
||||
SyncShapeBindingState(*binding.owner, shapeBinding);
|
||||
}
|
||||
|
||||
const physx::PxTransform targetPose = ToPxTransform(*binding.owner->GetTransform());
|
||||
if (binding.dynamicActor) {
|
||||
if (binding.rigidbody) {
|
||||
ApplyDynamicBodyProperties(*binding.dynamicActor, *binding.rigidbody);
|
||||
}
|
||||
|
||||
if (binding.rigidbody && binding.rigidbody->GetBodyType() == PhysicsBodyType::Kinematic) {
|
||||
binding.dynamicActor->setKinematicTarget(targetPose);
|
||||
}
|
||||
@@ -450,7 +491,7 @@ void PhysXWorldBackend::Step(float fixedDeltaTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
SyncActorPosesToScene();
|
||||
SyncActorStateToScene();
|
||||
|
||||
m_native->scene->simulate(fixedDeltaTime);
|
||||
m_native->scene->fetchResults(true);
|
||||
@@ -515,7 +556,7 @@ bool PhysXWorldBackend::Raycast(
|
||||
return false;
|
||||
}
|
||||
|
||||
SyncActorPosesToScene();
|
||||
SyncActorStateToScene();
|
||||
|
||||
physx::PxRaycastBuffer hitBuffer;
|
||||
const physx::PxHitFlags hitFlags = physx::PxHitFlag::ePOSITION | physx::PxHitFlag::eNORMAL;
|
||||
@@ -570,14 +611,21 @@ size_t PhysXWorldBackend::GetShapeCount() const {
|
||||
#endif
|
||||
}
|
||||
|
||||
void PhysXWorldBackend::SyncActorPosesToScene() {
|
||||
void PhysXWorldBackend::SyncActorStateToScene() {
|
||||
#if XCENGINE_ENABLE_PHYSX
|
||||
if (!m_native) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& [_, binding] : m_native->actorsByOwner) {
|
||||
if (BindingNeedsActorRebuild(binding)) {
|
||||
RebuildSceneState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [_, binding] : m_native->actorsByOwner) {
|
||||
SyncActorBindingPose(binding);
|
||||
SyncActorBindingState(binding);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
|
||||
private:
|
||||
void RebuildSceneState(Components::Component* ignoredComponent = nullptr);
|
||||
void SyncActorPosesToScene();
|
||||
void SyncActorStateToScene();
|
||||
|
||||
PhysicsWorldCreateInfo m_createInfo = {};
|
||||
bool m_initialized = false;
|
||||
|
||||
@@ -280,4 +280,122 @@ TEST(PhysicsWorld_Test, RaycastCanHitTriggerCollider) {
|
||||
EXPECT_NEAR(hit.normal.z, -1.0f, 0.02f);
|
||||
}
|
||||
|
||||
TEST(PhysicsWorld_Test, RuntimeColliderGeometryChangesUpdateRaycast) {
|
||||
using namespace XCEngine::Components;
|
||||
|
||||
Scene scene("PhysicsScene");
|
||||
GameObject* target = scene.CreateGameObject("Target");
|
||||
BoxColliderComponent* boxCollider = target->AddComponent<BoxColliderComponent>();
|
||||
boxCollider->SetSize(XCEngine::Math::Vector3(1.0f, 1.0f, 1.0f));
|
||||
|
||||
XCEngine::Physics::PhysicsWorld world;
|
||||
XCEngine::Physics::PhysicsWorldCreateInfo createInfo = {};
|
||||
createInfo.scene = &scene;
|
||||
|
||||
const bool expectedInitialized = XCEngine::Physics::PhysicsWorld::IsPhysXAvailable();
|
||||
ASSERT_EQ(world.Initialize(createInfo), expectedInitialized);
|
||||
|
||||
XCEngine::Physics::RaycastHit hit;
|
||||
EXPECT_FALSE(world.Raycast(
|
||||
XCEngine::Math::Vector3(0.0f, 1.5f, -5.0f),
|
||||
XCEngine::Math::Vector3::Forward(),
|
||||
10.0f,
|
||||
hit));
|
||||
|
||||
boxCollider->SetSize(XCEngine::Math::Vector3(1.0f, 4.0f, 1.0f));
|
||||
|
||||
const bool result = world.Raycast(
|
||||
XCEngine::Math::Vector3(0.0f, 1.5f, -5.0f),
|
||||
XCEngine::Math::Vector3::Forward(),
|
||||
10.0f,
|
||||
hit);
|
||||
|
||||
if (!expectedInitialized) {
|
||||
EXPECT_FALSE(result);
|
||||
EXPECT_EQ(hit.gameObject, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(hit.gameObject, target);
|
||||
EXPECT_NEAR(hit.distance, 4.5f, 0.02f);
|
||||
}
|
||||
|
||||
TEST(PhysicsWorld_Test, RuntimeTriggerFlagChangesUpdateRaycastHit) {
|
||||
using namespace XCEngine::Components;
|
||||
|
||||
Scene scene("PhysicsScene");
|
||||
GameObject* target = scene.CreateGameObject("Target");
|
||||
SphereColliderComponent* sphereCollider = target->AddComponent<SphereColliderComponent>();
|
||||
sphereCollider->SetRadius(1.0f);
|
||||
|
||||
XCEngine::Physics::PhysicsWorld world;
|
||||
XCEngine::Physics::PhysicsWorldCreateInfo createInfo = {};
|
||||
createInfo.scene = &scene;
|
||||
|
||||
const bool expectedInitialized = XCEngine::Physics::PhysicsWorld::IsPhysXAvailable();
|
||||
ASSERT_EQ(world.Initialize(createInfo), expectedInitialized);
|
||||
|
||||
sphereCollider->SetTrigger(true);
|
||||
|
||||
XCEngine::Physics::RaycastHit hit;
|
||||
const bool result = world.Raycast(
|
||||
XCEngine::Math::Vector3(0.0f, 0.0f, -5.0f),
|
||||
XCEngine::Math::Vector3::Forward(),
|
||||
10.0f,
|
||||
hit);
|
||||
|
||||
if (!expectedInitialized) {
|
||||
EXPECT_FALSE(result);
|
||||
EXPECT_EQ(hit.gameObject, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(hit.gameObject, target);
|
||||
EXPECT_TRUE(hit.isTrigger);
|
||||
}
|
||||
|
||||
TEST(PhysicsWorld_Test, RuntimeBodyTypeChangesRebuildActorAndEnableSimulation) {
|
||||
using namespace XCEngine::Components;
|
||||
|
||||
if (!XCEngine::Physics::PhysicsWorld::IsPhysXAvailable()) {
|
||||
GTEST_SKIP() << "PhysX backend is not available in this build.";
|
||||
}
|
||||
|
||||
Scene scene("PhysicsScene");
|
||||
|
||||
GameObject* ground = scene.CreateGameObject("Ground");
|
||||
ground->GetTransform()->SetPosition(XCEngine::Math::Vector3(0.0f, -0.5f, 0.0f));
|
||||
BoxColliderComponent* groundCollider = ground->AddComponent<BoxColliderComponent>();
|
||||
groundCollider->SetSize(XCEngine::Math::Vector3(20.0f, 1.0f, 20.0f));
|
||||
|
||||
GameObject* body = scene.CreateGameObject("Body");
|
||||
body->GetTransform()->SetPosition(XCEngine::Math::Vector3(0.0f, 4.0f, 0.0f));
|
||||
RigidbodyComponent* rigidbody = body->AddComponent<RigidbodyComponent>();
|
||||
rigidbody->SetBodyType(XCEngine::Physics::PhysicsBodyType::Static);
|
||||
SphereColliderComponent* sphereCollider = body->AddComponent<SphereColliderComponent>();
|
||||
sphereCollider->SetRadius(0.5f);
|
||||
|
||||
XCEngine::Physics::PhysicsWorld world;
|
||||
XCEngine::Physics::PhysicsWorldCreateInfo createInfo = {};
|
||||
createInfo.scene = &scene;
|
||||
ASSERT_TRUE(world.Initialize(createInfo));
|
||||
ASSERT_EQ(world.GetNativeActorCount(), 2u);
|
||||
|
||||
const float initialY = body->GetTransform()->GetPosition().y;
|
||||
world.Step(1.0f / 60.0f);
|
||||
EXPECT_NEAR(body->GetTransform()->GetPosition().y, initialY, 0.001f);
|
||||
|
||||
rigidbody->SetBodyType(XCEngine::Physics::PhysicsBodyType::Dynamic);
|
||||
world.Step(1.0f / 60.0f);
|
||||
EXPECT_EQ(world.GetNativeActorCount(), 2u);
|
||||
|
||||
for (int index = 0; index < 30; ++index) {
|
||||
world.Step(1.0f / 60.0f);
|
||||
}
|
||||
|
||||
EXPECT_LT(body->GetTransform()->GetPosition().y, initialY - 0.1f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user