feat(physics): add runtime physics scaffolding

This commit is contained in:
2026-04-15 11:58:27 +08:00
parent d17ddffdef
commit 3317e47009
31 changed files with 2120 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
#pragma once
#if XCENGINE_ENABLE_PHYSX
#include <PxPhysicsAPI.h>
#endif

View File

@@ -0,0 +1,157 @@
#include "Physics/PhysX/PhysXWorldBackend.h"
#include "Physics/PhysX/PhysXCommon.h"
namespace XCEngine {
namespace Physics {
#if XCENGINE_ENABLE_PHYSX
namespace {
physx::PxVec3 ToPxVec3(const Math::Vector3& value) {
return physx::PxVec3(value.x, value.y, value.z);
}
} // namespace
struct PhysXWorldBackend::NativeState {
physx::PxDefaultAllocator allocator;
physx::PxDefaultErrorCallback errorCallback;
physx::PxFoundation* foundation = nullptr;
physx::PxPhysics* physics = nullptr;
physx::PxDefaultCpuDispatcher* dispatcher = nullptr;
physx::PxScene* scene = nullptr;
physx::PxMaterial* defaultMaterial = nullptr;
bool extensionsInitialized = false;
};
#else
struct PhysXWorldBackend::NativeState {};
#endif
PhysXWorldBackend::PhysXWorldBackend() = default;
PhysXWorldBackend::~PhysXWorldBackend() {
Shutdown();
}
bool PhysXWorldBackend::Initialize(const PhysicsWorldCreateInfo& createInfo) {
Shutdown();
m_createInfo = createInfo;
#if XCENGINE_ENABLE_PHYSX
m_native = std::make_unique<NativeState>();
NativeState& native = *m_native;
native.foundation = PxCreateFoundation(
PX_PHYSICS_VERSION,
native.allocator,
native.errorCallback);
if (!native.foundation) {
Shutdown();
return false;
}
native.physics = PxCreatePhysics(
PX_PHYSICS_VERSION,
*native.foundation,
physx::PxTolerancesScale());
if (!native.physics) {
Shutdown();
return false;
}
native.extensionsInitialized = PxInitExtensions(*native.physics, nullptr);
if (!native.extensionsInitialized) {
Shutdown();
return false;
}
native.dispatcher = physx::PxDefaultCpuDispatcherCreate(0);
if (!native.dispatcher) {
Shutdown();
return false;
}
physx::PxSceneDesc sceneDesc(native.physics->getTolerancesScale());
sceneDesc.gravity = ToPxVec3(createInfo.gravity);
sceneDesc.cpuDispatcher = native.dispatcher;
sceneDesc.filterShader = physx::PxDefaultSimulationFilterShader;
if (!sceneDesc.isValid()) {
Shutdown();
return false;
}
native.scene = native.physics->createScene(sceneDesc);
if (!native.scene) {
Shutdown();
return false;
}
native.defaultMaterial = native.physics->createMaterial(0.5f, 0.5f, 0.6f);
if (!native.defaultMaterial) {
Shutdown();
return false;
}
m_initialized = true;
#else
m_initialized = false;
#endif
return m_initialized;
}
void PhysXWorldBackend::Shutdown() {
#if XCENGINE_ENABLE_PHYSX
if (m_native) {
if (m_native->scene) {
m_native->scene->release();
m_native->scene = nullptr;
}
if (m_native->defaultMaterial) {
m_native->defaultMaterial->release();
m_native->defaultMaterial = nullptr;
}
if (m_native->dispatcher) {
m_native->dispatcher->release();
m_native->dispatcher = nullptr;
}
if (m_native->extensionsInitialized) {
PxCloseExtensions();
m_native->extensionsInitialized = false;
}
if (m_native->physics) {
m_native->physics->release();
m_native->physics = nullptr;
}
if (m_native->foundation) {
m_native->foundation->release();
m_native->foundation = nullptr;
}
m_native.reset();
}
#else
m_native.reset();
#endif
m_initialized = false;
m_createInfo = PhysicsWorldCreateInfo{};
}
void PhysXWorldBackend::Step(float fixedDeltaTime) {
if (!m_initialized || !m_native || !m_native->scene || fixedDeltaTime <= 0.0f) {
return;
}
m_native->scene->simulate(fixedDeltaTime);
m_native->scene->fetchResults(true);
}
} // namespace Physics
} // namespace XCEngine

View File

@@ -0,0 +1,28 @@
#pragma once
#include <XCEngine/Physics/PhysicsTypes.h>
#include <memory>
namespace XCEngine {
namespace Physics {
class PhysXWorldBackend {
public:
PhysXWorldBackend();
~PhysXWorldBackend();
bool Initialize(const PhysicsWorldCreateInfo& createInfo);
void Shutdown();
void Step(float fixedDeltaTime);
private:
struct NativeState;
PhysicsWorldCreateInfo m_createInfo = {};
bool m_initialized = false;
std::unique_ptr<NativeState> m_native;
};
} // namespace Physics
} // namespace XCEngine

View File

@@ -0,0 +1,185 @@
#include <XCEngine/Physics/PhysicsWorld.h>
#include "Physics/PhysX/PhysXWorldBackend.h"
#include <XCEngine/Components/ColliderComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/RigidbodyComponent.h>
#include <XCEngine/Scene/Scene.h>
#include <functional>
namespace XCEngine {
namespace Physics {
namespace {
void ApplyTrackedCountDelta(size_t& value, int delta) {
if (delta > 0) {
value += static_cast<size_t>(delta);
return;
}
const size_t magnitude = static_cast<size_t>(-delta);
value = magnitude > value ? 0u : (value - magnitude);
}
void VisitGameObjectHierarchy(
Components::GameObject* gameObject,
const std::function<void(Components::GameObject*)>& visitor) {
if (!gameObject) {
return;
}
visitor(gameObject);
for (Components::GameObject* child : gameObject->GetChildren()) {
VisitGameObjectHierarchy(child, visitor);
}
}
} // namespace
PhysicsWorld::PhysicsWorld() = default;
PhysicsWorld::~PhysicsWorld() {
Shutdown();
}
bool PhysicsWorld::IsPhysXAvailable() {
#if XCENGINE_ENABLE_PHYSX
return true;
#else
return false;
#endif
}
bool PhysicsWorld::Initialize(const PhysicsWorldCreateInfo& createInfo) {
Shutdown();
m_createInfo = createInfo;
AttachSceneEventHandlers(m_createInfo.scene);
RebuildTrackedSceneState();
m_backend = std::make_unique<PhysXWorldBackend>();
m_initialized = m_backend->Initialize(createInfo);
return m_initialized;
}
void PhysicsWorld::Shutdown() {
if (m_backend) {
m_backend->Shutdown();
m_backend.reset();
}
DetachSceneEventHandlers();
m_initialized = false;
m_trackedRigidbodyCount = 0;
m_trackedColliderCount = 0;
m_createInfo = PhysicsWorldCreateInfo{};
}
void PhysicsWorld::Step(float fixedDeltaTime) {
if (!m_backend || !m_initialized) {
return;
}
m_backend->Step(fixedDeltaTime);
}
void PhysicsWorld::AttachSceneEventHandlers(Components::Scene* scene) {
if (!scene) {
return;
}
m_componentAddedSubscriptionId = scene->OnComponentAdded().Subscribe(
[this](Components::GameObject*, Components::Component* component) {
TrackComponent(component, 1);
});
m_componentRemovedSubscriptionId = scene->OnComponentRemoved().Subscribe(
[this](Components::GameObject*, Components::Component* component) {
TrackComponent(component, -1);
});
m_gameObjectDestroyedSubscriptionId = scene->OnGameObjectDestroyed().Subscribe(
[this](Components::GameObject* gameObject) {
TrackGameObjectComponents(gameObject, -1);
});
}
void PhysicsWorld::DetachSceneEventHandlers() {
Components::Scene* const scene = m_createInfo.scene;
if (!scene) {
m_componentAddedSubscriptionId = 0;
m_componentRemovedSubscriptionId = 0;
m_gameObjectDestroyedSubscriptionId = 0;
return;
}
if (m_componentAddedSubscriptionId != 0) {
scene->OnComponentAdded().Unsubscribe(m_componentAddedSubscriptionId);
scene->OnComponentAdded().ProcessUnsubscribes();
m_componentAddedSubscriptionId = 0;
}
if (m_componentRemovedSubscriptionId != 0) {
scene->OnComponentRemoved().Unsubscribe(m_componentRemovedSubscriptionId);
scene->OnComponentRemoved().ProcessUnsubscribes();
m_componentRemovedSubscriptionId = 0;
}
if (m_gameObjectDestroyedSubscriptionId != 0) {
scene->OnGameObjectDestroyed().Unsubscribe(m_gameObjectDestroyedSubscriptionId);
scene->OnGameObjectDestroyed().ProcessUnsubscribes();
m_gameObjectDestroyedSubscriptionId = 0;
}
}
void PhysicsWorld::RebuildTrackedSceneState() {
m_trackedRigidbodyCount = 0;
m_trackedColliderCount = 0;
Components::Scene* const scene = m_createInfo.scene;
if (!scene) {
return;
}
for (Components::GameObject* rootGameObject : scene->GetRootGameObjects()) {
VisitGameObjectHierarchy(
rootGameObject,
[this](Components::GameObject* gameObject) {
TrackGameObjectComponents(gameObject, 1);
});
}
}
void PhysicsWorld::TrackGameObjectComponents(Components::GameObject* gameObject, int delta) {
if (!gameObject) {
return;
}
for (Components::RigidbodyComponent* rigidbody : gameObject->GetComponents<Components::RigidbodyComponent>()) {
TrackComponent(rigidbody, delta);
}
for (Components::ColliderComponent* collider : gameObject->GetComponents<Components::ColliderComponent>()) {
TrackComponent(collider, delta);
}
}
void PhysicsWorld::TrackComponent(Components::Component* component, int delta) {
if (!component) {
return;
}
if (dynamic_cast<Components::RigidbodyComponent*>(component) != nullptr) {
ApplyTrackedCountDelta(m_trackedRigidbodyCount, delta);
}
if (dynamic_cast<Components::ColliderComponent*>(component) != nullptr) {
ApplyTrackedCountDelta(m_trackedColliderCount, delta);
}
}
} // namespace Physics
} // namespace XCEngine