mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-25 22:25:07 +00:00
362 lines
8.9 KiB
Lua
362 lines
8.9 KiB
Lua
local cube_mesh_info = {
|
|
path = "models/cube.stl",
|
|
loaded = false,
|
|
vertex_count = 0,
|
|
index_count = 0,
|
|
error = "load_mesh_from_file() not registered",
|
|
}
|
|
|
|
local cube_vertices = {}
|
|
local cube_indices = {}
|
|
|
|
local function load_cube_mesh()
|
|
if type(load_mesh_from_file) ~= "function" then
|
|
cube_mesh_info.error = "load_mesh_from_file() is unavailable"
|
|
return
|
|
end
|
|
|
|
local mesh, err = load_mesh_from_file(cube_mesh_info.path)
|
|
if not mesh then
|
|
cube_mesh_info.error = err or "load_mesh_from_file() failed"
|
|
return
|
|
end
|
|
|
|
if type(mesh.vertices) ~= "table" or type(mesh.indices) ~= "table" then
|
|
cube_mesh_info.error = "loader returned unexpected structure"
|
|
return
|
|
end
|
|
|
|
cube_vertices = mesh.vertices
|
|
cube_indices = mesh.indices
|
|
cube_mesh_info.loaded = true
|
|
cube_mesh_info.vertex_count = #mesh.vertices
|
|
cube_mesh_info.index_count = #mesh.indices
|
|
cube_mesh_info.error = nil
|
|
end
|
|
|
|
load_cube_mesh()
|
|
|
|
if not cube_mesh_info.loaded then
|
|
error("Unable to load cube mesh: " .. (cube_mesh_info.error or "unknown"))
|
|
end
|
|
|
|
local music_state = {
|
|
playing = false,
|
|
togglePressed = false,
|
|
}
|
|
|
|
local function start_music()
|
|
if type(audio_play_background) == "function" then
|
|
audio_play_background("modmusic.ogg", true)
|
|
music_state.playing = true
|
|
end
|
|
end
|
|
|
|
local function stop_music()
|
|
if type(audio_stop_background) == "function" then
|
|
audio_stop_background()
|
|
end
|
|
music_state.playing = false
|
|
end
|
|
|
|
local function toggle_music()
|
|
if music_state.playing then
|
|
stop_music()
|
|
else
|
|
start_music()
|
|
end
|
|
end
|
|
|
|
start_music()
|
|
|
|
local math3d = require("math3d")
|
|
local string_format = string.format
|
|
|
|
local InputState = {}
|
|
InputState.__index = InputState
|
|
|
|
function InputState:new()
|
|
local instance = {
|
|
mouseX = 0.0,
|
|
mouseY = 0.0,
|
|
mouseDeltaX = 0.0,
|
|
mouseDeltaY = 0.0,
|
|
mouseDown = false,
|
|
wheel = 0.0,
|
|
textInput = "",
|
|
keyStates = {},
|
|
lastMouseX = nil,
|
|
lastMouseY = nil,
|
|
gamepad = {
|
|
connected = false,
|
|
leftX = 0.0,
|
|
leftY = 0.0,
|
|
rightX = 0.0,
|
|
rightY = 0.0,
|
|
togglePressed = false,
|
|
},
|
|
}
|
|
return setmetatable(instance, InputState)
|
|
end
|
|
|
|
function InputState:resetTransient()
|
|
self.textInput = ""
|
|
self.wheel = 0.0
|
|
self.mouseDeltaX = 0.0
|
|
self.mouseDeltaY = 0.0
|
|
end
|
|
|
|
function InputState:setMouse(x, y, isDown)
|
|
if self.lastMouseX ~= nil and self.lastMouseY ~= nil then
|
|
self.mouseDeltaX = x - self.lastMouseX
|
|
self.mouseDeltaY = y - self.lastMouseY
|
|
end
|
|
self.lastMouseX = x
|
|
self.lastMouseY = y
|
|
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
|
|
|
|
function InputState:setGamepad(connected, leftX, leftY, rightX, rightY, togglePressed)
|
|
local pad = self.gamepad
|
|
pad.connected = connected and true or false
|
|
pad.leftX = leftX or 0.0
|
|
pad.leftY = leftY or 0.0
|
|
pad.rightX = rightX or 0.0
|
|
pad.rightY = rightY or 0.0
|
|
pad.togglePressed = togglePressed and true or false
|
|
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 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)
|
|
end
|
|
|
|
local shader_variants = {
|
|
default = {
|
|
vertex = "shaders/cube.vert.spv",
|
|
fragment = "shaders/cube.frag.spv",
|
|
},
|
|
}
|
|
|
|
local camera = {
|
|
position = {0.0, 0.0, 5.0},
|
|
yaw = -math.pi / 2.0,
|
|
pitch = 0.0,
|
|
fov = 0.78,
|
|
near = 0.1,
|
|
far = 50.0,
|
|
}
|
|
|
|
local controls = {
|
|
move_speed = 4.0,
|
|
mouse_sensitivity = 0.0025,
|
|
gamepad_look_speed = 2.5,
|
|
stick_deadzone = 0.2,
|
|
max_pitch = math.rad(85.0),
|
|
}
|
|
|
|
local last_frame_time = nil
|
|
local world_up = {0.0, 1.0, 0.0}
|
|
|
|
local function clamp(value, minValue, maxValue)
|
|
if value < minValue then
|
|
return minValue
|
|
end
|
|
if value > maxValue then
|
|
return maxValue
|
|
end
|
|
return value
|
|
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)
|
|
if len == 0.0 then
|
|
return {x, y, z}
|
|
end
|
|
return {x / len, y / len, z / len}
|
|
end
|
|
|
|
local function cross(a, b)
|
|
return {
|
|
a[2] * b[3] - a[3] * b[2],
|
|
a[3] * b[1] - a[1] * b[3],
|
|
a[1] * b[2] - a[2] * b[1],
|
|
}
|
|
end
|
|
|
|
local function apply_deadzone(value, deadzone)
|
|
local magnitude = math.abs(value)
|
|
if magnitude < deadzone then
|
|
return 0.0
|
|
end
|
|
local scaled = (magnitude - deadzone) / (1.0 - deadzone)
|
|
if value < 0.0 then
|
|
return -scaled
|
|
end
|
|
return scaled
|
|
end
|
|
|
|
local function forward_from_angles(yaw, pitch)
|
|
local cos_pitch = math.cos(pitch)
|
|
return {
|
|
math.cos(yaw) * cos_pitch,
|
|
math.sin(pitch),
|
|
math.sin(yaw) * cos_pitch,
|
|
}
|
|
end
|
|
|
|
local function update_camera(dt)
|
|
if not gui_input then
|
|
return
|
|
end
|
|
|
|
local look_delta_x = gui_input.mouseDeltaX * controls.mouse_sensitivity
|
|
local look_delta_y = -gui_input.mouseDeltaY * controls.mouse_sensitivity
|
|
|
|
local pad = gui_input.gamepad
|
|
if pad and pad.connected then
|
|
local stick_x = apply_deadzone(pad.rightX, controls.stick_deadzone)
|
|
local stick_y = apply_deadzone(pad.rightY, controls.stick_deadzone)
|
|
look_delta_x = look_delta_x + stick_x * controls.gamepad_look_speed * dt
|
|
look_delta_y = look_delta_y - stick_y * controls.gamepad_look_speed * dt
|
|
end
|
|
|
|
camera.yaw = camera.yaw + look_delta_x
|
|
camera.pitch = clamp(camera.pitch + look_delta_y, -controls.max_pitch, controls.max_pitch)
|
|
|
|
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 move_x = 0.0
|
|
local move_z = 0.0
|
|
|
|
if gui_input.keyStates["move_forward"] then
|
|
move_z = move_z + 1.0
|
|
end
|
|
if gui_input.keyStates["move_back"] then
|
|
move_z = move_z - 1.0
|
|
end
|
|
if gui_input.keyStates["move_right"] then
|
|
move_x = move_x + 1.0
|
|
end
|
|
if gui_input.keyStates["move_left"] then
|
|
move_x = move_x - 1.0
|
|
end
|
|
|
|
if pad and pad.connected then
|
|
move_x = move_x + apply_deadzone(pad.leftX, controls.stick_deadzone)
|
|
move_z = move_z - apply_deadzone(pad.leftY, controls.stick_deadzone)
|
|
end
|
|
|
|
local length = math.sqrt(move_x * move_x + move_z * move_z)
|
|
if length > 1.0 then
|
|
move_x = move_x / length
|
|
move_z = move_z / length
|
|
end
|
|
|
|
if length > 0.0 then
|
|
local speed = controls.move_speed * dt
|
|
camera.position[1] = camera.position[1] + (right[1] * move_x + forward_flat[1] * move_z) * speed
|
|
camera.position[2] = camera.position[2] + (right[2] * move_x + forward_flat[2] * move_z) * speed
|
|
camera.position[3] = camera.position[3] + (right[3] * move_x + forward_flat[3] * move_z) * speed
|
|
end
|
|
end
|
|
|
|
local function update_audio_controls()
|
|
if not gui_input then
|
|
return
|
|
end
|
|
|
|
local pad = gui_input.gamepad
|
|
local toggle_pressed = gui_input.keyStates["music_toggle"]
|
|
if pad and pad.connected and pad.togglePressed then
|
|
toggle_pressed = true
|
|
end
|
|
|
|
if toggle_pressed and not music_state.togglePressed then
|
|
toggle_music()
|
|
end
|
|
|
|
music_state.togglePressed = toggle_pressed and true or false
|
|
end
|
|
|
|
local rotation_speed = 0.9
|
|
|
|
local function create_spinning_cube()
|
|
local function compute_model_matrix(time)
|
|
return math3d.rotation_y(time * rotation_speed)
|
|
end
|
|
|
|
return {
|
|
vertices = cube_vertices,
|
|
indices = cube_indices,
|
|
compute_model_matrix = compute_model_matrix,
|
|
shader_key = "default",
|
|
}
|
|
end
|
|
|
|
function get_scene_objects()
|
|
return {
|
|
create_spinning_cube(),
|
|
}
|
|
end
|
|
|
|
function get_shader_paths()
|
|
return shader_variants
|
|
end
|
|
|
|
function get_view_projection(aspect)
|
|
local now = os.clock()
|
|
local dt = 0.0
|
|
if last_frame_time then
|
|
dt = now - last_frame_time
|
|
end
|
|
last_frame_time = now
|
|
if dt < 0.0 then
|
|
dt = 0.0
|
|
elseif dt > 0.1 then
|
|
dt = 0.1
|
|
end
|
|
|
|
update_camera(dt)
|
|
update_audio_controls()
|
|
|
|
local forward = forward_from_angles(camera.yaw, camera.pitch)
|
|
local center = {
|
|
camera.position[1] + forward[1],
|
|
camera.position[2] + forward[2],
|
|
camera.position[3] + forward[3],
|
|
}
|
|
|
|
local view = math3d.look_at(camera.position, center, world_up)
|
|
local projection = math3d.perspective(camera.fov, aspect, camera.near, camera.far)
|
|
return math3d.multiply(projection, view)
|
|
end
|