/* * 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; }