This commit is contained in:
Richard Ward
2025-12-18 19:13:31 +00:00
parent 7d83fc3ae9
commit 3ca9ce773d
8 changed files with 218 additions and 161 deletions

View File

@@ -35,6 +35,8 @@ local pyramid_indices = {
4, 5, 2,
}
local math3d = require("math3d")
local rotation_speeds = {x = 0.5, y = 0.7}
local shader_variants = {
@@ -52,62 +54,26 @@ local shader_variants = {
},
}
local function rotation_y(radians)
local c = math.cos(radians)
local s = math.sin(radians)
return {
c, 0.0, -s, 0.0,
0.0, 1.0, 0.0, 0.0,
s, 0.0, c, 0.0,
0.0, 0.0, 0.0, 1.0,
}
end
local function rotation_x(radians)
local c = math.cos(radians)
local s = math.sin(radians)
return {
1.0, 0.0, 0.0, 0.0,
0.0, c, s, 0.0,
0.0, -s, c, 0.0,
0.0, 0.0, 0.0, 1.0,
}
end
local function multiply_matrices(a, b)
local result = {}
for row = 1, 4 do
for col = 1, 4 do
local sum = 0.0
for idx = 1, 4 do
sum = sum + a[(idx - 1) * 4 + row] * b[(col - 1) * 4 + idx]
end
result[(col - 1) * 4 + row] = sum
end
end
return result
end
local function translation(x, y, z)
return {
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
x, y, z, 1.0,
}
end
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 function build_model(time)
local y = rotation_y(time * rotation_speeds.y)
local x = rotation_x(time * rotation_speeds.x)
return multiply_matrices(y, x)
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 = translation(position[1], position[2], position[3])
return multiply_matrices(offset, base)
local offset = math3d.translation(position[1], position[2], position[3])
return math3d.multiply(offset, base)
end
return {
@@ -121,8 +87,8 @@ end
local function create_pyramid(position, shader_key)
local function compute_model_matrix(time)
local base = build_model(time * 0.6)
local offset = translation(position[1], position[2], position[3])
return multiply_matrices(offset, base)
local offset = math3d.translation(position[1], position[2], position[3])
return math3d.multiply(offset, base)
end
return {
@@ -145,3 +111,9 @@ end
function get_shader_paths()
return shader_variants
end
function get_view_projection(aspect)
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

119
scripts/math3d.lua Normal file
View File

@@ -0,0 +1,119 @@
local math3d = {}
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 dot(a, b)
return a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
end
local function identity_matrix()
return {
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
}
end
function math3d.identity()
return identity_matrix()
end
function math3d.multiply(a, b)
local result = {}
for row = 1, 4 do
for col = 1, 4 do
local sum = 0.0
for idx = 1, 4 do
sum = sum + a[(idx - 1) * 4 + row] * b[(col - 1) * 4 + idx]
end
result[(col - 1) * 4 + row] = sum
end
end
return result
end
function math3d.translation(x, y, z)
return {
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
x, y, z, 1.0,
}
end
function math3d.rotation_x(radians)
local c = math.cos(radians)
local s = math.sin(radians)
return {
1.0, 0.0, 0.0, 0.0,
0.0, c, s, 0.0,
0.0, -s, c, 0.0,
0.0, 0.0, 0.0, 1.0,
}
end
function math3d.rotation_y(radians)
local c = math.cos(radians)
local s = math.sin(radians)
return {
c, 0.0, -s, 0.0,
0.0, 1.0, 0.0, 0.0,
s, 0.0, c, 0.0,
0.0, 0.0, 0.0, 1.0,
}
end
function math3d.look_at(eye, center, up)
local f = normalize({center[1] - eye[1], center[2] - eye[2], center[3] - eye[3]})
local s = normalize(cross(f, up))
local u = cross(s, f)
local result = identity_matrix()
result[1] = s[1]
result[2] = u[1]
result[3] = -f[1]
result[5] = s[2]
result[6] = u[2]
result[7] = -f[2]
result[9] = s[3]
result[10] = u[3]
result[11] = -f[3]
result[13] = -dot(s, eye)
result[14] = -dot(u, eye)
result[15] = dot(f, eye)
return result
end
function math3d.perspective(fov, aspect, zNear, zFar)
local tanHalf = math.tan(fov / 2.0)
local result = {
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
}
result[1] = 1.0 / (aspect * tanHalf)
result[6] = -1.0 / tanHalf
result[11] = zFar / (zNear - zFar)
result[12] = -1.0
result[15] = (zNear * zFar) / (zNear - zFar)
return result
end
return math3d

View File

@@ -705,11 +705,8 @@ void VulkanCubeApp::DrawFrame(float time) {
throw std::runtime_error("Failed to acquire swap chain image");
}
auto view = core::LookAt({2.0f, 2.0f, 2.5f}, {0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f});
auto projection = core::Perspective(0.78f, static_cast<float>(swapChainExtent_.width) /
static_cast<float>(swapChainExtent_.height),
0.1f, 10.0f);
auto viewProj = core::MultiplyMatrix(projection, view);
float aspect = static_cast<float>(swapChainExtent_.width) / static_cast<float>(swapChainExtent_.height);
auto viewProj = cubeScript_.GetViewProjectionMatrix(aspect);
vkResetCommandBuffer(commandBuffers_[imageIndex], 0);
RecordCommandBuffer(commandBuffers_[imageIndex], imageIndex, time, viewProj);

View File

@@ -16,7 +16,7 @@
#include <SDL3/SDL_vulkan.h>
#include <vulkan/vulkan.h>
#include "core/math.hpp"
#include "core/vertex.hpp"
#include "script/cube_script.hpp"
namespace sdl3cpp::app {

View File

@@ -1,102 +0,0 @@
#ifndef SDL3CPP_CORE_MATH_HPP
#define SDL3CPP_CORE_MATH_HPP
#include <array>
#include <cmath>
#include <cstdint>
namespace sdl3cpp::core {
struct Vec3 {
float x;
float y;
float z;
};
struct Vertex {
std::array<float, 3> position;
std::array<float, 3> color;
};
struct PushConstants {
std::array<float, 16> model;
std::array<float, 16> viewProj;
};
static_assert(sizeof(PushConstants) == sizeof(float) * 32, "push constant size mismatch");
inline std::array<float, 16> MultiplyMatrix(const std::array<float, 16>& a,
const std::array<float, 16>& b) {
std::array<float, 16> result{};
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
float sum = 0.0f;
for (int idx = 0; idx < 4; ++idx) {
sum += a[idx * 4 + row] * b[col * 4 + idx];
}
result[col * 4 + row] = sum;
}
}
return result;
}
inline std::array<float, 16> IdentityMatrix() {
return {1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f};
}
inline Vec3 Normalize(Vec3 v) {
float len = std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
if (len == 0.0f) {
return v;
}
return {v.x / len, v.y / len, v.z / len};
}
inline Vec3 Cross(const Vec3& a, const Vec3& b) {
return {a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x};
}
inline float Dot(const Vec3& a, const Vec3& b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
inline std::array<float, 16> LookAt(const Vec3& eye, const Vec3& center, const Vec3& up) {
Vec3 f = Normalize({center.x - eye.x, center.y - eye.y, center.z - eye.z});
Vec3 s = Normalize(Cross(f, up));
Vec3 u = Cross(s, f);
std::array<float, 16> result = IdentityMatrix();
result[0] = s.x;
result[1] = u.x;
result[2] = -f.x;
result[4] = s.y;
result[5] = u.y;
result[6] = -f.y;
result[8] = s.z;
result[9] = u.z;
result[10] = -f.z;
result[12] = -Dot(s, eye);
result[13] = -Dot(u, eye);
result[14] = Dot(f, eye);
return result;
}
inline std::array<float, 16> Perspective(float fovRadians, float aspect, float zNear, float zFar) {
float tanHalf = std::tan(fovRadians / 2.0f);
std::array<float, 16> result{};
result[0] = 1.0f / (aspect * tanHalf);
result[5] = -1.0f / tanHalf;
result[10] = zFar / (zNear - zFar);
result[11] = -1.0f;
result[14] = (zNear * zFar) / (zNear - zFar);
return result;
}
} // namespace sdl3cpp::core
#endif // SDL3CPP_CORE_MATH_HPP

22
src/core/vertex.hpp Normal file
View File

@@ -0,0 +1,22 @@
#ifndef SDL3CPP_CORE_VERTEX_HPP
#define SDL3CPP_CORE_VERTEX_HPP
#include <array>
namespace sdl3cpp::core {
struct Vertex {
std::array<float, 3> position;
std::array<float, 3> color;
};
struct PushConstants {
std::array<float, 16> model;
std::array<float, 16> viewProj;
};
static_assert(sizeof(PushConstants) == sizeof(float) * 32, "push constant size mismatch");
} // namespace sdl3cpp::core
#endif // SDL3CPP_CORE_VERTEX_HPP

View File

@@ -6,11 +6,38 @@
namespace sdl3cpp::script {
namespace {
std::array<float, 16> IdentityMatrix() {
return {1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f};
}
} // namespace
CubeScript::CubeScript(const std::filesystem::path& scriptPath) : L_(luaL_newstate()) {
if (!L_) {
throw std::runtime_error("Failed to create Lua state");
}
luaL_openlibs(L_);
auto scriptDir = scriptPath.parent_path();
if (!scriptDir.empty()) {
lua_getglobal(L_, "package");
if (lua_istable(L_, -1)) {
lua_getfield(L_, -1, "path");
const char* currentPath = lua_tostring(L_, -1);
std::string newPath = scriptDir.string() + "/?.lua;";
if (currentPath) {
newPath += currentPath;
}
lua_pop(L_, 1);
lua_pushstring(L_, newPath.c_str());
lua_setfield(L_, -2, "path");
}
lua_pop(L_, 1);
}
if (luaL_dofile(L_, scriptPath.string().c_str()) != LUA_OK) {
std::string message = LuaErrorMessage(L_);
lua_pop(L_, 1);
@@ -97,7 +124,7 @@ std::array<float, 16> CubeScript::ComputeModelMatrix(int functionRef, float time
lua_getglobal(L_, "compute_model_matrix");
if (!lua_isfunction(L_, -1)) {
lua_pop(L_, 1);
return core::IdentityMatrix();
return IdentityMatrix();
}
} else {
lua_rawgeti(L_, LUA_REGISTRYINDEX, functionRef);
@@ -119,6 +146,27 @@ std::array<float, 16> CubeScript::ComputeModelMatrix(int functionRef, float time
return matrix;
}
std::array<float, 16> CubeScript::GetViewProjectionMatrix(float aspect) {
lua_getglobal(L_, "get_view_projection");
if (!lua_isfunction(L_, -1)) {
lua_pop(L_, 1);
throw std::runtime_error("Lua function 'get_view_projection' is missing");
}
lua_pushnumber(L_, aspect);
if (lua_pcall(L_, 1, 1, 0) != LUA_OK) {
std::string message = LuaErrorMessage(L_);
lua_pop(L_, 1);
throw std::runtime_error("Lua get_view_projection failed: " + message);
}
if (!lua_istable(L_, -1)) {
lua_pop(L_, 1);
throw std::runtime_error("'get_view_projection' did not return a table");
}
std::array<float, 16> matrix = ReadMatrix(L_, -1);
lua_pop(L_, 1);
return matrix;
}
std::vector<core::Vertex> CubeScript::ReadVertexArray(lua_State* L, int index) {
int absIndex = lua_absindex(L, index);
if (!lua_istable(L, absIndex)) {

View File

@@ -9,7 +9,7 @@
#include <lua.hpp>
#include "core/math.hpp"
#include "core/vertex.hpp"
namespace sdl3cpp::script {
@@ -32,6 +32,7 @@ public:
std::vector<SceneObject> LoadSceneObjects();
std::array<float, 16> ComputeModelMatrix(int functionRef, float time);
std::array<float, 16> GetViewProjectionMatrix(float aspect);
std::unordered_map<std::string, ShaderPaths> LoadShaderPathsMap();
private: