mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 21:55:09 +00:00
bullet3 support
This commit is contained in:
@@ -77,6 +77,7 @@ find_package(lua CONFIG REQUIRED)
|
||||
find_package(CLI11 CONFIG REQUIRED)
|
||||
find_package(rapidjson CONFIG REQUIRED)
|
||||
find_package(assimp CONFIG REQUIRED)
|
||||
find_package(Bullet CONFIG REQUIRED)
|
||||
|
||||
if(BUILD_SDL3_APP)
|
||||
add_executable(sdl3_app
|
||||
@@ -93,7 +94,7 @@ add_executable(sdl3_app
|
||||
src/script/cube_script.cpp
|
||||
)
|
||||
target_include_directories(sdl3_app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(sdl3_app PRIVATE sdl::sdl Vulkan::Vulkan lua::lua CLI11::CLI11 rapidjson assimp::assimp)
|
||||
target_link_libraries(sdl3_app PRIVATE sdl::sdl Vulkan::Vulkan lua::lua CLI11::CLI11 rapidjson assimp::assimp Bullet::Bullet)
|
||||
target_compile_definitions(sdl3_app PRIVATE SDL_MAIN_HANDLED)
|
||||
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/shaders" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
@@ -107,5 +108,5 @@ add_executable(cube_script_tests
|
||||
src/script/cube_script.cpp
|
||||
)
|
||||
target_include_directories(cube_script_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(cube_script_tests PRIVATE lua::lua assimp::assimp)
|
||||
target_link_libraries(cube_script_tests PRIVATE lua::lua assimp::assimp Bullet::Bullet)
|
||||
add_test(NAME cube_script_tests COMMAND cube_script_tests)
|
||||
|
||||
@@ -113,9 +113,72 @@ end
|
||||
if cube_mesh_info.loaded then
|
||||
log_debug("Loaded cube mesh from %s (%d vertices, %d indices)",
|
||||
cube_mesh_info.path, cube_mesh_info.vertex_count, cube_mesh_info.index_count)
|
||||
else
|
||||
log_debug("Failed to load cube mesh (%s); using fallback cube",
|
||||
cube_mesh_info.error or "unknown")
|
||||
end
|
||||
|
||||
local cube_body_name = "cube_body"
|
||||
local cube_state = {
|
||||
position = {0.0, 0.0, 0.0},
|
||||
rotation = {0.0, 0.0, 0.0, 1.0},
|
||||
}
|
||||
local physics_last_time = 0.0
|
||||
|
||||
local function quaternion_to_matrix(q)
|
||||
local x, y, z, w = q[1], q[2], q[3], q[4]
|
||||
local xx = x * x
|
||||
local yy = y * y
|
||||
local zz = z * z
|
||||
local xy = x * y
|
||||
local xz = x * z
|
||||
local yz = y * z
|
||||
local wx = w * x
|
||||
local wy = w * y
|
||||
local wz = w * z
|
||||
return {
|
||||
1.0 - 2.0 * yy - 2.0 * zz, 2.0 * xy + 2.0 * wz, 2.0 * xz - 2.0 * wy, 0.0,
|
||||
2.0 * xy - 2.0 * wz, 1.0 - 2.0 * xx - 2.0 * zz, 2.0 * yz + 2.0 * wx, 0.0,
|
||||
2.0 * xz + 2.0 * wy, 2.0 * yz - 2.0 * wx, 1.0 - 2.0 * xx - 2.0 * yy, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}
|
||||
end
|
||||
|
||||
local function initialize_physics()
|
||||
if type(physics_create_box) ~= "function" then
|
||||
error("physics_create_box() is unavailable")
|
||||
end
|
||||
local ok, err = physics_create_box(
|
||||
cube_body_name,
|
||||
{1.0, 1.0, 1.0},
|
||||
1.0,
|
||||
{0.0, 2.0, 0.0},
|
||||
{0.0, 0.0, 0.0, 1.0}
|
||||
)
|
||||
if not ok then
|
||||
error("physics_create_box failed: " .. (err or "unknown"))
|
||||
end
|
||||
if type(physics_step_simulation) == "function" then
|
||||
physics_step_simulation(0.0)
|
||||
end
|
||||
end
|
||||
initialize_physics()
|
||||
|
||||
local function sync_physics(time)
|
||||
local dt = time - physics_last_time
|
||||
if dt < 0.0 then
|
||||
dt = 0.0
|
||||
end
|
||||
if dt > 0.0 and type(physics_step_simulation) == "function" then
|
||||
physics_step_simulation(dt)
|
||||
end
|
||||
physics_last_time = time
|
||||
if type(physics_get_transform) ~= "function" then
|
||||
error("physics_get_transform() is unavailable")
|
||||
end
|
||||
local transform, err = physics_get_transform(cube_body_name)
|
||||
if not transform then
|
||||
error("physics_get_transform failed: " .. (err or "unknown"))
|
||||
end
|
||||
cube_state.position = transform.position
|
||||
cube_state.rotation = transform.rotation
|
||||
end
|
||||
|
||||
local rotation_speeds = {x = 0.5, y = 0.7}
|
||||
@@ -188,7 +251,7 @@ local function build_model(time)
|
||||
return math3d.multiply(y, x)
|
||||
end
|
||||
|
||||
local function create_cube(position, speed_scale, shader_key)
|
||||
local function create_rotating_cube(position, speed_scale, shader_key)
|
||||
local function compute_model_matrix(time)
|
||||
local base = build_model(time * speed_scale)
|
||||
local offset = math3d.translation(position[1], position[2], position[3])
|
||||
@@ -203,6 +266,26 @@ local function create_cube(position, speed_scale, shader_key)
|
||||
}
|
||||
end
|
||||
|
||||
local function create_physics_cube(shader_key)
|
||||
local function compute_model_matrix(time)
|
||||
sync_physics(time)
|
||||
local offset = math3d.translation(
|
||||
cube_state.position[1],
|
||||
cube_state.position[2],
|
||||
cube_state.position[3]
|
||||
)
|
||||
local rotation_matrix = quaternion_to_matrix(cube_state.rotation)
|
||||
return math3d.multiply(offset, rotation_matrix)
|
||||
end
|
||||
|
||||
return {
|
||||
vertices = cube_vertices,
|
||||
indices = cube_indices,
|
||||
compute_model_matrix = compute_model_matrix,
|
||||
shader_key = shader_key or "cube",
|
||||
}
|
||||
end
|
||||
|
||||
local function create_pyramid(position, shader_key)
|
||||
local function compute_model_matrix(time)
|
||||
local base = build_model(time * 0.6)
|
||||
@@ -220,9 +303,9 @@ end
|
||||
|
||||
function get_scene_objects()
|
||||
local objects = {
|
||||
create_cube({0.0, 0.0, 0.0}, 1.0, "cube"),
|
||||
create_cube({3.0, 0.0, 0.0}, 0.8, "cube"),
|
||||
create_cube({-3.0, 0.0, 0.0}, 1.2, "cube"),
|
||||
create_physics_cube("cube"),
|
||||
create_rotating_cube({3.0, 0.0, 0.0}, 0.8, "cube"),
|
||||
create_rotating_cube({-3.0, 0.0, 0.0}, 1.2, "cube"),
|
||||
create_pyramid({0.0, -0.5, -4.0}, "pyramid"),
|
||||
}
|
||||
if lua_debug then
|
||||
|
||||
@@ -4,18 +4,135 @@
|
||||
#include <assimp/material.h>
|
||||
#include <assimp/postprocess.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
namespace {
|
||||
|
||||
struct PhysicsBridge {
|
||||
struct BodyRecord {
|
||||
std::unique_ptr<btCollisionShape> shape;
|
||||
std::unique_ptr<btMotionState> motionState;
|
||||
std::unique_ptr<btRigidBody> body;
|
||||
};
|
||||
|
||||
PhysicsBridge();
|
||||
~PhysicsBridge();
|
||||
|
||||
bool addBoxRigidBody(const std::string& name,
|
||||
const btVector3& halfExtents,
|
||||
float mass,
|
||||
const btTransform& transform,
|
||||
std::string& error);
|
||||
int stepSimulation(float deltaTime);
|
||||
bool getRigidBodyTransform(const std::string& name,
|
||||
btTransform& outTransform,
|
||||
std::string& error) const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<btDefaultCollisionConfiguration> collisionConfig_;
|
||||
std::unique_ptr<btCollisionDispatcher> dispatcher_;
|
||||
std::unique_ptr<btBroadphaseInterface> broadphase_;
|
||||
std::unique_ptr<btSequentialImpulseConstraintSolver> solver_;
|
||||
std::unique_ptr<btDiscreteDynamicsWorld> world_;
|
||||
std::unordered_map<std::string, BodyRecord> bodies_;
|
||||
};
|
||||
|
||||
PhysicsBridge::PhysicsBridge()
|
||||
: 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())) {
|
||||
world_->setGravity(btVector3(0.0f, -9.81f, 0.0f));
|
||||
}
|
||||
|
||||
PhysicsBridge::~PhysicsBridge() {
|
||||
if (world_) {
|
||||
for (auto& [name, entry] : bodies_) {
|
||||
if (entry.body) {
|
||||
world_->removeRigidBody(entry.body.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsBridge::addBoxRigidBody(const std::string& name,
|
||||
const btVector3& halfExtents,
|
||||
float mass,
|
||||
const btTransform& transform,
|
||||
std::string& error) {
|
||||
if (name.empty()) {
|
||||
error = "Rigid body name must not be empty";
|
||||
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),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
int PhysicsBridge::stepSimulation(float deltaTime) {
|
||||
if (!world_) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(world_->stepSimulation(deltaTime, 10, 1.0f / 60.0f));
|
||||
}
|
||||
|
||||
bool PhysicsBridge::getRigidBodyTransform(const std::string& name,
|
||||
btTransform& outTransform,
|
||||
std::string& error) const {
|
||||
auto it = bodies_.find(name);
|
||||
if (it == bodies_.end()) {
|
||||
error = "Rigid body not found: " + name;
|
||||
return false;
|
||||
}
|
||||
if (!it->second.motionState) {
|
||||
error = "Rigid body motion state is missing";
|
||||
return false;
|
||||
}
|
||||
it->second.motionState->getWorldTransform(outTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct MeshPayload {
|
||||
std::vector<std::array<float, 3>> positions;
|
||||
std::vector<std::array<float, 3>> colors;
|
||||
@@ -153,6 +270,83 @@ int LuaLoadMeshFromFile(lua_State* L) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
int LuaPhysicsCreateBox(lua_State* L) {
|
||||
auto* script = static_cast<CubeScript*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
const char* name = luaL_checkstring(L, 1);
|
||||
if (!lua_istable(L, 2) || !lua_istable(L, 4) || !lua_istable(L, 5)) {
|
||||
luaL_error(L, "physics_create_box expects vector tables for half extents, origin, and rotation");
|
||||
}
|
||||
std::array<float, 3> halfExtents = ReadVector3(L, 2);
|
||||
float mass = static_cast<float>(luaL_checknumber(L, 3));
|
||||
std::array<float, 3> origin = ReadVector3(L, 4);
|
||||
std::array<float, 4> rotation = ReadQuaternion(L, 5);
|
||||
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
transform.setOrigin(btVector3(origin[0], origin[1], origin[2]));
|
||||
transform.setRotation(btQuaternion(rotation[0], rotation[1], rotation[2], rotation[3]));
|
||||
|
||||
std::string error;
|
||||
if (!script->GetPhysicsBridge().addBoxRigidBody(
|
||||
name,
|
||||
btVector3(halfExtents[0], halfExtents[1], halfExtents[2]),
|
||||
mass,
|
||||
transform,
|
||||
error)) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, error.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaPhysicsStepSimulation(lua_State* L) {
|
||||
auto* script = static_cast<CubeScript*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
float deltaTime = static_cast<float>(luaL_checknumber(L, 1));
|
||||
int steps = script->GetPhysicsBridge().stepSimulation(deltaTime);
|
||||
lua_pushinteger(L, steps);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaPhysicsGetTransform(lua_State* L) {
|
||||
auto* script = static_cast<CubeScript*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
const char* name = luaL_checkstring(L, 1);
|
||||
btTransform transform;
|
||||
std::string error;
|
||||
if (!script->GetPhysicsBridge().getRigidBodyTransform(name, transform, error)) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, error.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
lua_newtable(L);
|
||||
lua_newtable(L);
|
||||
const btVector3& origin = transform.getOrigin();
|
||||
lua_pushnumber(L, origin.x());
|
||||
lua_rawseti(L, -2, 1);
|
||||
lua_pushnumber(L, origin.y());
|
||||
lua_rawseti(L, -2, 2);
|
||||
lua_pushnumber(L, origin.z());
|
||||
lua_rawseti(L, -2, 3);
|
||||
lua_setfield(L, -2, "position");
|
||||
|
||||
lua_newtable(L);
|
||||
const btQuaternion& orientation = transform.getRotation();
|
||||
lua_pushnumber(L, orientation.x());
|
||||
lua_rawseti(L, -2, 1);
|
||||
lua_pushnumber(L, orientation.y());
|
||||
lua_rawseti(L, -2, 2);
|
||||
lua_pushnumber(L, orientation.z());
|
||||
lua_rawseti(L, -2, 3);
|
||||
lua_pushnumber(L, orientation.w());
|
||||
lua_rawseti(L, -2, 4);
|
||||
lua_setfield(L, -2, "rotation");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::array<float, 16> IdentityMatrix() {
|
||||
return {1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, 0.0f,
|
||||
@@ -163,7 +357,10 @@ std::array<float, 16> IdentityMatrix() {
|
||||
} // namespace
|
||||
|
||||
CubeScript::CubeScript(const std::filesystem::path& scriptPath, bool debugEnabled)
|
||||
: L_(luaL_newstate()), scriptDirectory_(scriptPath.parent_path()), debugEnabled_(debugEnabled) {
|
||||
: L_(luaL_newstate()),
|
||||
scriptDirectory_(scriptPath.parent_path()),
|
||||
debugEnabled_(debugEnabled),
|
||||
physicsBridge_(std::make_unique<PhysicsBridge>()) {
|
||||
if (!L_) {
|
||||
throw std::runtime_error("Failed to create Lua state");
|
||||
}
|
||||
@@ -171,6 +368,18 @@ CubeScript::CubeScript(const std::filesystem::path& scriptPath, bool debugEnable
|
||||
lua_pushlightuserdata(L_, this);
|
||||
lua_pushcclosure(L_, &LuaLoadMeshFromFile, 1);
|
||||
lua_setglobal(L_, "load_mesh_from_file");
|
||||
lua_pushlightuserdata(L_, this);
|
||||
lua_pushcclosure(L_, &LuaPhysicsCreateBox, 1);
|
||||
lua_setglobal(L_, "physics_create_box");
|
||||
lua_pushlightuserdata(L_, this);
|
||||
lua_pushcclosure(L_, &LuaPhysicsStepSimulation, 1);
|
||||
lua_setglobal(L_, "physics_step_simulation");
|
||||
lua_pushlightuserdata(L_, this);
|
||||
lua_pushcclosure(L_, &LuaPhysicsGetTransform, 1);
|
||||
lua_setglobal(L_, "physics_get_transform");
|
||||
lua_pushlightuserdata(L_, this);
|
||||
lua_pushcclosure(L_, &LuaLoadMeshFromFile, 1);
|
||||
lua_setglobal(L_, "load_mesh_from_file");
|
||||
lua_pushboolean(L_, debugEnabled_);
|
||||
lua_setglobal(L_, "lua_debug");
|
||||
auto scriptDir = scriptPath.parent_path();
|
||||
@@ -494,11 +703,34 @@ std::array<float, 16> CubeScript::ReadMatrix(lua_State* L, int index) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<float, 4> CubeScript::ReadQuaternion(lua_State* L, int index) {
|
||||
std::array<float, 4> result{};
|
||||
int absIndex = lua_absindex(L, index);
|
||||
size_t len = lua_rawlen(L, absIndex);
|
||||
if (len != 4) {
|
||||
throw std::runtime_error("Expected quaternion with 4 components");
|
||||
}
|
||||
for (size_t i = 1; i <= 4; ++i) {
|
||||
lua_rawgeti(L, absIndex, static_cast<int>(i));
|
||||
if (!lua_isnumber(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
throw std::runtime_error("Quaternion component is not a number");
|
||||
}
|
||||
result[i - 1] = static_cast<float>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string CubeScript::LuaErrorMessage(lua_State* L) {
|
||||
const char* message = lua_tostring(L, -1);
|
||||
return message ? message : "unknown lua error";
|
||||
}
|
||||
|
||||
PhysicsBridge& CubeScript::GetPhysicsBridge() {
|
||||
return *physicsBridge_;
|
||||
}
|
||||
|
||||
std::vector<CubeScript::GuiCommand> CubeScript::LoadGuiCommands() {
|
||||
std::vector<GuiCommand> commands;
|
||||
if (guiCommandsFnRef_ == LUA_REFNIL) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -13,6 +14,8 @@
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
struct PhysicsBridge;
|
||||
|
||||
struct GuiInputSnapshot {
|
||||
float mouseX = 0.0f;
|
||||
float mouseY = 0.0f;
|
||||
@@ -91,10 +94,12 @@ public:
|
||||
void UpdateGuiInput(const GuiInputSnapshot& input);
|
||||
bool HasGuiCommands() const;
|
||||
std::filesystem::path GetScriptDirectory() const;
|
||||
PhysicsBridge& GetPhysicsBridge();
|
||||
|
||||
private:
|
||||
static std::array<float, 3> ReadVector3(lua_State* L, int index);
|
||||
static std::array<float, 16> ReadMatrix(lua_State* L, int index);
|
||||
static std::array<float, 4> ReadQuaternion(lua_State* L, int index);
|
||||
static std::vector<core::Vertex> ReadVertexArray(lua_State* L, int index);
|
||||
static std::vector<uint16_t> ReadIndexArray(lua_State* L, int index);
|
||||
static std::string LuaErrorMessage(lua_State* L);
|
||||
@@ -108,6 +113,7 @@ private:
|
||||
int guiCommandsFnRef_ = LUA_REFNIL;
|
||||
std::filesystem::path scriptDirectory_;
|
||||
bool debugEnabled_ = false;
|
||||
std::unique_ptr<PhysicsBridge> physicsBridge_;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::script
|
||||
|
||||
Reference in New Issue
Block a user