/*
* Tuxánci 2 - A first person shooter
* Copyright (C) 2007-2011 Tuxánci Development Team
* Copyright (C) 2025-2026 Connor Thomson
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include "obj_model.h"
#include "ta_sdl.h"
#include "files.h"
typedef struct ta_obj_uniforms {
float mvp[16];
float model[16];
} ta_obj_uniforms;
static SDL_GPUShader *ta_obj_shader(SDL_GPUDevice *device, const void *code, size_t size, SDL_GPUShaderStage stage) {
SDL_GPUShaderCreateInfo info = {
.code = code,
.code_size = size,
.entrypoint = "main",
.format = SDL_GPU_SHADERFORMAT_SPIRV,
.stage = stage,
.num_uniform_buffers = stage == SDL_GPU_SHADERSTAGE_VERTEX ? 1 : 0,
};
return ta_sdl_create_gpu_shader(device, &info);
}
static void ta_obj_identity(float *matrix) {
for (int index = 0; index < 16; index++) matrix[index] = index % 5 == 0 ? 1.0f : 0.0f;
}
static void ta_obj_multiply(float *result, const float *left, const float *right) {
float product[16];
for (int column = 0; column < 4; column++) for (int row = 0; row < 4; row++) {
product[column * 4 + row] = 0.0f;
for (int inner = 0; inner < 4; inner++) product[column * 4 + row] += left[inner * 4 + row] * right[column * 4 + inner];
}
memcpy(result, product, sizeof(product));
}
bool ta_obj_model_init(ta_obj_model *model, SDL_GPUDevice *device, const unsigned char *data, size_t size, SDL_GPUTextureFormat color_format, SDL_GPUTextureFormat depth_format, SDL_GPUSampleCount sample_count) {
memset(model, 0, sizeof(*model));
if (!ta_obj_parse(data, size, &model->mesh) || model->mesh.vertex_count == 0) return false;
float minimum[3];
float maximum[3];
memcpy(minimum, model->mesh.vertices[0].position, sizeof(minimum));
memcpy(maximum, minimum, sizeof(maximum));
for (size_t index = 1; index < model->mesh.vertex_count; index++) for (int axis = 0; axis < 3; axis++) {
if (model->mesh.vertices[index].position[axis] < minimum[axis]) minimum[axis] = model->mesh.vertices[index].position[axis];
if (model->mesh.vertices[index].position[axis] > maximum[axis]) maximum[axis] = model->mesh.vertices[index].position[axis];
}
float center[3] = {(minimum[0] + maximum[0]) * 0.5f, (minimum[1] + maximum[1]) * 0.5f, (minimum[2] + maximum[2]) * 0.5f};
float extent = maximum[0] - minimum[0];
for (int axis = 1; axis < 3; axis++) if (maximum[axis] - minimum[axis] > extent) extent = maximum[axis] - minimum[axis];
for (size_t index = 0; index < model->mesh.vertex_count; index++) for (int axis = 0; axis < 3; axis++) model->mesh.vertices[index].position[axis] = (model->mesh.vertices[index].position[axis] - center[axis]) * (2.2f / extent);
SDL_GPUBufferCreateInfo buffer_info = {.usage = SDL_GPU_BUFFERUSAGE_VERTEX, .size = (Uint32)(model->mesh.vertex_count * sizeof(*model->mesh.vertices))};
model->vertex_buffer = SDL_CreateGPUBuffer(device, &buffer_info);
if (!model->vertex_buffer) {
ta_obj_free(&model->mesh);
return false;
}
model->vertex_count = (Uint32)model->mesh.vertex_count;
SDL_GPUShader *vertex_shader = ta_obj_shader(device, file_obj_vert_slang_spv_start, file_obj_vert_slang_spv_size, SDL_GPU_SHADERSTAGE_VERTEX);
SDL_GPUShader *fragment_shader = ta_obj_shader(device, file_obj_frag_slang_spv_start, file_obj_frag_slang_spv_size, SDL_GPU_SHADERSTAGE_FRAGMENT);
SDL_GPUVertexBufferDescription vertex_buffer = {.slot = 0, .pitch = sizeof(ta_obj_vertex), .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX};
SDL_GPUVertexAttribute attributes[2] = {
{.location = 0, .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, .offset = 0},
{.location = 1, .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, .offset = sizeof(float) * 3},
};
SDL_GPUColorTargetDescription color_target = {.format = color_format};
SDL_GPUGraphicsPipelineCreateInfo pipeline_info = {
.vertex_shader = vertex_shader,
.fragment_shader = fragment_shader,
.vertex_input_state = {.vertex_buffer_descriptions = &vertex_buffer, .num_vertex_buffers = 1, .vertex_attributes = attributes, .num_vertex_attributes = 2},
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
.rasterizer_state = {.cull_mode = SDL_GPU_CULLMODE_NONE},
.depth_stencil_state = {.compare_op = SDL_GPU_COMPAREOP_LESS, .enable_depth_test = true, .enable_depth_write = true},
.multisample_state = {.sample_count = sample_count},
.target_info = {.num_color_targets = 1, .color_target_descriptions = &color_target, .depth_stencil_format = depth_format, .has_depth_stencil_target = true},
};
model->pipeline = ta_sdl_create_gpu_graphics_pipeline(device, &pipeline_info);
SDL_ReleaseGPUShader(device, vertex_shader);
SDL_ReleaseGPUShader(device, fragment_shader);
return model->pipeline != NULL;
}
void ta_obj_model_upload(ta_obj_model *model, SDL_GPUCommandBuffer *command_buffer, SDL_GPUDevice *device) {
if (model->uploaded || !model->vertex_buffer) return;
Uint32 size = (Uint32)(model->mesh.vertex_count * sizeof(*model->mesh.vertices));
SDL_GPUTransferBufferCreateInfo transfer_info = {.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = size};
SDL_GPUTransferBuffer *transfer_buffer = ta_sdl_create_gpu_transfer_buffer(device, &transfer_info);
void *mapped = ta_sdl_map_gpu_transfer_buffer(device, transfer_buffer, false);
memcpy(mapped, model->mesh.vertices, size);
SDL_UnmapGPUTransferBuffer(device, transfer_buffer);
SDL_GPUCopyPass *copy_pass = ta_sdl_begin_gpu_copy_pass(command_buffer);
SDL_GPUTransferBufferLocation source = {.transfer_buffer = transfer_buffer};
SDL_GPUBufferRegion destination = {.buffer = model->vertex_buffer, .size = size};
SDL_UploadToGPUBuffer(copy_pass, &source, &destination, false);
SDL_EndGPUCopyPass(copy_pass);
SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
ta_obj_free(&model->mesh);
model->uploaded = true;
}
void ta_obj_model_render(const ta_obj_model *model, SDL_GPUCommandBuffer *command_buffer, SDL_GPURenderPass *render_pass, int window_width, int window_height) {
if (!model->pipeline || !model->vertex_buffer || !model->uploaded) return;
ta_obj_uniforms uniforms;
float view[16];
float projection[16] = {0};
float view_model[16];
float rotation_x[16];
float rotation_y[16];
float rotation_z[16];
float rotation_xy[16];
float cosine = cosf(model->rotation[0]);
float sine = sinf(model->rotation[0]);
ta_obj_identity(rotation_x);
rotation_x[5] = cosine;
rotation_x[6] = sine;
rotation_x[9] = -sine;
rotation_x[10] = cosine;
cosine = cosf(model->rotation[1]);
sine = sinf(model->rotation[1]);
ta_obj_identity(rotation_y);
rotation_y[0] = cosine;
rotation_y[2] = -sine;
rotation_y[8] = sine;
rotation_y[10] = cosine;
cosine = cosf(model->rotation[2]);
sine = sinf(model->rotation[2]);
ta_obj_identity(rotation_z);
rotation_z[0] = cosine;
rotation_z[1] = sine;
rotation_z[4] = -sine;
rotation_z[5] = cosine;
ta_obj_multiply(rotation_xy, rotation_y, rotation_x);
ta_obj_multiply(uniforms.model, rotation_z, rotation_xy);
uniforms.model[12] = model->position[0];
uniforms.model[13] = model->position[1];
uniforms.model[14] = model->position[2];
float aspect = (float)window_width / (float)window_height;
float focal_length = 1.0f / tanf(22.5f * 0.01745329252f);
ta_obj_identity(view);
view[14] = -5.0f;
projection[0] = focal_length / aspect;
projection[5] = focal_length;
projection[10] = -101.0f / 99.0f;
projection[11] = -1.0f;
projection[14] = -200.0f / 99.0f;
ta_obj_multiply(view_model, view, uniforms.model);
ta_obj_multiply(uniforms.mvp, projection, view_model);
SDL_PushGPUVertexUniformData(command_buffer, 0, &uniforms, sizeof(uniforms));
SDL_BindGPUGraphicsPipeline(render_pass, model->pipeline);
SDL_GPUBufferBinding binding = {.buffer = model->vertex_buffer};
SDL_BindGPUVertexBuffers(render_pass, 0, &binding, 1);
SDL_DrawGPUPrimitives(render_pass, model->vertex_count, 1, 0, 0);
}
void ta_obj_model_destroy(ta_obj_model *model, SDL_GPUDevice *device) {
SDL_ReleaseGPUGraphicsPipeline(device, model->pipeline);
SDL_ReleaseGPUBuffer(device, model->vertex_buffer);
ta_obj_free(&model->mesh);
memset(model, 0, sizeof(*model));
}