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,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

View 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

View 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

View File

@@ -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>);

View File

@@ -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()) {

View 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

View 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