feat(physics): add runtime physics scaffolding
This commit is contained in:
66
engine/src/Components/BoxColliderComponent.cpp
Normal file
66
engine/src/Components/BoxColliderComponent.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "Components/BoxColliderComponent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
namespace {
|
||||
|
||||
float SanitizeExtent(float value) {
|
||||
return std::isfinite(value) && value > 0.0f ? value : 0.001f;
|
||||
}
|
||||
|
||||
bool TryParseVector3(const std::string& value, Math::Vector3& outValue) {
|
||||
std::string normalized = value;
|
||||
std::replace(normalized.begin(), normalized.end(), ',', ' ');
|
||||
std::istringstream stream(normalized);
|
||||
stream >> outValue.x >> outValue.y >> outValue.z;
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void BoxColliderComponent::SetSize(const Math::Vector3& value) {
|
||||
m_size = Math::Vector3(
|
||||
SanitizeExtent(value.x),
|
||||
SanitizeExtent(value.y),
|
||||
SanitizeExtent(value.z));
|
||||
}
|
||||
|
||||
void BoxColliderComponent::Serialize(std::ostream& os) const {
|
||||
SerializeBase(os);
|
||||
os << "size=" << m_size.x << "," << m_size.y << "," << m_size.z << ";";
|
||||
}
|
||||
|
||||
void BoxColliderComponent::Deserialize(std::istream& is) {
|
||||
std::string token;
|
||||
while (std::getline(is, token, ';')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t eqPos = token.find('=');
|
||||
if (eqPos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string key = token.substr(0, eqPos);
|
||||
const std::string value = token.substr(eqPos + 1);
|
||||
if (DeserializeBaseField(key, value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == "size") {
|
||||
Math::Vector3 parsedSize = Math::Vector3::One();
|
||||
if (TryParseVector3(value, parsedSize)) {
|
||||
SetSize(parsedSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
76
engine/src/Components/CapsuleColliderComponent.cpp
Normal file
76
engine/src/Components/CapsuleColliderComponent.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "Components/CapsuleColliderComponent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
namespace {
|
||||
|
||||
float SanitizePositive(float value, float fallback) {
|
||||
return std::isfinite(value) && value > 0.0f ? value : fallback;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CapsuleColliderComponent::SetRadius(float value) {
|
||||
m_radius = SanitizePositive(value, 0.5f);
|
||||
m_height = std::max(m_height, m_radius * 2.0f);
|
||||
}
|
||||
|
||||
void CapsuleColliderComponent::SetHeight(float value) {
|
||||
m_height = std::max(SanitizePositive(value, 2.0f), m_radius * 2.0f);
|
||||
}
|
||||
|
||||
void CapsuleColliderComponent::SetAxis(ColliderAxis value) {
|
||||
switch (value) {
|
||||
case ColliderAxis::X:
|
||||
case ColliderAxis::Y:
|
||||
case ColliderAxis::Z:
|
||||
m_axis = value;
|
||||
break;
|
||||
default:
|
||||
m_axis = ColliderAxis::Y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CapsuleColliderComponent::Serialize(std::ostream& os) const {
|
||||
SerializeBase(os);
|
||||
os << "radius=" << m_radius << ";";
|
||||
os << "height=" << m_height << ";";
|
||||
os << "axis=" << static_cast<int>(m_axis) << ";";
|
||||
}
|
||||
|
||||
void CapsuleColliderComponent::Deserialize(std::istream& is) {
|
||||
std::string token;
|
||||
while (std::getline(is, token, ';')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t eqPos = token.find('=');
|
||||
if (eqPos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string key = token.substr(0, eqPos);
|
||||
const std::string value = token.substr(eqPos + 1);
|
||||
if (DeserializeBaseField(key, value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == "radius") {
|
||||
SetRadius(std::stof(value));
|
||||
} else if (key == "height") {
|
||||
SetHeight(std::stof(value));
|
||||
} else if (key == "axis") {
|
||||
SetAxis(static_cast<ColliderAxis>(std::stoi(value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
90
engine/src/Components/ColliderComponent.cpp
Normal file
90
engine/src/Components/ColliderComponent.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "Components/ColliderComponent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
namespace {
|
||||
|
||||
float SanitizeFinite(float value, float fallback) {
|
||||
return std::isfinite(value) ? value : fallback;
|
||||
}
|
||||
|
||||
float SanitizeNonNegativeFinite(float value, float fallback) {
|
||||
return std::isfinite(value) && value >= 0.0f ? value : fallback;
|
||||
}
|
||||
|
||||
bool TryParseVector3(const std::string& value, Math::Vector3& outValue) {
|
||||
std::string normalized = value;
|
||||
std::replace(normalized.begin(), normalized.end(), ',', ' ');
|
||||
std::istringstream stream(normalized);
|
||||
stream >> outValue.x >> outValue.y >> outValue.z;
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ColliderComponent::SetCenter(const Math::Vector3& value) {
|
||||
m_center = Math::Vector3(
|
||||
SanitizeFinite(value.x, 0.0f),
|
||||
SanitizeFinite(value.y, 0.0f),
|
||||
SanitizeFinite(value.z, 0.0f));
|
||||
}
|
||||
|
||||
void ColliderComponent::SetStaticFriction(float value) {
|
||||
m_staticFriction = SanitizeNonNegativeFinite(value, 0.6f);
|
||||
}
|
||||
|
||||
void ColliderComponent::SetDynamicFriction(float value) {
|
||||
m_dynamicFriction = SanitizeNonNegativeFinite(value, 0.6f);
|
||||
}
|
||||
|
||||
void ColliderComponent::SetRestitution(float value) {
|
||||
m_restitution = std::isfinite(value) ? std::clamp(value, 0.0f, 1.0f) : 0.0f;
|
||||
}
|
||||
|
||||
void ColliderComponent::SerializeBase(std::ostream& os) const {
|
||||
os << "isTrigger=" << (m_isTrigger ? 1 : 0) << ";";
|
||||
os << "center=" << m_center.x << "," << m_center.y << "," << m_center.z << ";";
|
||||
os << "staticFriction=" << m_staticFriction << ";";
|
||||
os << "dynamicFriction=" << m_dynamicFriction << ";";
|
||||
os << "restitution=" << m_restitution << ";";
|
||||
}
|
||||
|
||||
bool ColliderComponent::DeserializeBaseField(const std::string& key, const std::string& value) {
|
||||
if (key == "isTrigger") {
|
||||
m_isTrigger = (std::stoi(value) != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == "center") {
|
||||
Math::Vector3 parsedCenter = Math::Vector3::Zero();
|
||||
if (TryParseVector3(value, parsedCenter)) {
|
||||
SetCenter(parsedCenter);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == "staticFriction") {
|
||||
SetStaticFriction(std::stof(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == "dynamicFriction") {
|
||||
SetDynamicFriction(std::stof(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == "restitution") {
|
||||
SetRestitution(std::stof(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
@@ -2,12 +2,17 @@
|
||||
|
||||
#include "Components/AudioListenerComponent.h"
|
||||
#include "Components/AudioSourceComponent.h"
|
||||
#include "Components/BoxColliderComponent.h"
|
||||
#include "Components/CameraComponent.h"
|
||||
#include "Components/CapsuleColliderComponent.h"
|
||||
#include "Components/ColliderComponent.h"
|
||||
#include "Components/GaussianSplatRendererComponent.h"
|
||||
#include "Components/GameObject.h"
|
||||
#include "Components/LightComponent.h"
|
||||
#include "Components/MeshFilterComponent.h"
|
||||
#include "Components/MeshRendererComponent.h"
|
||||
#include "Components/RigidbodyComponent.h"
|
||||
#include "Components/SphereColliderComponent.h"
|
||||
#include "Components/VolumeRendererComponent.h"
|
||||
#include "Scripting/ScriptComponent.h"
|
||||
|
||||
@@ -33,6 +38,10 @@ ComponentFactoryRegistry::ComponentFactoryRegistry() {
|
||||
RegisterFactory("Light", &CreateBuiltInComponent<LightComponent>);
|
||||
RegisterFactory("AudioSource", &CreateBuiltInComponent<AudioSourceComponent>);
|
||||
RegisterFactory("AudioListener", &CreateBuiltInComponent<AudioListenerComponent>);
|
||||
RegisterFactory("Rigidbody", &CreateBuiltInComponent<RigidbodyComponent>);
|
||||
RegisterFactory("BoxCollider", &CreateBuiltInComponent<BoxColliderComponent>);
|
||||
RegisterFactory("SphereCollider", &CreateBuiltInComponent<SphereColliderComponent>);
|
||||
RegisterFactory("CapsuleCollider", &CreateBuiltInComponent<CapsuleColliderComponent>);
|
||||
RegisterFactory("MeshFilter", &CreateBuiltInComponent<MeshFilterComponent>);
|
||||
RegisterFactory("MeshRenderer", &CreateBuiltInComponent<MeshRendererComponent>);
|
||||
RegisterFactory("GaussianSplatRenderer", &CreateBuiltInComponent<GaussianSplatRendererComponent>);
|
||||
|
||||
@@ -58,6 +58,18 @@ std::unordered_map<GameObject::ID, GameObject*>& GameObject::GetGlobalRegistry()
|
||||
return *registry;
|
||||
}
|
||||
|
||||
void GameObject::NotifyComponentAdded(Component* component) {
|
||||
if (m_scene && component) {
|
||||
m_scene->m_onComponentAdded.Invoke(this, component);
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::NotifyComponentRemoving(Component* component) {
|
||||
if (m_scene && component) {
|
||||
m_scene->m_onComponentRemoved.Invoke(this, component);
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::NotifyComponentsBecameActive() {
|
||||
for (auto& comp : m_components) {
|
||||
if (comp->IsEnabled()) {
|
||||
|
||||
75
engine/src/Components/RigidbodyComponent.cpp
Normal file
75
engine/src/Components/RigidbodyComponent.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "Components/RigidbodyComponent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
namespace {
|
||||
|
||||
float SanitizePositiveFinite(float value, float fallback) {
|
||||
return std::isfinite(value) && value > 0.0f ? value : fallback;
|
||||
}
|
||||
|
||||
float SanitizeNonNegativeFinite(float value, float fallback) {
|
||||
return std::isfinite(value) && value >= 0.0f ? value : fallback;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RigidbodyComponent::SetMass(float value) {
|
||||
m_mass = SanitizePositiveFinite(value, 1.0f);
|
||||
}
|
||||
|
||||
void RigidbodyComponent::SetLinearDamping(float value) {
|
||||
m_linearDamping = SanitizeNonNegativeFinite(value, 0.0f);
|
||||
}
|
||||
|
||||
void RigidbodyComponent::SetAngularDamping(float value) {
|
||||
m_angularDamping = SanitizeNonNegativeFinite(value, 0.05f);
|
||||
}
|
||||
|
||||
void RigidbodyComponent::Serialize(std::ostream& os) const {
|
||||
os << "bodyType=" << static_cast<int>(m_bodyType) << ";";
|
||||
os << "mass=" << m_mass << ";";
|
||||
os << "linearDamping=" << m_linearDamping << ";";
|
||||
os << "angularDamping=" << m_angularDamping << ";";
|
||||
os << "useGravity=" << (m_useGravity ? 1 : 0) << ";";
|
||||
os << "enableCCD=" << (m_enableCCD ? 1 : 0) << ";";
|
||||
}
|
||||
|
||||
void RigidbodyComponent::Deserialize(std::istream& is) {
|
||||
std::string token;
|
||||
while (std::getline(is, token, ';')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t eqPos = token.find('=');
|
||||
if (eqPos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string key = token.substr(0, eqPos);
|
||||
const std::string value = token.substr(eqPos + 1);
|
||||
|
||||
if (key == "bodyType") {
|
||||
m_bodyType = static_cast<Physics::PhysicsBodyType>(std::stoi(value));
|
||||
} else if (key == "mass") {
|
||||
SetMass(std::stof(value));
|
||||
} else if (key == "linearDamping") {
|
||||
SetLinearDamping(std::stof(value));
|
||||
} else if (key == "angularDamping") {
|
||||
SetAngularDamping(std::stof(value));
|
||||
} else if (key == "useGravity") {
|
||||
m_useGravity = (std::stoi(value) != 0);
|
||||
} else if (key == "enableCCD") {
|
||||
m_enableCCD = (std::stoi(value) != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
43
engine/src/Components/SphereColliderComponent.cpp
Normal file
43
engine/src/Components/SphereColliderComponent.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "Components/SphereColliderComponent.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
void SphereColliderComponent::SetRadius(float value) {
|
||||
m_radius = std::isfinite(value) && value > 0.0f ? value : 0.001f;
|
||||
}
|
||||
|
||||
void SphereColliderComponent::Serialize(std::ostream& os) const {
|
||||
SerializeBase(os);
|
||||
os << "radius=" << m_radius << ";";
|
||||
}
|
||||
|
||||
void SphereColliderComponent::Deserialize(std::istream& is) {
|
||||
std::string token;
|
||||
while (std::getline(is, token, ';')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t eqPos = token.find('=');
|
||||
if (eqPos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string key = token.substr(0, eqPos);
|
||||
const std::string value = token.substr(eqPos + 1);
|
||||
if (DeserializeBaseField(key, value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == "radius") {
|
||||
SetRadius(std::stof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
5
engine/src/Physics/PhysX/PhysXCommon.h
Normal file
5
engine/src/Physics/PhysX/PhysXCommon.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#if XCENGINE_ENABLE_PHYSX
|
||||
#include <PxPhysicsAPI.h>
|
||||
#endif
|
||||
157
engine/src/Physics/PhysX/PhysXWorldBackend.cpp
Normal file
157
engine/src/Physics/PhysX/PhysXWorldBackend.cpp
Normal 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
|
||||
28
engine/src/Physics/PhysX/PhysXWorldBackend.h
Normal file
28
engine/src/Physics/PhysX/PhysXWorldBackend.h
Normal 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
|
||||
185
engine/src/Physics/PhysicsWorld.cpp
Normal file
185
engine/src/Physics/PhysicsWorld.cpp
Normal 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
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Scene/SceneRuntime.h"
|
||||
|
||||
#include <XCEngine/Physics/PhysicsWorld.h>
|
||||
#include "Scripting/ScriptEngine.h"
|
||||
#include <XCEngine/UI/Runtime/UISceneRuntimeContext.h>
|
||||
|
||||
@@ -28,6 +29,7 @@ void SceneRuntime::Start(Scene* scene) {
|
||||
}
|
||||
|
||||
m_scene = scene;
|
||||
CreatePhysicsWorldForScene(scene);
|
||||
m_running = true;
|
||||
m_uiRuntime->Reset();
|
||||
Scripting::ScriptEngine::Get().OnRuntimeStart(scene);
|
||||
@@ -35,12 +37,14 @@ void SceneRuntime::Start(Scene* scene) {
|
||||
|
||||
void SceneRuntime::Stop() {
|
||||
if (!m_running) {
|
||||
DestroyPhysicsWorld();
|
||||
m_uiRuntime->Reset();
|
||||
m_scene = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
Scripting::ScriptEngine::Get().OnRuntimeStop();
|
||||
DestroyPhysicsWorld();
|
||||
m_uiRuntime->Reset();
|
||||
m_running = false;
|
||||
m_scene = nullptr;
|
||||
@@ -48,12 +52,14 @@ void SceneRuntime::Stop() {
|
||||
|
||||
void SceneRuntime::ReplaceScene(Scene* scene) {
|
||||
if (!m_running) {
|
||||
DestroyPhysicsWorld();
|
||||
m_scene = scene;
|
||||
m_uiRuntime->Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
m_scene = scene;
|
||||
CreatePhysicsWorldForScene(scene);
|
||||
m_uiRuntime->Reset();
|
||||
Scripting::ScriptEngine::Get().OnRuntimeSceneReplaced(scene);
|
||||
}
|
||||
@@ -66,6 +72,9 @@ void SceneRuntime::FixedUpdate(float fixedDeltaTime) {
|
||||
// Scripts run first so their state changes are visible to native components in the same frame.
|
||||
Scripting::ScriptEngine::Get().OnFixedUpdate(fixedDeltaTime);
|
||||
m_scene->FixedUpdate(fixedDeltaTime);
|
||||
if (m_physicsWorld) {
|
||||
m_physicsWorld->Step(fixedDeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneRuntime::Update(float deltaTime) {
|
||||
@@ -115,5 +124,29 @@ void SceneRuntime::ClearQueuedUIInputEvents() {
|
||||
m_uiRuntime->ClearQueuedInputEvents();
|
||||
}
|
||||
|
||||
void SceneRuntime::CreatePhysicsWorldForScene(Scene* scene) {
|
||||
if (!scene) {
|
||||
DestroyPhysicsWorld();
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyPhysicsWorld();
|
||||
|
||||
auto physicsWorld = std::make_unique<Physics::PhysicsWorld>();
|
||||
Physics::PhysicsWorldCreateInfo createInfo = {};
|
||||
createInfo.scene = scene;
|
||||
physicsWorld->Initialize(createInfo);
|
||||
m_physicsWorld = std::move(physicsWorld);
|
||||
}
|
||||
|
||||
void SceneRuntime::DestroyPhysicsWorld() {
|
||||
if (!m_physicsWorld) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_physicsWorld->Shutdown();
|
||||
m_physicsWorld.reset();
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
|
||||
Reference in New Issue
Block a user