/*
* Tuxánci 2 - A first person shooter
* 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 "shadow.h"
#include "gpu.h"
#include "ta_math.h"
#define TA_SHADOW_EXTENT 16.0f
#define TA_SHADOW_DISTANCE 30.0f
#define TA_SHADOW_NEAR 1.0f
#define TA_SHADOW_FAR 60.0f
SDL_GPUTexture *ta_shadow_texture;
SDL_GPUSampler *ta_shadow_sampler;
float ta_shadow_light_direction[TA_SHADOW_DIMENSION] = { -0.45f, 0.75f, 0.55f };
float ta_shadow_normal_offset = 0.03f;
float ta_shadow_minimum_bias = 0.0005f;
float ta_shadow_maximum_bias = 0.003f;
float ta_shadow_strength = 1.0f;
static float ta_shadow_dot(const float *left, const float *right) {
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
}
static void ta_shadow_cross(float *result, const float *left, const float *right) {
result[0] = left[1] * right[2] - left[2] * right[1];
result[1] = left[2] * right[0] - left[0] * right[2];
result[2] = left[0] * right[1] - left[1] * right[0];
}
static void ta_shadow_normalize(float *vector) {
float length = sqrtf(ta_shadow_dot(vector, vector));
if (length > 0.0f) {
vector[0] /= length;
vector[1] /= length;
vector[2] /= length;
}
}
void ta_shadow_init(void) {
SDL_GPUTextureCreateInfo texture_info = {
.type = SDL_GPU_TEXTURETYPE_2D,
.format = TA_SHADOW_FORMAT,
.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER,
.width = TA_SHADOW_MAP_SIZE,
.height = TA_SHADOW_MAP_SIZE,
.layer_count_or_depth = 1,
.num_levels = 1,
.sample_count = SDL_GPU_SAMPLECOUNT_1,
};
ta_shadow_texture = ta_sdl_create_gpu_texture(ta_gpu_device, &texture_info);
SDL_GPUSamplerCreateInfo sampler_info = {
.min_filter = SDL_GPU_FILTER_LINEAR,
.mag_filter = SDL_GPU_FILTER_LINEAR,
.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
.compare_op = SDL_GPU_COMPAREOP_LESS,
.enable_compare = true,
};
ta_shadow_sampler = ta_sdl_create_gpu_sampler(ta_gpu_device, &sampler_info);
}
void ta_shadow_get_light_view_projection(float *matrix) {
static const float world_up[TA_SHADOW_DIMENSION] = { 0.0f, 1.0f, 0.0f };
static const float world_forward[TA_SHADOW_DIMENSION] = { 0.0f, 0.0f, 1.0f };
float direction[TA_SHADOW_DIMENSION];
float forward[TA_SHADOW_DIMENSION];
float right[TA_SHADOW_DIMENSION];
float up[TA_SHADOW_DIMENSION];
float eye[TA_SHADOW_DIMENSION];
float view[TA_MATRIX_ELEMENTS];
float projection[TA_MATRIX_ELEMENTS] = { 0 };
memcpy(direction, ta_shadow_light_direction, sizeof(direction));
ta_shadow_normalize(direction);
for (int index = 0; index < TA_SHADOW_DIMENSION; index++) {
eye[index] = direction[index] * TA_SHADOW_DISTANCE;
forward[index] = -direction[index];
}
ta_shadow_cross(right, forward, fabsf(direction[1]) > 0.999f ? world_forward : world_up);
ta_shadow_normalize(right);
ta_shadow_cross(up, right, forward);
ta_matrix_identity(view);
view[0] = right[0];
view[4] = right[1];
view[8] = right[2];
view[12] = -ta_shadow_dot(right, eye);
view[1] = up[0];
view[5] = up[1];
view[9] = up[2];
view[13] = -ta_shadow_dot(up, eye);
view[2] = -forward[0];
view[6] = -forward[1];
view[10] = -forward[2];
view[14] = ta_shadow_dot(forward, eye);
projection[0] = 1.0f / TA_SHADOW_EXTENT;
projection[5] = 1.0f / TA_SHADOW_EXTENT;
projection[10] = -1.0f / (TA_SHADOW_FAR - TA_SHADOW_NEAR);
projection[14] = -TA_SHADOW_NEAR / (TA_SHADOW_FAR - TA_SHADOW_NEAR);
projection[15] = 1.0f;
ta_matrix_multiply(matrix, projection, view);
}
SDL_GPURenderPass *ta_shadow_begin_render_pass(SDL_GPUCommandBuffer *command_buffer) {
if (!ta_shadow_texture) {
return NULL;
}
SDL_GPUDepthStencilTargetInfo depth_target = {
.texture = ta_shadow_texture,
.clear_depth = 1.0f,
.load_op = SDL_GPU_LOADOP_CLEAR,
.store_op = SDL_GPU_STOREOP_STORE,
.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE,
.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE,
};
return ta_sdl_begin_gpu_render_pass(command_buffer, NULL, 0, &depth_target);
}
void ta_shadow_shutdown(void) {
SDL_ReleaseGPUSampler(ta_gpu_device, ta_shadow_sampler);
SDL_ReleaseGPUTexture(ta_gpu_device, ta_shadow_texture);
ta_shadow_sampler = NULL;
ta_shadow_texture = NULL;
}