mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 21:55:09 +00:00
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.
This commit is contained in:
@@ -21,6 +21,13 @@ local function resolve_number(value, fallback)
|
||||
return fallback
|
||||
end
|
||||
|
||||
local function resolve_boolean(value, fallback)
|
||||
if type(value) == "boolean" then
|
||||
return value
|
||||
end
|
||||
return fallback
|
||||
end
|
||||
|
||||
local function resolve_vec3(value, fallback)
|
||||
if type(value) == "table"
|
||||
and type(value[1]) == "number"
|
||||
@@ -81,6 +88,54 @@ local function clamp(value, min_value, max_value)
|
||||
return value
|
||||
end
|
||||
|
||||
local function transform_point(matrix, point)
|
||||
local x = point[1]
|
||||
local y = point[2]
|
||||
local z = point[3]
|
||||
return {
|
||||
matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13],
|
||||
matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14],
|
||||
matrix[3] * x + matrix[7] * y + matrix[11] * z + matrix[15],
|
||||
}
|
||||
end
|
||||
|
||||
local function compute_bounds(vertices, matrix)
|
||||
if type(vertices) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
local min_bounds = {math.huge, math.huge, math.huge}
|
||||
local max_bounds = {-math.huge, -math.huge, -math.huge}
|
||||
for i = 1, #vertices do
|
||||
local vertex = vertices[i]
|
||||
local position = vertex and (vertex.position or vertex)
|
||||
if type(position) == "table" then
|
||||
local world_pos = transform_point(matrix, position)
|
||||
if world_pos[1] < min_bounds[1] then
|
||||
min_bounds[1] = world_pos[1]
|
||||
end
|
||||
if world_pos[2] < min_bounds[2] then
|
||||
min_bounds[2] = world_pos[2]
|
||||
end
|
||||
if world_pos[3] < min_bounds[3] then
|
||||
min_bounds[3] = world_pos[3]
|
||||
end
|
||||
if world_pos[1] > max_bounds[1] then
|
||||
max_bounds[1] = world_pos[1]
|
||||
end
|
||||
if world_pos[2] > max_bounds[2] then
|
||||
max_bounds[2] = world_pos[2]
|
||||
end
|
||||
if world_pos[3] > max_bounds[3] then
|
||||
max_bounds[3] = world_pos[3]
|
||||
end
|
||||
end
|
||||
end
|
||||
if min_bounds[1] == math.huge then
|
||||
return nil
|
||||
end
|
||||
return {min = min_bounds, max = max_bounds}
|
||||
end
|
||||
|
||||
local function normalize(vec)
|
||||
local x, y, z = vec[1], vec[2], vec[3]
|
||||
local len = math.sqrt(x * x + y * y + z * z)
|
||||
@@ -117,6 +172,14 @@ local function is_action_down(action_name, fallback_key)
|
||||
return false
|
||||
end
|
||||
|
||||
local action_states = {}
|
||||
local function is_action_pressed(action_name, fallback_key)
|
||||
local is_down = is_action_down(action_name, fallback_key)
|
||||
local was_down = action_states[action_name]
|
||||
action_states[action_name] = is_down
|
||||
return is_down and not was_down
|
||||
end
|
||||
|
||||
local fallback_bindings = {
|
||||
move_forward = "W",
|
||||
move_back = "S",
|
||||
@@ -124,6 +187,8 @@ local fallback_bindings = {
|
||||
move_right = "D",
|
||||
fly_up = "Q",
|
||||
fly_down = "Z",
|
||||
jump = "Space",
|
||||
noclip_toggle = "N",
|
||||
}
|
||||
|
||||
local input_bindings = resolve_table(type(config) == "table" and config.input_bindings)
|
||||
@@ -164,9 +229,72 @@ log_debug("Loaded Quake 3 map %s from %s (%d vertices, %d indices)",
|
||||
#map_mesh.vertices,
|
||||
#map_mesh.indices)
|
||||
|
||||
local map_bounds = compute_bounds(map_mesh.vertices, map_model_matrix)
|
||||
if map_bounds then
|
||||
log_debug("Map bounds min=(%.2f, %.2f, %.2f) max=(%.2f, %.2f, %.2f)",
|
||||
map_bounds.min[1], map_bounds.min[2], map_bounds.min[3],
|
||||
map_bounds.max[1], map_bounds.max[2], map_bounds.max[3])
|
||||
end
|
||||
|
||||
local camera_config = resolve_table(quake3_config.camera)
|
||||
|
||||
local function compute_spawn_position(bounds)
|
||||
if not bounds then
|
||||
return {0.0, 48.0, 0.0}
|
||||
end
|
||||
local min_bounds = bounds.min
|
||||
local max_bounds = bounds.max
|
||||
local center = {
|
||||
(min_bounds[1] + max_bounds[1]) * 0.5,
|
||||
(min_bounds[2] + max_bounds[2]) * 0.5,
|
||||
(min_bounds[3] + max_bounds[3]) * 0.5,
|
||||
}
|
||||
local height = math.max(2.0, (max_bounds[2] - min_bounds[2]) * 0.1)
|
||||
local spawn_height = resolve_number(quake3_config.spawn_height, height)
|
||||
local spawn_offset = resolve_vec3(quake3_config.spawn_offset, {0.0, 0.0, 0.0})
|
||||
return {
|
||||
center[1] + spawn_offset[1],
|
||||
max_bounds[2] + spawn_height + spawn_offset[2],
|
||||
center[3] + spawn_offset[3],
|
||||
}
|
||||
end
|
||||
|
||||
local function is_position_far_from_bounds(position, bounds)
|
||||
if not bounds then
|
||||
return false
|
||||
end
|
||||
local min_bounds = bounds.min
|
||||
local max_bounds = bounds.max
|
||||
local extent_x = max_bounds[1] - min_bounds[1]
|
||||
local extent_y = max_bounds[2] - min_bounds[2]
|
||||
local extent_z = max_bounds[3] - min_bounds[3]
|
||||
local margin = math.max(extent_x, extent_y, extent_z) * 0.5
|
||||
if position[1] < min_bounds[1] - margin or position[1] > max_bounds[1] + margin then
|
||||
return true
|
||||
end
|
||||
if position[2] < min_bounds[2] - margin or position[2] > max_bounds[2] + margin then
|
||||
return true
|
||||
end
|
||||
if position[3] < min_bounds[3] - margin or position[3] > max_bounds[3] + margin then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local camera_position = resolve_vec3(camera_config.position, {0.0, 48.0, 0.0})
|
||||
local spawn_position = compute_spawn_position(map_bounds)
|
||||
local auto_spawn = quake3_config.auto_spawn
|
||||
if auto_spawn == nil then
|
||||
auto_spawn = true
|
||||
end
|
||||
if auto_spawn and is_position_far_from_bounds(camera_position, map_bounds) then
|
||||
camera_position = {spawn_position[1], spawn_position[2], spawn_position[3]}
|
||||
log_debug("Camera spawn adjusted to map bounds (%.2f, %.2f, %.2f)",
|
||||
camera_position[1], camera_position[2], camera_position[3])
|
||||
end
|
||||
|
||||
local camera = {
|
||||
position = resolve_vec3(camera_config.position, {0.0, 48.0, 0.0}),
|
||||
position = camera_position,
|
||||
yaw = math.rad(resolve_number(camera_config.yaw_degrees, 0.0)),
|
||||
pitch = math.rad(resolve_number(camera_config.pitch_degrees, 0.0)),
|
||||
fov = resolve_number(camera_config.fov, 0.85),
|
||||
@@ -181,10 +309,46 @@ local controls = {
|
||||
max_pitch = math.rad(85.0),
|
||||
}
|
||||
|
||||
local physics_config = resolve_table(quake3_config.physics)
|
||||
local physics_enabled = resolve_boolean(physics_config.enabled, true)
|
||||
local physics_available = type(physics_create_static_mesh) == "function"
|
||||
and type(physics_create_sphere) == "function"
|
||||
and type(physics_get_transform) == "function"
|
||||
and type(physics_set_linear_velocity) == "function"
|
||||
and type(physics_get_linear_velocity) == "function"
|
||||
and type(physics_step_simulation) == "function"
|
||||
|
||||
if physics_enabled and not physics_available then
|
||||
log_debug("Physics disabled: required bindings are unavailable")
|
||||
end
|
||||
|
||||
local physics_state = {
|
||||
enabled = physics_enabled and physics_available,
|
||||
ready = false,
|
||||
noclip = false,
|
||||
map_body_name = physics_config.map_body_name or "quake3_map",
|
||||
player_body_name = physics_config.player_body_name or "quake3_player",
|
||||
player_radius = resolve_number(physics_config.player_radius, resolve_number(quake3_config.player_radius, 0.6)),
|
||||
player_mass = resolve_number(physics_config.player_mass, resolve_number(quake3_config.player_mass, 1.0)),
|
||||
eye_height = resolve_number(physics_config.eye_height, resolve_number(quake3_config.eye_height, 0.6)),
|
||||
gravity = resolve_vec3(physics_config.gravity, {0.0, -9.8, 0.0}),
|
||||
jump_impulse = resolve_number(physics_config.jump_impulse, resolve_number(quake3_config.jump_impulse, 4.5)),
|
||||
jump_velocity_threshold = resolve_number(
|
||||
physics_config.jump_velocity_threshold,
|
||||
resolve_number(quake3_config.jump_velocity_threshold, 0.2)),
|
||||
max_sub_steps = resolve_number(physics_config.max_sub_steps, resolve_number(quake3_config.max_sub_steps, 8)),
|
||||
}
|
||||
|
||||
physics_state.spawn_position = {
|
||||
camera.position[1],
|
||||
camera.position[2] - physics_state.eye_height,
|
||||
camera.position[3],
|
||||
}
|
||||
|
||||
local last_frame_time = nil
|
||||
local world_up = {0.0, 1.0, 0.0}
|
||||
|
||||
local function update_camera(dt)
|
||||
local function update_camera_angles()
|
||||
local look_delta_x = 0.0
|
||||
local look_delta_y = 0.0
|
||||
if type(input_get_mouse_delta) == "function" then
|
||||
@@ -199,11 +363,9 @@ local function update_camera(dt)
|
||||
|
||||
camera.yaw = camera.yaw + look_delta_x
|
||||
camera.pitch = clamp(camera.pitch + look_delta_y, -controls.max_pitch, controls.max_pitch)
|
||||
end
|
||||
|
||||
local forward = forward_from_angles(camera.yaw, camera.pitch)
|
||||
local forward_flat = normalize({forward[1], 0.0, forward[3]})
|
||||
local right = normalize(cross(forward_flat, world_up))
|
||||
|
||||
local function resolve_move_input()
|
||||
local move_x = 0.0
|
||||
local move_z = 0.0
|
||||
local move_y = 0.0
|
||||
@@ -233,6 +395,11 @@ local function update_camera(dt)
|
||||
move_z = move_z / length
|
||||
end
|
||||
|
||||
return move_x, move_y, move_z
|
||||
end
|
||||
|
||||
local function update_free_fly(dt, forward_flat, right)
|
||||
local move_x, move_y, move_z = resolve_move_input()
|
||||
local planar_speed = controls.move_speed * dt
|
||||
camera.position[1] = camera.position[1] + (right[1] * move_x + forward_flat[1] * move_z) * planar_speed
|
||||
camera.position[2] = camera.position[2] + (right[2] * move_x + forward_flat[2] * move_z) * planar_speed
|
||||
@@ -243,6 +410,96 @@ local function update_camera(dt)
|
||||
end
|
||||
end
|
||||
|
||||
local function ensure_physics_setup()
|
||||
if not physics_state.enabled then
|
||||
return false
|
||||
end
|
||||
if physics_state.ready then
|
||||
return true
|
||||
end
|
||||
|
||||
if type(physics_clear) == "function" then
|
||||
physics_clear()
|
||||
end
|
||||
if type(physics_set_gravity) == "function" then
|
||||
local ok, err = physics_set_gravity(physics_state.gravity)
|
||||
if not ok then
|
||||
log_debug("Physics gravity failed: %s", err or "unknown")
|
||||
end
|
||||
end
|
||||
|
||||
local ok, err = physics_create_static_mesh(
|
||||
physics_state.map_body_name,
|
||||
map_mesh.vertices,
|
||||
map_mesh.indices,
|
||||
map_model_matrix)
|
||||
if not ok then
|
||||
log_debug("Physics map creation failed: %s", err or "unknown")
|
||||
return false
|
||||
end
|
||||
|
||||
local rotation = {0.0, 0.0, 0.0, 1.0}
|
||||
ok, err = physics_create_sphere(
|
||||
physics_state.player_body_name,
|
||||
physics_state.player_radius,
|
||||
physics_state.player_mass,
|
||||
physics_state.spawn_position,
|
||||
rotation)
|
||||
if not ok then
|
||||
log_debug("Physics player creation failed: %s", err or "unknown")
|
||||
return false
|
||||
end
|
||||
|
||||
if type(physics_set_linear_velocity) == "function" then
|
||||
physics_set_linear_velocity(physics_state.player_body_name, {0.0, 0.0, 0.0})
|
||||
end
|
||||
|
||||
physics_state.ready = true
|
||||
log_debug("Physics world initialized for Quake 3 map")
|
||||
return true
|
||||
end
|
||||
|
||||
local function apply_physics_controls(forward_flat, right)
|
||||
local move_x, _, move_z = resolve_move_input()
|
||||
local desired_x = (right[1] * move_x + forward_flat[1] * move_z) * controls.move_speed
|
||||
local desired_z = (right[3] * move_x + forward_flat[3] * move_z) * controls.move_speed
|
||||
|
||||
local current_velocity = {0.0, 0.0, 0.0}
|
||||
local velocity, velocity_err = physics_get_linear_velocity(physics_state.player_body_name)
|
||||
if type(velocity) == "table" then
|
||||
current_velocity = velocity
|
||||
elseif velocity_err then
|
||||
log_debug("Physics velocity query failed: %s", velocity_err)
|
||||
end
|
||||
|
||||
local desired_velocity = {desired_x, current_velocity[2] or 0.0, desired_z}
|
||||
local ok, err = physics_set_linear_velocity(physics_state.player_body_name, desired_velocity)
|
||||
if not ok then
|
||||
log_debug("Physics velocity failed: %s", err or "unknown")
|
||||
end
|
||||
|
||||
if type(physics_apply_impulse) == "function"
|
||||
and is_action_pressed("jump", get_binding("jump"))
|
||||
and math.abs(current_velocity[2] or 0.0) < physics_state.jump_velocity_threshold then
|
||||
local impulse = {0.0, physics_state.jump_impulse, 0.0}
|
||||
local jump_ok, jump_err = physics_apply_impulse(physics_state.player_body_name, impulse)
|
||||
if not jump_ok then
|
||||
log_debug("Physics jump failed: %s", jump_err or "unknown")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function sync_camera_from_physics()
|
||||
local transform, transform_err = physics_get_transform(physics_state.player_body_name)
|
||||
if type(transform) == "table" and type(transform.position) == "table" then
|
||||
camera.position[1] = transform.position[1]
|
||||
camera.position[2] = transform.position[2] + physics_state.eye_height
|
||||
camera.position[3] = transform.position[3]
|
||||
elseif transform_err then
|
||||
log_debug("Physics transform query failed: %s", transform_err)
|
||||
end
|
||||
end
|
||||
|
||||
local shader_variants_module = require("shader_variants")
|
||||
local shader_variants = shader_variants_module.build_cube_variants(config, log_debug)
|
||||
|
||||
@@ -276,9 +533,47 @@ local function build_view_state(aspect)
|
||||
dt = 0.1
|
||||
end
|
||||
|
||||
update_camera(dt)
|
||||
update_camera_angles()
|
||||
|
||||
local forward = forward_from_angles(camera.yaw, camera.pitch)
|
||||
local forward_flat = normalize({forward[1], 0.0, forward[3]})
|
||||
local right = normalize(cross(forward_flat, world_up))
|
||||
|
||||
local physics_ready = physics_state.enabled and ensure_physics_setup()
|
||||
if physics_ready then
|
||||
if is_action_pressed("noclip_toggle", get_binding("noclip_toggle")) then
|
||||
physics_state.noclip = not physics_state.noclip
|
||||
log_debug("Noclip toggled: %s", tostring(physics_state.noclip))
|
||||
if not physics_state.noclip and type(physics_set_transform) == "function" then
|
||||
local rotation = {0.0, 0.0, 0.0, 1.0}
|
||||
local reset_position = {
|
||||
camera.position[1],
|
||||
camera.position[2] - physics_state.eye_height,
|
||||
camera.position[3],
|
||||
}
|
||||
local ok, err = physics_set_transform(
|
||||
physics_state.player_body_name,
|
||||
reset_position,
|
||||
rotation)
|
||||
if not ok then
|
||||
log_debug("Physics reset failed: %s", err or "unknown")
|
||||
elseif type(physics_set_linear_velocity) == "function" then
|
||||
physics_set_linear_velocity(physics_state.player_body_name, {0.0, 0.0, 0.0})
|
||||
end
|
||||
end
|
||||
end
|
||||
if physics_state.noclip then
|
||||
update_free_fly(dt, forward_flat, right)
|
||||
else
|
||||
apply_physics_controls(forward_flat, right)
|
||||
if dt > 0.0 then
|
||||
physics_step_simulation(dt, physics_state.max_sub_steps)
|
||||
end
|
||||
sync_camera_from_physics()
|
||||
end
|
||||
else
|
||||
update_free_fly(dt, forward_flat, right)
|
||||
end
|
||||
local center = {
|
||||
camera.position[1] + forward[1],
|
||||
camera.position[2] + forward[2],
|
||||
|
||||
@@ -671,6 +671,8 @@ bgfx::ShaderHandle BgfxGraphicsBackend::CreateShader(const std::string& label,
|
||||
shaderc::Compiler compiler;
|
||||
shaderc::CompileOptions options;
|
||||
options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);
|
||||
options.SetAutoBindUniforms(true);
|
||||
options.SetAutoMapLocations(true);
|
||||
|
||||
shaderc_shader_kind kind = isVertex ? shaderc_vertex_shader : shaderc_fragment_shader;
|
||||
|
||||
@@ -686,7 +688,11 @@ bgfx::ShaderHandle BgfxGraphicsBackend::CreateShader(const std::string& label,
|
||||
std::vector<uint32_t> spirv(result.cbegin(), result.cend());
|
||||
const bgfx::Memory* mem = bgfx::copy(spirv.data(),
|
||||
static_cast<uint32_t>(spirv.size() * sizeof(uint32_t)));
|
||||
return bgfx::createShader(mem);
|
||||
bgfx::ShaderHandle handle = bgfx::createShader(mem);
|
||||
if (!bgfx::isValid(handle) && logger_) {
|
||||
logger_->Error("BgfxGraphicsBackend::CreateShader: Failed to create shader handle for " + label);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
bgfx::TextureHandle BgfxGraphicsBackend::LoadTextureFromFile(const std::string& path,
|
||||
|
||||
@@ -35,9 +35,7 @@ layout(location = 2) in vec2 inTexCoord;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
layout(location = 1) out vec2 fragTexCoord;
|
||||
|
||||
layout(set = 0, binding = 1) uniform GuiUniforms {
|
||||
mat4 u_modelViewProj;
|
||||
};
|
||||
uniform mat4 u_modelViewProj;
|
||||
|
||||
void main() {
|
||||
fragColor = inColor;
|
||||
@@ -54,7 +52,7 @@ layout(location = 1) in vec2 fragTexCoord;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
layout(binding = 0) uniform sampler2D s_tex;
|
||||
uniform sampler2D s_tex;
|
||||
|
||||
void main() {
|
||||
outColor = fragColor * texture(s_tex, fragTexCoord);
|
||||
@@ -105,11 +103,16 @@ void BgfxGuiService::PrepareFrame(const std::vector<GuiCommand>& commands,
|
||||
}
|
||||
|
||||
if (!bgfx::isValid(program_) || !bgfx::isValid(whiteTexture_)) {
|
||||
if (logger_) {
|
||||
if (logger_ && !loggedMissingResources_) {
|
||||
logger_->Warn("BgfxGuiService::PrepareFrame: GUI resources not initialized");
|
||||
}
|
||||
loggedMissingResources_ = true;
|
||||
return;
|
||||
}
|
||||
if (loggedMissingResources_ && logger_) {
|
||||
logger_->Trace("BgfxGuiService", "PrepareFrame", "GUI resources recovered");
|
||||
}
|
||||
loggedMissingResources_ = false;
|
||||
|
||||
ApplyGuiView(width, height);
|
||||
scissorStack_.clear();
|
||||
@@ -830,6 +833,8 @@ bgfx::ShaderHandle BgfxGuiService::CreateShader(const std::string& label,
|
||||
shaderc::Compiler compiler;
|
||||
shaderc::CompileOptions options;
|
||||
options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);
|
||||
options.SetAutoBindUniforms(true);
|
||||
options.SetAutoMapLocations(true);
|
||||
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGuiService", "CreateShader",
|
||||
@@ -848,7 +853,11 @@ bgfx::ShaderHandle BgfxGuiService::CreateShader(const std::string& label,
|
||||
std::vector<uint32_t> spirv(result.cbegin(), result.cend());
|
||||
const bgfx::Memory* mem = bgfx::copy(spirv.data(),
|
||||
static_cast<uint32_t>(spirv.size() * sizeof(uint32_t)));
|
||||
return bgfx::createShader(mem);
|
||||
bgfx::ShaderHandle handle = bgfx::createShader(mem);
|
||||
if (!bgfx::isValid(handle) && logger_) {
|
||||
logger_->Error("BgfxGuiService::CreateShader: Failed to create shader handle for " + label);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
void BgfxGuiService::PruneTextCache() {
|
||||
|
||||
@@ -159,6 +159,7 @@ private:
|
||||
uint32_t frameHeight_ = 0;
|
||||
uint16_t viewId_ = 1;
|
||||
bool initialized_ = false;
|
||||
bool loggedMissingResources_ = false;
|
||||
uint64_t frameIndex_ = 0;
|
||||
size_t maxTextCacheEntries_ = 256;
|
||||
size_t maxSvgCacheEntries_ = 64;
|
||||
|
||||
@@ -265,32 +265,47 @@ bool ReplaceFirstOccurrence(std::string& source, const std::string& before, cons
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ConvertIndividualOutputsToBlock(const std::string& source) {
|
||||
std::string ConvertIndividualOutputsToBlock(const std::string& source,
|
||||
const std::shared_ptr<ILogger>& logger) {
|
||||
// Find individual output declarations like:
|
||||
// layout (location = N) out vec3 varname;
|
||||
// And convert them to a VertexData block
|
||||
|
||||
std::vector<std::tuple<int, std::string, std::string>> outputs; // location, type, name
|
||||
const std::string layoutToken = "layout (location =";
|
||||
const std::string layoutTokenCompact = "layout(location =";
|
||||
size_t searchPos = 0;
|
||||
size_t firstOutputStart = std::string::npos;
|
||||
size_t lastOutputEnd = 0;
|
||||
|
||||
while (true) {
|
||||
size_t layoutPos = source.find("layout (location =", searchPos);
|
||||
if (layoutPos == std::string::npos) break;
|
||||
size_t layoutPos = source.find(layoutToken, searchPos);
|
||||
size_t compactPos = source.find(layoutTokenCompact, searchPos);
|
||||
size_t tokenLength = 0;
|
||||
if (compactPos != std::string::npos &&
|
||||
(layoutPos == std::string::npos || compactPos < layoutPos)) {
|
||||
layoutPos = compactPos;
|
||||
tokenLength = layoutTokenCompact.size();
|
||||
} else {
|
||||
tokenLength = layoutToken.size();
|
||||
}
|
||||
if (layoutPos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this line contains "out" (to confirm it's an output)
|
||||
size_t lineEnd = source.find('\n', layoutPos);
|
||||
if (lineEnd == std::string::npos) lineEnd = source.size();
|
||||
std::string line = source.substr(layoutPos, lineEnd - layoutPos);
|
||||
|
||||
if (line.find(" out ") == std::string::npos) {
|
||||
if (line.find(" out ") == std::string::npos ||
|
||||
line.find("VertexData") != std::string::npos) {
|
||||
searchPos = lineEnd;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract location number
|
||||
size_t locStart = layoutPos + 18; // after "layout (location ="
|
||||
size_t locStart = layoutPos + tokenLength; // after "layout (location ="
|
||||
while (locStart < source.size() && std::isspace(source[locStart])) ++locStart;
|
||||
size_t locEnd = locStart;
|
||||
while (locEnd < source.size() && std::isdigit(source[locEnd])) ++locEnd;
|
||||
@@ -344,10 +359,16 @@ std::string ConvertIndividualOutputsToBlock(const std::string& source) {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Build the VertexData block
|
||||
std::string block = "layout (location = 0) out VertexData\n{\n";
|
||||
std::sort(outputs.begin(), outputs.end(),
|
||||
[](const auto& left, const auto& right) {
|
||||
return std::get<0>(left) < std::get<0>(right);
|
||||
});
|
||||
|
||||
// Build the VertexData block while preserving explicit locations.
|
||||
std::string block = "out VertexData\n{\n";
|
||||
for (const auto& [loc, type, name] : outputs) {
|
||||
block += " " + type + " " + name + ";\n";
|
||||
block += " layout (location = " + std::to_string(loc) + ") " +
|
||||
type + " " + name + ";\n";
|
||||
}
|
||||
block += "} vd;\n\n";
|
||||
|
||||
@@ -356,36 +377,54 @@ std::string ConvertIndividualOutputsToBlock(const std::string& source) {
|
||||
result += block;
|
||||
result += source.substr(lastOutputEnd);
|
||||
|
||||
if (logger) {
|
||||
logger->Trace("MaterialXShaderGenerator", "Generate",
|
||||
"vertexOutputsConverted=" + std::to_string(outputs.size()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ConvertIndividualInputsToBlock(const std::string& source) {
|
||||
std::string ConvertIndividualInputsToBlock(const std::string& source,
|
||||
const std::shared_ptr<ILogger>& logger) {
|
||||
// Find individual input declarations like:
|
||||
// layout (location = N) in vec3 varname;
|
||||
// And convert them to a VertexData block
|
||||
|
||||
std::vector<std::tuple<int, std::string, std::string>> inputs; // location, type, name
|
||||
const std::string layoutToken = "layout (location =";
|
||||
const std::string layoutTokenCompact = "layout(location =";
|
||||
size_t searchPos = 0;
|
||||
size_t firstInputStart = std::string::npos;
|
||||
size_t lastInputEnd = 0;
|
||||
|
||||
while (true) {
|
||||
size_t layoutPos = source.find("layout (location =", searchPos);
|
||||
if (layoutPos == std::string::npos) break;
|
||||
size_t layoutPos = source.find(layoutToken, searchPos);
|
||||
size_t compactPos = source.find(layoutTokenCompact, searchPos);
|
||||
size_t tokenLength = 0;
|
||||
if (compactPos != std::string::npos &&
|
||||
(layoutPos == std::string::npos || compactPos < layoutPos)) {
|
||||
layoutPos = compactPos;
|
||||
tokenLength = layoutTokenCompact.size();
|
||||
} else {
|
||||
tokenLength = layoutToken.size();
|
||||
}
|
||||
if (layoutPos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this line contains "in" (to confirm it's an input)
|
||||
size_t lineEnd = source.find('\n', layoutPos);
|
||||
if (lineEnd == std::string::npos) lineEnd = source.size();
|
||||
std::string line = source.substr(layoutPos, lineEnd - layoutPos);
|
||||
|
||||
// Skip lines with "in vec3 i_" (vertex inputs)
|
||||
if (line.find(" in ") == std::string::npos || line.find(" in vec3 i_") != std::string::npos) {
|
||||
if (line.find(" in ") == std::string::npos ||
|
||||
line.find("VertexData") != std::string::npos) {
|
||||
searchPos = lineEnd;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract location number
|
||||
size_t locStart = layoutPos + 18; // after "layout (location ="
|
||||
size_t locStart = layoutPos + tokenLength; // after "layout (location ="
|
||||
while (locStart < source.size() && std::isspace(source[locStart])) ++locStart;
|
||||
size_t locEnd = locStart;
|
||||
while (locEnd < source.size() && std::isdigit(source[locEnd])) ++locEnd;
|
||||
@@ -439,10 +478,16 @@ std::string ConvertIndividualInputsToBlock(const std::string& source) {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Build the VertexData block
|
||||
std::string block = "layout (location = 0) in VertexData\n{\n";
|
||||
std::sort(inputs.begin(), inputs.end(),
|
||||
[](const auto& left, const auto& right) {
|
||||
return std::get<0>(left) < std::get<0>(right);
|
||||
});
|
||||
|
||||
// Build the VertexData block while preserving explicit locations.
|
||||
std::string block = "in VertexData\n{\n";
|
||||
for (const auto& [loc, type, name] : inputs) {
|
||||
block += " " + type + " " + name + ";\n";
|
||||
block += " layout (location = " + std::to_string(loc) + ") " +
|
||||
type + " " + name + ";\n";
|
||||
}
|
||||
block += "} vd;\n\n";
|
||||
|
||||
@@ -451,6 +496,10 @@ std::string ConvertIndividualInputsToBlock(const std::string& source) {
|
||||
result += block;
|
||||
result += source.substr(lastInputEnd);
|
||||
|
||||
if (logger) {
|
||||
logger->Trace("MaterialXShaderGenerator", "Generate",
|
||||
"fragmentInputsConverted=" + std::to_string(inputs.size()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -778,10 +827,10 @@ ShaderPaths MaterialXShaderGenerator::Generate(const MaterialXConfig& config,
|
||||
// MaterialX VkShaderGenerator incorrectly emits individual out variables instead of
|
||||
// a VertexData struct block, which causes compilation errors when the shader code
|
||||
// references vd.normalWorld etc. We convert them here as a workaround.
|
||||
paths.vertexSource = ConvertIndividualOutputsToBlock(paths.vertexSource);
|
||||
paths.vertexSource = ConvertIndividualOutputsToBlock(paths.vertexSource, logger_);
|
||||
|
||||
// Fix fragment shader inputs: convert individual layout inputs to VertexData block
|
||||
paths.fragmentSource = ConvertIndividualInputsToBlock(paths.fragmentSource);
|
||||
paths.fragmentSource = ConvertIndividualInputsToBlock(paths.fragmentSource, logger_);
|
||||
|
||||
// Ensure any remaining MaterialX tokens are substituted using the generator's map.
|
||||
const unsigned int airyIterations = ResolveAiryFresnelIterations(context, logger_);
|
||||
|
||||
@@ -105,6 +105,7 @@ bool PhysicsBridgeService::AddBoxRigidBody(const std::string& name,
|
||||
std::move(shape),
|
||||
std::move(motionState),
|
||||
std::move(body),
|
||||
nullptr,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -160,6 +161,84 @@ bool PhysicsBridgeService::AddSphereRigidBody(const std::string& name,
|
||||
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;
|
||||
}
|
||||
@@ -284,6 +363,23 @@ bool PhysicsBridgeService::SetLinearVelocity(const std::string& name,
|
||||
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",
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
|
||||
#include "../interfaces/i_physics_bridge_service.hpp"
|
||||
#include "../interfaces/i_logger.hpp"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class btVector3;
|
||||
class btTransform;
|
||||
class btCollisionShape;
|
||||
class btMotionState;
|
||||
class btRigidBody;
|
||||
class btTriangleMesh;
|
||||
class btDefaultCollisionConfiguration;
|
||||
class btCollisionDispatcher;
|
||||
class btBroadphaseInterface;
|
||||
@@ -40,6 +43,11 @@ public:
|
||||
float mass,
|
||||
const btTransform& transform,
|
||||
std::string& error) override;
|
||||
bool 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) override;
|
||||
bool RemoveRigidBody(const std::string& name,
|
||||
std::string& error) override;
|
||||
bool SetRigidBodyTransform(const std::string& name,
|
||||
@@ -54,6 +62,9 @@ public:
|
||||
bool SetLinearVelocity(const std::string& name,
|
||||
const btVector3& velocity,
|
||||
std::string& error) override;
|
||||
bool GetLinearVelocity(const std::string& name,
|
||||
btVector3& outVelocity,
|
||||
std::string& error) const override;
|
||||
int StepSimulation(float deltaTime, int maxSubSteps = 10) override;
|
||||
bool GetRigidBodyTransform(const std::string& name,
|
||||
btTransform& outTransform,
|
||||
@@ -66,6 +77,7 @@ private:
|
||||
std::unique_ptr<btCollisionShape> shape;
|
||||
std::unique_ptr<btMotionState> motionState;
|
||||
std::unique_ptr<btRigidBody> body;
|
||||
std::unique_ptr<btTriangleMesh> triangleMesh;
|
||||
};
|
||||
|
||||
BodyRecord* FindBodyRecord(const std::string& name, std::string& error);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
namespace mx = MaterialX;
|
||||
@@ -34,6 +35,18 @@ struct MaterialXSurfaceParameters {
|
||||
bool hasMetallic = false;
|
||||
};
|
||||
|
||||
std::array<float, 3> TransformPoint(const std::array<float, 16>& matrix,
|
||||
const std::array<float, 3>& point) {
|
||||
const float x = point[0];
|
||||
const float y = point[1];
|
||||
const float z = point[2];
|
||||
return {
|
||||
matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12],
|
||||
matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13],
|
||||
matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14],
|
||||
};
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveMaterialXPath(const std::filesystem::path& path,
|
||||
const std::filesystem::path& scriptDirectory) {
|
||||
if (path.empty()) {
|
||||
@@ -474,11 +487,13 @@ void ScriptEngineService::RegisterBindings(lua_State* L) {
|
||||
bind("load_mesh_from_pk3", &ScriptEngineService::LoadMeshFromArchive);
|
||||
bind("physics_create_box", &ScriptEngineService::PhysicsCreateBox);
|
||||
bind("physics_create_sphere", &ScriptEngineService::PhysicsCreateSphere);
|
||||
bind("physics_create_static_mesh", &ScriptEngineService::PhysicsCreateStaticMesh);
|
||||
bind("physics_remove_body", &ScriptEngineService::PhysicsRemoveBody);
|
||||
bind("physics_set_transform", &ScriptEngineService::PhysicsSetTransform);
|
||||
bind("physics_apply_force", &ScriptEngineService::PhysicsApplyForce);
|
||||
bind("physics_apply_impulse", &ScriptEngineService::PhysicsApplyImpulse);
|
||||
bind("physics_set_linear_velocity", &ScriptEngineService::PhysicsSetLinearVelocity);
|
||||
bind("physics_get_linear_velocity", &ScriptEngineService::PhysicsGetLinearVelocity);
|
||||
bind("physics_set_gravity", &ScriptEngineService::PhysicsSetGravity);
|
||||
bind("physics_step_simulation", &ScriptEngineService::PhysicsStepSimulation);
|
||||
bind("physics_get_transform", &ScriptEngineService::PhysicsGetTransform);
|
||||
@@ -660,6 +675,94 @@ int ScriptEngineService::PhysicsCreateSphere(lua_State* L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ScriptEngineService::PhysicsCreateStaticMesh(lua_State* L) {
|
||||
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
auto logger = context ? context->logger : nullptr;
|
||||
if (!context || !context->physicsBridgeService) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "Physics service not available");
|
||||
return 2;
|
||||
}
|
||||
|
||||
const char* name = luaL_checkstring(L, 1);
|
||||
if (logger) {
|
||||
logger->Trace("ScriptEngineService", "PhysicsCreateStaticMesh",
|
||||
"name=" + std::string(name));
|
||||
}
|
||||
|
||||
if (!lua_istable(L, 2) || !lua_istable(L, 3)) {
|
||||
luaL_error(L, "physics_create_static_mesh expects vertex and index tables");
|
||||
}
|
||||
|
||||
std::array<float, 16> transformMatrix = lua::IdentityMatrix();
|
||||
if (lua_gettop(L) >= 4) {
|
||||
if (!lua_istable(L, 4)) {
|
||||
luaL_error(L, "physics_create_static_mesh expects a transform matrix table");
|
||||
}
|
||||
transformMatrix = lua::ReadMatrix(L, 4);
|
||||
}
|
||||
|
||||
const int verticesIndex = lua_absindex(L, 2);
|
||||
const int indicesIndex = lua_absindex(L, 3);
|
||||
const size_t vertexCount = lua_rawlen(L, verticesIndex);
|
||||
const size_t indexCount = lua_rawlen(L, indicesIndex);
|
||||
|
||||
std::vector<std::array<float, 3>> vertices;
|
||||
vertices.reserve(vertexCount);
|
||||
|
||||
for (size_t vertexIndex = 1; vertexIndex <= vertexCount; ++vertexIndex) {
|
||||
lua_rawgeti(L, verticesIndex, static_cast<int>(vertexIndex));
|
||||
if (!lua_istable(L, -1)) {
|
||||
luaL_error(L, "physics_create_static_mesh vertices must be tables");
|
||||
}
|
||||
|
||||
std::array<float, 3> position;
|
||||
lua_getfield(L, -1, "position");
|
||||
if (lua_istable(L, -1)) {
|
||||
position = lua::ReadVector3(L, -1);
|
||||
lua_pop(L, 1);
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
position = lua::ReadVector3(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
vertices.push_back(TransformPoint(transformMatrix, position));
|
||||
}
|
||||
|
||||
std::vector<uint32_t> indices;
|
||||
indices.reserve(indexCount);
|
||||
for (size_t index = 1; index <= indexCount; ++index) {
|
||||
lua_rawgeti(L, indicesIndex, static_cast<int>(index));
|
||||
if (!lua_isinteger(L, -1) && !lua_isnumber(L, -1)) {
|
||||
luaL_error(L, "physics_create_static_mesh indices must be numbers");
|
||||
}
|
||||
lua_Integer rawIndex = lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (rawIndex <= 0) {
|
||||
luaL_error(L, "physics_create_static_mesh indices must be 1-based positive integers");
|
||||
}
|
||||
indices.push_back(static_cast<uint32_t>(rawIndex - 1));
|
||||
}
|
||||
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
std::string error;
|
||||
if (!context->physicsBridgeService->AddTriangleMeshRigidBody(
|
||||
name,
|
||||
vertices,
|
||||
indices,
|
||||
transform,
|
||||
error)) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, error.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ScriptEngineService::PhysicsRemoveBody(lua_State* L) {
|
||||
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
auto logger = context ? context->logger : nullptr;
|
||||
@@ -829,6 +932,39 @@ int ScriptEngineService::PhysicsSetLinearVelocity(lua_State* L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ScriptEngineService::PhysicsGetLinearVelocity(lua_State* L) {
|
||||
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
auto logger = context ? context->logger : nullptr;
|
||||
if (!context || !context->physicsBridgeService) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "Physics service not available");
|
||||
return 2;
|
||||
}
|
||||
|
||||
const char* name = luaL_checkstring(L, 1);
|
||||
if (logger) {
|
||||
logger->Trace("ScriptEngineService", "PhysicsGetLinearVelocity",
|
||||
"name=" + std::string(name));
|
||||
}
|
||||
|
||||
btVector3 velocity;
|
||||
std::string error;
|
||||
if (!context->physicsBridgeService->GetLinearVelocity(name, velocity, error)) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, error.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
lua_newtable(L);
|
||||
lua_pushnumber(L, velocity.getX());
|
||||
lua_rawseti(L, -2, 1);
|
||||
lua_pushnumber(L, velocity.getY());
|
||||
lua_rawseti(L, -2, 2);
|
||||
lua_pushnumber(L, velocity.getZ());
|
||||
lua_rawseti(L, -2, 3);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ScriptEngineService::PhysicsSetGravity(lua_State* L) {
|
||||
auto* context = static_cast<LuaBindingContext*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
auto logger = context ? context->logger : nullptr;
|
||||
|
||||
@@ -64,11 +64,13 @@ private:
|
||||
static int LoadMeshFromArchive(lua_State* L);
|
||||
static int PhysicsCreateBox(lua_State* L);
|
||||
static int PhysicsCreateSphere(lua_State* L);
|
||||
static int PhysicsCreateStaticMesh(lua_State* L);
|
||||
static int PhysicsRemoveBody(lua_State* L);
|
||||
static int PhysicsSetTransform(lua_State* L);
|
||||
static int PhysicsApplyForce(lua_State* L);
|
||||
static int PhysicsApplyImpulse(lua_State* L);
|
||||
static int PhysicsSetLinearVelocity(lua_State* L);
|
||||
static int PhysicsGetLinearVelocity(lua_State* L);
|
||||
static int PhysicsSetGravity(lua_State* L);
|
||||
static int PhysicsStepSimulation(lua_State* L);
|
||||
static int PhysicsGetTransform(lua_State* L);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class btVector3;
|
||||
class btTransform;
|
||||
@@ -28,6 +31,11 @@ public:
|
||||
float mass,
|
||||
const btTransform& transform,
|
||||
std::string& error) = 0;
|
||||
virtual bool 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) = 0;
|
||||
virtual bool RemoveRigidBody(const std::string& name,
|
||||
std::string& error) = 0;
|
||||
virtual bool SetRigidBodyTransform(const std::string& name,
|
||||
@@ -42,6 +50,9 @@ public:
|
||||
virtual bool SetLinearVelocity(const std::string& name,
|
||||
const btVector3& velocity,
|
||||
std::string& error) = 0;
|
||||
virtual bool GetLinearVelocity(const std::string& name,
|
||||
btVector3& outVelocity,
|
||||
std::string& error) const = 0;
|
||||
virtual int StepSimulation(float deltaTime, int maxSubSteps = 10) = 0;
|
||||
virtual bool GetRigidBodyTransform(const std::string& name,
|
||||
btTransform& outTransform,
|
||||
|
||||
Reference in New Issue
Block a user