feat(physics): add physx raycast queries

This commit is contained in:
2026-04-15 12:43:38 +08:00
parent f90f449745
commit 7cbc992bd8
5 changed files with 240 additions and 21 deletions

View File

@@ -54,6 +54,10 @@ float Max3(float a, float b, float c) {
return std::max(a, std::max(b, c));
}
void ResetRaycastHit(RaycastHit& outHit) {
outHit = RaycastHit{};
}
physx::PxTransform ToPxTransform(const Components::TransformComponent& transform) {
return physx::PxTransform(
ToPxVec3(transform.GetPosition()),
@@ -291,6 +295,30 @@ void ApplyDynamicBodyProperties(
}
}
void SyncActorBindingPose(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));
}
}
const physx::PxTransform targetPose = ToPxTransform(*binding.owner->GetTransform());
if (binding.dynamicActor) {
if (binding.rigidbody && binding.rigidbody->GetBodyType() == PhysicsBodyType::Kinematic) {
binding.dynamicActor->setKinematicTarget(targetPose);
}
return;
}
binding.actor->setGlobalPose(targetPose, false);
}
} // namespace
#else
namespace {
@@ -300,6 +328,10 @@ bool IsRelevantPhysicsComponent(Components::Component* component) {
return false;
}
void ResetRaycastHit(RaycastHit& outHit) {
outHit = RaycastHit{};
}
} // namespace
struct PhysXWorldBackend::NativeState {};
@@ -418,27 +450,7 @@ void PhysXWorldBackend::Step(float fixedDeltaTime) {
return;
}
for (auto& [_, binding] : m_native->actorsByOwner) {
if (!binding.actor || !binding.owner) {
continue;
}
for (ShapeBinding& shapeBinding : binding.shapes) {
if (shapeBinding.shape && shapeBinding.collider) {
shapeBinding.shape->setLocalPose(
BuildShapeLocalPose(*binding.owner, *shapeBinding.collider));
}
}
const physx::PxTransform targetPose = ToPxTransform(*binding.owner->GetTransform());
if (binding.dynamicActor) {
if (binding.rigidbody && binding.rigidbody->GetBodyType() == PhysicsBodyType::Kinematic) {
binding.dynamicActor->setKinematicTarget(targetPose);
}
} else {
binding.actor->setGlobalPose(targetPose, false);
}
}
SyncActorPosesToScene();
m_native->scene->simulate(fixedDeltaTime);
m_native->scene->fetchResults(true);
@@ -486,6 +498,53 @@ void PhysXWorldBackend::RemoveGameObject(Components::GameObject* gameObject) {
RebuildSceneState();
}
bool PhysXWorldBackend::Raycast(
const Math::Vector3& origin,
const Math::Vector3& direction,
float maxDistance,
RaycastHit& outHit) {
ResetRaycastHit(outHit);
#if XCENGINE_ENABLE_PHYSX
if (!m_initialized || !m_native || !m_native->scene || maxDistance <= 0.0f) {
return false;
}
const Math::Vector3 normalizedDirection = direction.Normalized();
if (normalizedDirection.SqrMagnitude() <= Math::EPSILON) {
return false;
}
SyncActorPosesToScene();
physx::PxRaycastBuffer hitBuffer;
const physx::PxHitFlags hitFlags = physx::PxHitFlag::ePOSITION | physx::PxHitFlag::eNORMAL;
const bool hasHit = m_native->scene->raycast(
ToPxVec3(origin),
ToPxVec3(normalizedDirection),
maxDistance,
hitBuffer,
hitFlags);
if (!hasHit || !hitBuffer.hasBlock || !hitBuffer.block.shape) {
return false;
}
const physx::PxRaycastHit& hit = hitBuffer.block;
auto* collider = static_cast<Components::ColliderComponent*>(hit.shape->userData);
outHit.gameObject = collider ? collider->GetGameObject() : nullptr;
outHit.point = ToVector3(hit.position);
outHit.normal = ToVector3(hit.normal);
outHit.distance = hit.distance;
outHit.isTrigger = hit.shape->getFlags().isSet(physx::PxShapeFlag::eTRIGGER_SHAPE);
return true;
#else
(void)origin;
(void)direction;
(void)maxDistance;
return false;
#endif
}
size_t PhysXWorldBackend::GetActorCount() const {
#if XCENGINE_ENABLE_PHYSX
return m_native ? m_native->actorsByOwner.size() : 0u;
@@ -511,6 +570,18 @@ size_t PhysXWorldBackend::GetShapeCount() const {
#endif
}
void PhysXWorldBackend::SyncActorPosesToScene() {
#if XCENGINE_ENABLE_PHYSX
if (!m_native) {
return;
}
for (auto& [_, binding] : m_native->actorsByOwner) {
SyncActorBindingPose(binding);
}
#endif
}
void PhysXWorldBackend::RebuildSceneState(Components::Component* ignoredComponent) {
#if XCENGINE_ENABLE_PHYSX
if (!m_native || !m_native->scene || !m_native->physics || !m_initialized) {
@@ -593,10 +664,12 @@ void PhysXWorldBackend::RebuildSceneState(Components::Component* ignoredComponen
shape->setLocalPose(BuildShapeLocalPose(*gameObject, *collider));
shape->setSimulationFilterData(physx::PxFilterData());
shape->setFlag(physx::PxShapeFlag::eSCENE_QUERY_SHAPE, true);
if (collider->IsTrigger()) {
shape->setFlag(physx::PxShapeFlag::eSIMULATION_SHAPE, false);
shape->setFlag(physx::PxShapeFlag::eTRIGGER_SHAPE, true);
}
shape->userData = collider;
ShapeBinding shapeBinding = {};
shapeBinding.collider = collider;

View File

@@ -1,5 +1,6 @@
#pragma once
#include <XCEngine/Physics/RaycastHit.h>
#include <XCEngine/Physics/PhysicsTypes.h>
#include <cstddef>
@@ -31,12 +32,18 @@ public:
void AddComponent(Components::Component* component);
void RemoveComponent(Components::Component* component);
void RemoveGameObject(Components::GameObject* gameObject);
bool Raycast(
const Math::Vector3& origin,
const Math::Vector3& direction,
float maxDistance,
RaycastHit& outHit);
size_t GetActorCount() const;
size_t GetShapeCount() const;
private:
void RebuildSceneState(Components::Component* ignoredComponent = nullptr);
void SyncActorPosesToScene();
PhysicsWorldCreateInfo m_createInfo = {};
bool m_initialized = false;

View File

@@ -89,6 +89,19 @@ void PhysicsWorld::Step(float fixedDeltaTime) {
m_backend->Step(fixedDeltaTime);
}
bool PhysicsWorld::Raycast(
const Math::Vector3& origin,
const Math::Vector3& direction,
float maxDistance,
RaycastHit& outHit) {
if (!m_backend || !m_initialized) {
outHit = RaycastHit{};
return false;
}
return m_backend->Raycast(origin, direction, maxDistance, outHit);
}
size_t PhysicsWorld::GetNativeActorCount() const {
return m_backend ? m_backend->GetActorCount() : 0u;
}