Files
SDL3CPlusPlus/src/services/impl/physics_bridge_service.cpp
johndoe6345789 c0c1fb881e feat: Enhance physics integration and scripting capabilities
- Added support for triangle mesh rigid bodies in the physics bridge service.
- Implemented methods to create static meshes and retrieve linear velocities in the script engine service.
- Introduced new Lua bindings for physics operations, including `physics_create_static_mesh` and `physics_get_linear_velocity`.
- Improved shader creation in the graphics backend with auto-bind uniform options and error handling.
- Refactored shader generation to consolidate output and input declarations into structured blocks.
- Enhanced GUI service with better resource logging and error handling.
- Added utility functions for transforming points using matrices.
2026-01-07 01:08:26 +00:00

470 lines
16 KiB
C++

#include "physics_bridge_service.hpp"
#include <btBulletDynamicsCommon.h>
#include <utility>
namespace sdl3cpp::services::impl {
PhysicsBridgeService::PhysicsBridgeService(std::shared_ptr<ILogger> logger)
: collisionConfig_(std::make_unique<btDefaultCollisionConfiguration>()),
dispatcher_(std::make_unique<btCollisionDispatcher>(collisionConfig_.get())),
broadphase_(std::make_unique<btDbvtBroadphase>()),
solver_(std::make_unique<btSequentialImpulseConstraintSolver>()),
world_(std::make_unique<btDiscreteDynamicsWorld>(
dispatcher_.get(),
broadphase_.get(),
solver_.get(),
collisionConfig_.get())),
logger_(std::move(logger)) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "PhysicsBridgeService");
}
world_->setGravity(btVector3(0.0f, -9.81f, 0.0f));
}
PhysicsBridgeService::~PhysicsBridgeService() {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "~PhysicsBridgeService");
}
if (world_) {
for (auto& [name, entry] : bodies_) {
if (entry.body) {
world_->removeRigidBody(entry.body.get());
}
}
}
}
bool PhysicsBridgeService::SetGravity(const btVector3& gravity,
std::string& error) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "SetGravity",
"gravity.x=" + std::to_string(gravity.getX()) +
", gravity.y=" + std::to_string(gravity.getY()) +
", gravity.z=" + std::to_string(gravity.getZ()));
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
world_->setGravity(gravity);
return true;
}
bool PhysicsBridgeService::AddBoxRigidBody(const std::string& name,
const btVector3& halfExtents,
float mass,
const btTransform& transform,
std::string& error) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "AddBoxRigidBody",
"name=" + name +
", halfExtents.x=" + std::to_string(halfExtents.getX()) +
", halfExtents.y=" + std::to_string(halfExtents.getY()) +
", halfExtents.z=" + std::to_string(halfExtents.getZ()) +
", mass=" + std::to_string(mass) +
", origin.x=" + std::to_string(transform.getOrigin().getX()) +
", origin.y=" + std::to_string(transform.getOrigin().getY()) +
", origin.z=" + std::to_string(transform.getOrigin().getZ()));
}
if (name.empty()) {
error = "Rigid body name must not be empty";
return false;
}
if (halfExtents.getX() <= 0.0f || halfExtents.getY() <= 0.0f || halfExtents.getZ() <= 0.0f) {
error = "Box half extents must be positive";
return false;
}
if (mass < 0.0f) {
error = "Rigid body mass must be non-negative";
return false;
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
if (bodies_.count(name)) {
error = "Rigid body already exists: " + name;
return false;
}
auto shape = std::make_unique<btBoxShape>(halfExtents);
btVector3 inertia(0.0f, 0.0f, 0.0f);
if (mass > 0.0f) {
shape->calculateLocalInertia(mass, inertia);
}
auto motionState = std::make_unique<btDefaultMotionState>(transform);
btRigidBody::btRigidBodyConstructionInfo constructionInfo(
mass,
motionState.get(),
shape.get(),
inertia);
auto body = std::make_unique<btRigidBody>(constructionInfo);
world_->addRigidBody(body.get());
bodies_.emplace(name, BodyRecord{
std::move(shape),
std::move(motionState),
std::move(body),
nullptr,
});
return true;
}
bool PhysicsBridgeService::AddSphereRigidBody(const std::string& name,
float radius,
float mass,
const btTransform& transform,
std::string& error) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "AddSphereRigidBody",
"name=" + name +
", radius=" + std::to_string(radius) +
", mass=" + std::to_string(mass) +
", origin.x=" + std::to_string(transform.getOrigin().getX()) +
", origin.y=" + std::to_string(transform.getOrigin().getY()) +
", origin.z=" + std::to_string(transform.getOrigin().getZ()));
}
if (name.empty()) {
error = "Rigid body name must not be empty";
return false;
}
if (radius <= 0.0f) {
error = "Sphere radius must be positive";
return false;
}
if (mass < 0.0f) {
error = "Rigid body mass must be non-negative";
return false;
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
if (bodies_.count(name)) {
error = "Rigid body already exists: " + name;
return false;
}
auto shape = std::make_unique<btSphereShape>(radius);
btVector3 inertia(0.0f, 0.0f, 0.0f);
if (mass > 0.0f) {
shape->calculateLocalInertia(mass, inertia);
}
auto motionState = std::make_unique<btDefaultMotionState>(transform);
btRigidBody::btRigidBodyConstructionInfo constructionInfo(
mass,
motionState.get(),
shape.get(),
inertia);
auto body = std::make_unique<btRigidBody>(constructionInfo);
world_->addRigidBody(body.get());
bodies_.emplace(name, BodyRecord{
std::move(shape),
std::move(motionState),
std::move(body),
nullptr,
});
return true;
}
bool PhysicsBridgeService::AddTriangleMeshRigidBody(const std::string& name,
const std::vector<std::array<float, 3>>& vertices,
const std::vector<uint32_t>& indices,
const btTransform& transform,
std::string& error) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "AddTriangleMeshRigidBody",
"name=" + name +
", vertexCount=" + std::to_string(vertices.size()) +
", indexCount=" + std::to_string(indices.size()) +
", origin.x=" + std::to_string(transform.getOrigin().getX()) +
", origin.y=" + std::to_string(transform.getOrigin().getY()) +
", origin.z=" + std::to_string(transform.getOrigin().getZ()));
}
if (name.empty()) {
error = "Rigid body name must not be empty";
return false;
}
if (vertices.empty()) {
error = "Triangle mesh vertices must not be empty";
return false;
}
if (indices.empty()) {
error = "Triangle mesh indices must not be empty";
return false;
}
if (indices.size() % 3 != 0) {
error = "Triangle mesh indices must be a multiple of 3";
return false;
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
if (bodies_.count(name)) {
error = "Rigid body already exists: " + name;
return false;
}
auto triangleMesh = std::make_unique<btTriangleMesh>();
for (size_t index = 0; index < indices.size(); index += 3) {
uint32_t i0 = indices[index];
uint32_t i1 = indices[index + 1];
uint32_t i2 = indices[index + 2];
if (i0 >= vertices.size() || i1 >= vertices.size() || i2 >= vertices.size()) {
error = "Triangle mesh index out of range";
return false;
}
const auto& v0 = vertices[i0];
const auto& v1 = vertices[i1];
const auto& v2 = vertices[i2];
triangleMesh->addTriangle(
btVector3(v0[0], v0[1], v0[2]),
btVector3(v1[0], v1[1], v1[2]),
btVector3(v2[0], v2[1], v2[2]),
true);
}
auto shape = std::make_unique<btBvhTriangleMeshShape>(triangleMesh.get(), true, true);
btVector3 inertia(0.0f, 0.0f, 0.0f);
auto motionState = std::make_unique<btDefaultMotionState>(transform);
btRigidBody::btRigidBodyConstructionInfo constructionInfo(
0.0f,
motionState.get(),
shape.get(),
inertia);
auto body = std::make_unique<btRigidBody>(constructionInfo);
world_->addRigidBody(body.get());
bodies_.emplace(name, BodyRecord{
std::move(shape),
std::move(motionState),
std::move(body),
std::move(triangleMesh),
});
return true;
}
bool PhysicsBridgeService::RemoveRigidBody(const std::string& name,
std::string& error) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "RemoveRigidBody", "name=" + name);
}
if (name.empty()) {
error = "Rigid body name must not be empty";
return false;
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
auto it = bodies_.find(name);
if (it == bodies_.end()) {
error = "Rigid body not found: " + name;
return false;
}
if (it->second.body) {
world_->removeRigidBody(it->second.body.get());
}
bodies_.erase(it);
return true;
}
bool PhysicsBridgeService::SetRigidBodyTransform(const std::string& name,
const btTransform& transform,
std::string& error) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "SetRigidBodyTransform",
"name=" + name +
", origin.x=" + std::to_string(transform.getOrigin().getX()) +
", origin.y=" + std::to_string(transform.getOrigin().getY()) +
", origin.z=" + std::to_string(transform.getOrigin().getZ()));
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
auto* record = FindBodyRecord(name, error);
if (!record || !record->body) {
return false;
}
record->body->setWorldTransform(transform);
if (record->motionState) {
record->motionState->setWorldTransform(transform);
}
record->body->activate(true);
return true;
}
bool PhysicsBridgeService::ApplyForce(const std::string& name,
const btVector3& force,
std::string& error) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "ApplyForce",
"name=" + name +
", force.x=" + std::to_string(force.getX()) +
", force.y=" + std::to_string(force.getY()) +
", force.z=" + std::to_string(force.getZ()));
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
auto* record = FindBodyRecord(name, error);
if (!record || !record->body) {
return false;
}
record->body->applyCentralForce(force);
record->body->activate(true);
return true;
}
bool PhysicsBridgeService::ApplyImpulse(const std::string& name,
const btVector3& impulse,
std::string& error) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "ApplyImpulse",
"name=" + name +
", impulse.x=" + std::to_string(impulse.getX()) +
", impulse.y=" + std::to_string(impulse.getY()) +
", impulse.z=" + std::to_string(impulse.getZ()));
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
auto* record = FindBodyRecord(name, error);
if (!record || !record->body) {
return false;
}
record->body->applyCentralImpulse(impulse);
record->body->activate(true);
return true;
}
bool PhysicsBridgeService::SetLinearVelocity(const std::string& name,
const btVector3& velocity,
std::string& error) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "SetLinearVelocity",
"name=" + name +
", velocity.x=" + std::to_string(velocity.getX()) +
", velocity.y=" + std::to_string(velocity.getY()) +
", velocity.z=" + std::to_string(velocity.getZ()));
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
auto* record = FindBodyRecord(name, error);
if (!record || !record->body) {
return false;
}
record->body->setLinearVelocity(velocity);
record->body->activate(true);
return true;
}
bool PhysicsBridgeService::GetLinearVelocity(const std::string& name,
btVector3& outVelocity,
std::string& error) const {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "GetLinearVelocity", "name=" + name);
}
if (!world_) {
error = "Physics world is not initialized";
return false;
}
auto* record = FindBodyRecord(name, error);
if (!record || !record->body) {
return false;
}
outVelocity = record->body->getLinearVelocity();
return true;
}
int PhysicsBridgeService::StepSimulation(float deltaTime, int maxSubSteps) {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "StepSimulation",
"deltaTime=" + std::to_string(deltaTime) +
", maxSubSteps=" + std::to_string(maxSubSteps));
}
if (!world_) {
return 0;
}
if (maxSubSteps < 0) {
maxSubSteps = 0;
}
return static_cast<int>(world_->stepSimulation(deltaTime, maxSubSteps, 1.0f / 60.0f));
}
bool PhysicsBridgeService::GetRigidBodyTransform(const std::string& name,
btTransform& outTransform,
std::string& error) const {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "GetRigidBodyTransform", "name=" + name);
}
auto* record = FindBodyRecord(name, error);
if (!record) {
return false;
}
if (record->motionState) {
record->motionState->getWorldTransform(outTransform);
return true;
}
if (record->body) {
outTransform = record->body->getWorldTransform();
return true;
}
error = "Rigid body handle is missing";
return false;
}
size_t PhysicsBridgeService::GetBodyCount() const {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "GetBodyCount");
}
return bodies_.size();
}
void PhysicsBridgeService::Clear() {
if (logger_) {
logger_->Trace("PhysicsBridgeService", "Clear");
}
if (world_) {
for (auto& [name, entry] : bodies_) {
if (entry.body) {
world_->removeRigidBody(entry.body.get());
}
}
}
bodies_.clear();
}
PhysicsBridgeService::BodyRecord* PhysicsBridgeService::FindBodyRecord(const std::string& name,
std::string& error) {
auto it = bodies_.find(name);
if (it == bodies_.end()) {
error = "Rigid body not found: " + name;
return nullptr;
}
if (!it->second.body) {
error = "Rigid body handle is missing";
return nullptr;
}
return &it->second;
}
const PhysicsBridgeService::BodyRecord* PhysicsBridgeService::FindBodyRecord(const std::string& name,
std::string& error) const {
auto it = bodies_.find(name);
if (it == bodies_.end()) {
error = "Rigid body not found: " + name;
return nullptr;
}
if (!it->second.body) {
error = "Rigid body handle is missing";
return nullptr;
}
return &it->second;
}
} // namespace sdl3cpp::services::impl