Files
SDL3CPlusPlus/scripts/cube_logic.lua
Richard Ward a9ad53ca8d add stlloader
2025-12-19 16:22:19 +00:00

318 lines
8.8 KiB
Lua

local pyramid_vertices = {
{ position = {0.0, 1.0, 0.0}, color = {1.0, 0.5, 0.0} },
{ position = {-1.0, -1.0, -1.0}, color = {0.0, 1.0, 1.0} },
{ position = {1.0, -1.0, -1.0}, color = {1.0, 0.0, 1.0} },
{ position = {1.0, -1.0, 1.0}, color = {1.0, 1.0, 0.0} },
{ position = {-1.0, -1.0, 1.0}, color = {0.0, 0.0, 1.0} },
}
local pyramid_indices = {
1, 2, 3,
1, 3, 4,
1, 4, 5,
1, 5, 2,
2, 3, 4,
4, 5, 2,
}
local fallback_cube_vertices = {
{ position = {-1.0, -1.0, -1.0}, color = {1.0, 0.0, 0.0} },
{ position = {1.0, -1.0, -1.0}, color = {0.0, 1.0, 0.0} },
{ position = {1.0, 1.0, -1.0}, color = {0.0, 0.0, 1.0} },
{ position = {-1.0, 1.0, -1.0}, color = {1.0, 1.0, 0.0} },
{ position = {-1.0, -1.0, 1.0}, color = {1.0, 0.0, 1.0} },
{ position = {1.0, -1.0, 1.0}, color = {0.0, 1.0, 1.0} },
{ position = {1.0, 1.0, 1.0}, color = {1.0, 1.0, 1.0} },
{ position = {-1.0, 1.0, 1.0}, color = {0.2, 0.2, 0.2} },
}
local fallback_cube_indices = {
1, 2, 3, 3, 4, 1, -- back
5, 6, 7, 7, 8, 5, -- front
1, 5, 8, 8, 4, 1, -- left
2, 6, 7, 7, 3, 2, -- right
4, 3, 7, 7, 8, 4, -- top
1, 2, 6, 6, 5, 1, -- bottom
}
local function resolve_script_directory()
local info = debug.getinfo(1, "S")
local source = info and info.source
if not source then
return "."
end
if source:sub(1, 1) == "@" then
local path = source:sub(2)
return path:match("^(.*[\\/])") or "."
end
return "."
end
local function combine_paths(first, ...)
local parts = {first, ...}
local combined = parts[1] or ""
for i = 2, #parts do
local piece = parts[i]
if piece and piece ~= "" then
local last = combined:sub(-1)
if last ~= "/" and last ~= "\\" then
combined = combined .. "/"
end
combined = combined .. piece
end
end
return combined
end
local function load_stl_mesh(path)
local file, err = io.open(path, "r")
if not file then
return nil, nil, err
end
local vertices = {}
local indices = {}
local color = {0.6, 0.8, 1.0}
local vertex_count = 0
for line in file:lines() do
if line:lower():match("^%s*vertex") then
local coords = {}
for token in line:gmatch("%S+") do
if token:lower() ~= "vertex" then
local value = tonumber(token)
if value then
coords[#coords + 1] = value
end
end
end
if #coords == 3 then
vertex_count = vertex_count + 1
vertices[vertex_count] = {
position = {coords[1], coords[2], coords[3]},
color = {color[1], color[2], color[3]},
}
indices[#indices + 1] = vertex_count
end
end
end
file:close()
if vertex_count == 0 then
return nil, nil, "STL did not include any vertex lines"
end
return vertices, indices, nil
end
local script_directory = resolve_script_directory()
local stl_cube_path = combine_paths(script_directory, "models", "cube.stl")
local stl_vertices, stl_indices, stl_error = load_stl_mesh(stl_cube_path)
local cube_vertices = fallback_cube_vertices
local cube_indices = fallback_cube_indices
local stl_debug_info = {
path = stl_cube_path,
loaded = false,
vertex_count = 0,
index_count = 0,
error = stl_error,
}
if stl_vertices then
cube_vertices = stl_vertices
cube_indices = stl_indices
stl_debug_info.loaded = true
stl_debug_info.vertex_count = #stl_vertices
stl_debug_info.index_count = #stl_indices
else
stl_debug_info.error = stl_error or "STL file not available"
end
local math3d = require("math3d")
local string_format = string.format
local table_concat = table.concat
local InputState = {}
InputState.__index = InputState
function InputState:new()
local instance = {
mouseX = 0.0,
mouseY = 0.0,
mouseDown = false,
wheel = 0.0,
textInput = "",
keyStates = {},
}
return setmetatable(instance, InputState)
end
function InputState:resetTransient()
self.textInput = ""
self.wheel = 0.0
end
function InputState:setMouse(x, y, isDown)
self.mouseX = x
self.mouseY = y
self.mouseDown = isDown
end
function InputState:setWheel(deltaY)
self.wheel = deltaY
end
function InputState:setKey(keyName, isDown)
self.keyStates[keyName] = isDown
end
function InputState:addTextInput(text)
if text then
self.textInput = self.textInput .. text
end
end
gui_input = InputState:new()
local function log_debug(fmt, ...)
if not lua_debug or not fmt then
return
end
print(string_format(fmt, ...))
end
if stl_debug_info.loaded then
log_debug("Loaded cube mesh from %s (%d vertices, %d indices)",
stl_debug_info.path, stl_debug_info.vertex_count, stl_debug_info.index_count)
else
log_debug("Failed to load cube STL (%s); using fallback cube",
stl_debug_info.error or "unknown")
end
local rotation_speeds = {x = 0.5, y = 0.7}
local shader_variants = {
default = {
vertex = "shaders/cube.vert.spv",
fragment = "shaders/cube.frag.spv",
},
cube = {
vertex = "shaders/cube.vert.spv",
fragment = "shaders/cube.frag.spv",
},
pyramid = {
vertex = "shaders/cube.vert.spv",
fragment = "shaders/cube.frag.spv",
},
}
local camera = {
eye = {2.0, 2.0, 2.5},
center = {0.0, 0.0, 0.0},
up = {0.0, 1.0, 0.0},
fov = 0.78,
near = 0.1,
far = 10.0,
}
local zoom_settings = {
min_distance = 2.0,
max_distance = 12.0,
speed = 0.25,
}
local function clamp_distance(value, minValue, maxValue)
if minValue and value < minValue then
return minValue
end
if maxValue and value > maxValue then
return maxValue
end
return value
end
local function update_camera_zoom(delta)
if delta == 0 then
return
end
local dx = camera.eye[1] - camera.center[1]
local dy = camera.eye[2] - camera.center[2]
local dz = camera.eye[3] - camera.center[3]
local distance = math.sqrt(dx * dx + dy * dy + dz * dz)
if distance == 0 then
return
end
local normalizedX = dx / distance
local normalizedY = dy / distance
local normalizedZ = dz / distance
local adjustment = -delta * zoom_settings.speed
local targetDistance = clamp_distance(distance + adjustment, zoom_settings.min_distance, zoom_settings.max_distance)
camera.eye[1] = camera.center[1] + normalizedX * targetDistance
camera.eye[2] = camera.center[2] + normalizedY * targetDistance
camera.eye[3] = camera.center[3] + normalizedZ * targetDistance
log_debug("zoom delta=%.2f -> distance=%.2f", delta, targetDistance)
end
local function build_model(time)
local y = math3d.rotation_y(time * rotation_speeds.y)
local x = math3d.rotation_x(time * rotation_speeds.x)
return math3d.multiply(y, x)
end
local function create_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])
return math3d.multiply(offset, base)
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)
local offset = math3d.translation(position[1], position[2], position[3])
return math3d.multiply(offset, base)
end
return {
vertices = pyramid_vertices,
indices = pyramid_indices,
compute_model_matrix = compute_model_matrix,
shader_key = shader_key or "pyramid",
}
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_pyramid({0.0, -0.5, -4.0}, "pyramid"),
}
if lua_debug then
local labels = {}
for idx, obj in ipairs(objects) do
table.insert(labels, string_format("[%d:%s]", idx, obj.shader_key))
end
log_debug("get_scene_objects -> %d entries: %s", #objects, table_concat(labels, ", "))
end
return objects
end
function get_shader_paths()
return shader_variants
end
function get_view_projection(aspect)
if gui_input then
update_camera_zoom(gui_input.wheel)
end
local view = math3d.look_at(camera.eye, camera.center, camera.up)
local projection = math3d.perspective(camera.fov, aspect, camera.near, camera.far)
return math3d.multiply(projection, view)
end