From 6259f594c823f7b7fcb7de1a53eb376ce5aef001 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 20 May 2026 23:07:49 +0200 Subject: [PATCH 01/27] feat(gpu): suport Metal/MSL a macOS i shaders SPIR-V embedits --- CMakeLists.txt | 71 +- source/core/rendering/gpu/gpu_device.cpp | 123 +- source/core/rendering/gpu/gpu_device.hpp | 40 +- .../core/rendering/gpu/gpu_line_pipeline.cpp | 46 +- .../rendering/gpu/gpu_postfx_pipeline.cpp | 187 +- source/core/rendering/gpu/msl/line_frag.msl.h | 34 + source/core/rendering/gpu/msl/line_vert.msl.h | 54 + .../core/rendering/gpu/msl/postfx_frag.msl.h | 90 + .../core/rendering/gpu/msl/postfx_vert.msl.h | 38 + source/core/rendering/gpu/shader_factory.hpp | 60 + source/core/rendering/gpu/spv/line_frag_spv.h | 670 ++++ source/core/rendering/gpu/spv/line_vert_spv.h | 1182 ++++++ .../core/rendering/gpu/spv/postfx_frag_spv.h | 3246 +++++++++++++++++ .../core/rendering/gpu/spv/postfx_vert_spv.h | 1110 ++++++ tools/shaders/compile_spirv.cmake | 99 + 15 files changed, 6823 insertions(+), 227 deletions(-) create mode 100644 source/core/rendering/gpu/msl/line_frag.msl.h create mode 100644 source/core/rendering/gpu/msl/line_vert.msl.h create mode 100644 source/core/rendering/gpu/msl/postfx_frag.msl.h create mode 100644 source/core/rendering/gpu/msl/postfx_vert.msl.h create mode 100644 source/core/rendering/gpu/shader_factory.hpp create mode 100644 source/core/rendering/gpu/spv/line_frag_spv.h create mode 100644 source/core/rendering/gpu/spv/line_vert_spv.h create mode 100644 source/core/rendering/gpu/spv/postfx_frag_spv.h create mode 100644 source/core/rendering/gpu/spv/postfx_vert_spv.h create mode 100644 tools/shaders/compile_spirv.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index e30eb7a..8722473 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,44 +133,47 @@ add_custom_command( add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK}) add_dependencies(${PROJECT_NAME} resource_pack) -# --- COMPILACIÓ DE SHADERS GLSL → SPIR-V --- -# Compila tots els shaders .glsl a SPIR-V (Vulkan/Linux/Windows). -# macOS necessitarà MSL en el futur (Metal) — es generen amb spirv-cross -# o glslang amb target distint en una etapa posterior. -# Sortida: build/shaders/*.spv +# --- COMPILACIÓ DE SHADERS GLSL → SPIR-V (headers C++ embedits) --- +# Compila els shaders .glsl a SPIR-V i els converteix en headers C++ embedits +# (source/core/rendering/gpu/spv/*.h). Aquests headers es commiteen al repo: +# en macOS no cal glslc (els headers ja existeixen). En Linux/Windows glslc +# és obligatori per regenerar els headers en cada canvi del GLSL. +# +# Per a macOS hi ha a més els headers MSL escrits a mà a source/core/rendering/gpu/msl/. +set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/shaders") +set(HEADERS_DIR "${CMAKE_SOURCE_DIR}/source/core/rendering/gpu/spv") +set(ALL_SHADER_HEADERS + "${HEADERS_DIR}/line_vert_spv.h" + "${HEADERS_DIR}/line_frag_spv.h" + "${HEADERS_DIR}/postfx_vert_spv.h" + "${HEADERS_DIR}/postfx_frag_spv.h" +) +set(ALL_SHADER_SOURCES + "${SHADERS_DIR}/line.vert.glsl" + "${SHADERS_DIR}/line.frag.glsl" + "${SHADERS_DIR}/postfx.vert.glsl" + "${SHADERS_DIR}/postfx.frag.glsl" +) find_program(GLSLC_EXE NAMES glslc HINTS ${Vulkan_GLSLC_EXECUTABLE}) if(GLSLC_EXE) - file(GLOB SHADER_SOURCES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/shaders/*.glsl") - set(COMPILED_SHADERS "") - foreach(SHADER ${SHADER_SOURCES}) - get_filename_component(SHADER_NAME ${SHADER} NAME) - # Detectar stage del nom: line.vert.glsl → vert, line.frag.glsl → frag - if(SHADER_NAME MATCHES "\\.vert\\.glsl$") - set(SHADER_STAGE "vert") - string(REPLACE ".glsl" ".spv" SPV_NAME ${SHADER_NAME}) - elseif(SHADER_NAME MATCHES "\\.frag\\.glsl$") - set(SHADER_STAGE "frag") - string(REPLACE ".glsl" ".spv" SPV_NAME ${SHADER_NAME}) - else() - message(WARNING "Shader sense stage detectat: ${SHADER_NAME} (esperat .vert.glsl o .frag.glsl)") - continue() - endif() - set(SPV_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${SPV_NAME}") - add_custom_command( - OUTPUT ${SPV_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/shaders" - COMMAND ${GLSLC_EXE} -fshader-stage=${SHADER_STAGE} -O ${SHADER} -o ${SPV_OUTPUT} - DEPENDS ${SHADER} - COMMENT "Compilant shader ${SHADER_NAME} → ${SPV_NAME}" - VERBATIM - ) - list(APPEND COMPILED_SHADERS ${SPV_OUTPUT}) - endforeach() - add_custom_target(shaders ALL DEPENDS ${COMPILED_SHADERS}) + add_custom_command( + OUTPUT ${ALL_SHADER_HEADERS} + COMMAND ${CMAKE_COMMAND} + -D GLSLC=${GLSLC_EXE} + -D SHADERS_DIR=${SHADERS_DIR} + -D HEADERS_DIR=${HEADERS_DIR} + -P ${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.cmake + DEPENDS ${ALL_SHADER_SOURCES} ${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.cmake + COMMENT "Compilant shaders GLSL → headers SPIR-V embedits" + VERBATIM + ) + add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS}) add_dependencies(${PROJECT_NAME} shaders) - message(STATUS "Shaders trobats: ${SHADER_SOURCES}") + message(STATUS "Shaders: glslc trobat (${GLSLC_EXE}); headers SPV es regeneraran si canvia el GLSL") +elseif(APPLE) + message(STATUS "Shaders: glslc no trobat en macOS — s'usaran els headers SPV ja commiteats") else() - message(FATAL_ERROR "glslc no trobat: instal·la 'shaderc' o 'vulkan-sdk' per compilar shaders SPIR-V") + message(FATAL_ERROR "glslc no trobat: instal·la 'shaderc' o 'vulkan-sdk' per compilar shaders SPIR-V (obligatori a Linux/Windows)") endif() # --- STATIC ANALYSIS / FORMAT TARGETS --- diff --git a/source/core/rendering/gpu/gpu_device.cpp b/source/core/rendering/gpu/gpu_device.cpp index c5a1260..35777e6 100644 --- a/source/core/rendering/gpu/gpu_device.cpp +++ b/source/core/rendering/gpu/gpu_device.cpp @@ -1,105 +1,58 @@ -// gpu_device.cpp - Implementación del wrapper de SDL_GPUDevice +// gpu_device.cpp - Implementació del wrapper de SDL_GPUDevice #include "core/rendering/gpu/gpu_device.hpp" #include #include -#include -#include -#include #include -#include -#include - -#include "core/utils/path_utils.hpp" namespace Rendering::GPU { -GpuDevice::~GpuDevice() { destroy(); } + GpuDevice::~GpuDevice() { destroy(); } -auto GpuDevice::init(SDL_Window* window) -> bool { - window_ = window; + auto GpuDevice::init(SDL_Window* window) -> bool { + window_ = window; - // Solicitar backends en orden de preferencia: Vulkan (Linux/Windows), - // Metal (macOS). Sin DirectX según decisión de proyecto. - // SDL_GPU_SHADERFORMAT_SPIRV: shaders compilados con glslc. - // SDL_GPU_SHADERFORMAT_MSL: pendiente para macOS (Fase futura). - constexpr SDL_GPUShaderFormat SHADER_FORMATS = - SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL; + // Selecció del format de shader per plataforma: + // - macOS → MSL (Metal Shading Language), shaders embedits a gpu/msl/*.msl.h + // - Resta → SPIR-V (Vulkan), shaders embedits a gpu/spv/*.h +#ifdef __APPLE__ + constexpr SDL_GPUShaderFormat SHADER_FORMATS = SDL_GPU_SHADERFORMAT_MSL; +#else + constexpr SDL_GPUShaderFormat SHADER_FORMATS = SDL_GPU_SHADERFORMAT_SPIRV; +#endif - device_ = SDL_CreateGPUDevice(SHADER_FORMATS, /*debug=*/true, /*name=*/nullptr); - if (device_ == nullptr) { - std::cerr << "[GpuDevice] SDL_CreateGPUDevice falló: " << SDL_GetError() << '\n'; - return false; - } - - const char* driver = SDL_GetGPUDeviceDriver(device_); - std::cout << "[GpuDevice] Backend GPU: " << (driver != nullptr ? driver : "?") << '\n'; - - if (!SDL_ClaimWindowForGPUDevice(device_, window_)) { - std::cerr << "[GpuDevice] SDL_ClaimWindowForGPUDevice falló: " << SDL_GetError() << '\n'; - SDL_DestroyGPUDevice(device_); - device_ = nullptr; - return false; - } - - swapchain_format_ = SDL_GetGPUSwapchainTextureFormat(device_, window_); - return true; -} - -void GpuDevice::destroy() { - if (device_ != nullptr) { - if (window_ != nullptr) { - SDL_ReleaseWindowFromGPUDevice(device_, window_); + device_ = SDL_CreateGPUDevice(SHADER_FORMATS, /*debug_mode=*/true, /*name=*/nullptr); + if (device_ == nullptr) { + std::cerr << "[GpuDevice] SDL_CreateGPUDevice falló: " << SDL_GetError() << '\n'; + return false; } - SDL_DestroyGPUDevice(device_); - device_ = nullptr; - } - window_ = nullptr; - swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID; -} -auto GpuDevice::loadShader(const std::string& spv_filename, - SDL_GPUShaderStage stage, - uint32_t num_uniform_buffers, - uint32_t num_samplers) const -> SDL_GPUShader* { - if (device_ == nullptr) { - return nullptr; + const char* driver = SDL_GetGPUDeviceDriver(device_); + std::cout << "[GpuDevice] Backend GPU: " << (driver != nullptr ? driver : "?") << '\n'; + + if (!SDL_ClaimWindowForGPUDevice(device_, window_)) { + std::cerr << "[GpuDevice] SDL_ClaimWindowForGPUDevice falló: " << SDL_GetError() << '\n'; + SDL_DestroyGPUDevice(device_); + device_ = nullptr; + return false; + } + + swapchain_format_ = SDL_GetGPUSwapchainTextureFormat(device_, window_); + return true; } - // Los .spv viven en build/shaders/ junto al binario. - const std::string PATH = Utils::getExecutableDirectory() + "/shaders/" + spv_filename; - - std::ifstream file(PATH, std::ios::binary | std::ios::ate); - if (!file.is_open()) { - std::cerr << "[GpuDevice] No s'ha pogut obrir el shader: " << PATH << '\n'; - return nullptr; + void GpuDevice::destroy() { + if (device_ != nullptr) { + if (window_ != nullptr) { + SDL_ReleaseWindowFromGPUDevice(device_, window_); + } + SDL_DestroyGPUDevice(device_); + device_ = nullptr; + } + window_ = nullptr; + swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID; } - const std::streamsize SIZE = file.tellg(); - file.seekg(0, std::ios::beg); - std::vector buffer(static_cast(SIZE)); - if (!file.read(reinterpret_cast(buffer.data()), SIZE)) { - std::cerr << "[GpuDevice] Error llegint shader: " << PATH << '\n'; - return nullptr; - } - - SDL_GPUShaderCreateInfo info{}; - info.code = buffer.data(); - info.code_size = buffer.size(); - info.entrypoint = "main"; - info.format = SDL_GPU_SHADERFORMAT_SPIRV; - info.stage = stage; - info.num_uniform_buffers = num_uniform_buffers; - info.num_samplers = num_samplers; - info.num_storage_buffers = 0; - info.num_storage_textures = 0; - - SDL_GPUShader* shader = SDL_CreateGPUShader(device_, &info); - if (shader == nullptr) { - std::cerr << "[GpuDevice] SDL_CreateGPUShader (" << spv_filename << "): " << SDL_GetError() << '\n'; - } - return shader; -} } // namespace Rendering::GPU diff --git a/source/core/rendering/gpu/gpu_device.hpp b/source/core/rendering/gpu/gpu_device.hpp index 4f523c3..46df9cd 100644 --- a/source/core/rendering/gpu/gpu_device.hpp +++ b/source/core/rendering/gpu/gpu_device.hpp @@ -1,13 +1,17 @@ // gpu_device.hpp - Wrapper de SDL_GPUDevice // © 2026 JailDesigner // -// Ownership del SDL_GPUDevice y del claim del window. Backend preferido: -// Vulkan (Linux, Windows) y Metal (macOS). Sin DirectX. +// Ownership del SDL_GPUDevice i del claim del window. Backend per plataforma: +// Vulkan (Linux, Windows) i Metal (macOS). Sense DirectX. // -// Uso: +// Els shaders s'embedien al binari (no es carreguen de disc): SPIR-V via +// headers generats per CMake a `gpu/spv/*.h`, i MSL a mà a `gpu/msl/*.msl.h`. +// La creació dels SDL_GPUShader la fan els pipelines via shader_factory.hpp. +// +// Ús: // GpuDevice device; // if (!device.init(window)) return -1; // claim del window -// ... renderer setup ... +// ... pipelines setup ... // device.destroy(); // unclaim + destroy device #pragma once @@ -15,45 +19,33 @@ #include #include -#include - namespace Rendering::GPU { -class GpuDevice { - public: + class GpuDevice { + public: GpuDevice() = default; ~GpuDevice(); - // No copia / move (RAII propietario del device). + // No copia / move (RAII propietari del device). GpuDevice(const GpuDevice&) = delete; auto operator=(const GpuDevice&) -> GpuDevice& = delete; GpuDevice(GpuDevice&&) = delete; auto operator=(GpuDevice&&) -> GpuDevice& = delete; - // Crea el device y claim el window. Devuelve false si no hay backend - // soportado o si el driver no permite el claim. + // Crea el device i claim el window. Selecciona MSL en macOS, SPIR-V + // en la resta. Retorna false si no hi ha backend suportat o si el + // driver no permet el claim. [[nodiscard]] auto init(SDL_Window* window) -> bool; void destroy(); - // Carga un shader SPIR-V desde build/shaders/{name}.spv. Devuelve un - // SDL_GPUShader* del que ahora es responsable el caller (libera con - // SDL_ReleaseGPUShader). Retorna nullptr si falla. - // - // num_uniform_buffers: nº de uniform buffers que usa el shader (slot 0..N-1). - // num_samplers: nº de samplers (combined image+sampler) usados por el shader. - [[nodiscard]] auto loadShader(const std::string& spv_filename, - SDL_GPUShaderStage stage, - uint32_t num_uniform_buffers, - uint32_t num_samplers = 0) const -> SDL_GPUShader*; - [[nodiscard]] auto get() const -> SDL_GPUDevice* { return device_; } [[nodiscard]] auto window() const -> SDL_Window* { return window_; } [[nodiscard]] auto swapchainFormat() const -> SDL_GPUTextureFormat { return swapchain_format_; } - private: + private: SDL_GPUDevice* device_{nullptr}; SDL_Window* window_{nullptr}; SDL_GPUTextureFormat swapchain_format_{SDL_GPU_TEXTUREFORMAT_INVALID}; -}; + }; } // namespace Rendering::GPU diff --git a/source/core/rendering/gpu/gpu_line_pipeline.cpp b/source/core/rendering/gpu/gpu_line_pipeline.cpp index 58bab9d..18e8bb6 100644 --- a/source/core/rendering/gpu/gpu_line_pipeline.cpp +++ b/source/core/rendering/gpu/gpu_line_pipeline.cpp @@ -1,4 +1,4 @@ -// gpu_line_pipeline.cpp - Implementación del pipeline de líneas +// gpu_line_pipeline.cpp - Implementació del pipeline de línies #include "core/rendering/gpu/gpu_line_pipeline.hpp" @@ -8,6 +8,15 @@ #include #include "core/rendering/gpu/gpu_device.hpp" +#include "core/rendering/gpu/shader_factory.hpp" + +#ifdef __APPLE__ +#include "core/rendering/gpu/msl/line_frag.msl.h" +#include "core/rendering/gpu/msl/line_vert.msl.h" +#else +#include "core/rendering/gpu/spv/line_frag_spv.h" +#include "core/rendering/gpu/spv/line_vert_spv.h" +#endif namespace Rendering::GPU { @@ -20,12 +29,37 @@ namespace Rendering::GPU { return false; } - SDL_GPUShader* vert = device.loadShader("line.vert.spv", + // Vertex: 1 UBO (viewport_size). Fragment: cap recurs. +#ifdef __APPLE__ + SDL_GPUShader* vert = createShaderMSL(owner_, + Msl::LINE_VERT_MSL, + "line_vs", SDL_GPU_SHADERSTAGE_VERTEX, + /*num_samplers=*/0, /*num_uniform_buffers=*/1); - SDL_GPUShader* frag = device.loadShader("line.frag.spv", + SDL_GPUShader* frag = createShaderMSL(owner_, + Msl::LINE_FRAG_MSL, + "line_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, + /*num_samplers=*/0, /*num_uniform_buffers=*/0); +#else + SDL_GPUShader* vert = createShaderSPIRV(owner_, + LINE_VERT_SPV, + LINE_VERT_SPV_SIZE, + "main", + SDL_GPU_SHADERSTAGE_VERTEX, + /*num_samplers=*/0, + /*num_uniform_buffers=*/1); + SDL_GPUShader* frag = createShaderSPIRV(owner_, + LINE_FRAG_SPV, + LINE_FRAG_SPV_SIZE, + "main", + SDL_GPU_SHADERSTAGE_FRAGMENT, + /*num_samplers=*/0, + /*num_uniform_buffers=*/0); +#endif + if ((vert == nullptr) || (frag == nullptr)) { if (vert != nullptr) { SDL_ReleaseGPUShader(owner_, vert); @@ -33,7 +67,7 @@ namespace Rendering::GPU { if (frag != nullptr) { SDL_ReleaseGPUShader(owner_, frag); } - std::cerr << "[GpuLinePipeline] Error cargando shaders\n"; + std::cerr << "[GpuLinePipeline] Error cargando shaders: " << SDL_GetError() << '\n'; return false; } @@ -98,8 +132,8 @@ namespace Rendering::GPU { pipeline_ = SDL_CreateGPUGraphicsPipeline(owner_, &info); - // Los shaders se pueden liberar tras crear el pipeline (SDL los retiene - // internamente mientras el pipeline esté vivo). + // Els shaders es poden alliberar després de crear el pipeline (SDL els + // reté internament mentre el pipeline estigui viu). SDL_ReleaseGPUShader(owner_, vert); SDL_ReleaseGPUShader(owner_, frag); diff --git a/source/core/rendering/gpu/gpu_postfx_pipeline.cpp b/source/core/rendering/gpu/gpu_postfx_pipeline.cpp index 63a53cd..95bf212 100644 --- a/source/core/rendering/gpu/gpu_postfx_pipeline.cpp +++ b/source/core/rendering/gpu/gpu_postfx_pipeline.cpp @@ -1,4 +1,4 @@ -// gpu_postfx_pipeline.cpp - Implementación del pipeline de postprocesado. +// gpu_postfx_pipeline.cpp - Implementació del pipeline de postprocesat. #include "core/rendering/gpu/gpu_postfx_pipeline.hpp" @@ -8,91 +8,122 @@ #include #include "core/rendering/gpu/gpu_device.hpp" +#include "core/rendering/gpu/shader_factory.hpp" + +#ifdef __APPLE__ +#include "core/rendering/gpu/msl/postfx_frag.msl.h" +#include "core/rendering/gpu/msl/postfx_vert.msl.h" +#else +#include "core/rendering/gpu/spv/postfx_frag_spv.h" +#include "core/rendering/gpu/spv/postfx_vert_spv.h" +#endif namespace Rendering::GPU { -GpuPostFxPipeline::~GpuPostFxPipeline() { destroy(); } + GpuPostFxPipeline::~GpuPostFxPipeline() { destroy(); } -auto GpuPostFxPipeline::init(const GpuDevice& device, - SDL_GPUTextureFormat target_format) -> bool { - owner_ = device.get(); - if (owner_ == nullptr) { - return false; - } - - // El vertex shader no usa UBO (emite tres vértices hardcodeados). - // El fragment shader usa 1 sampler (escena) y 1 UBO (parámetros postpro). - SDL_GPUShader* vert = device.loadShader("postfx.vert.spv", - SDL_GPU_SHADERSTAGE_VERTEX, - /*num_uniform_buffers=*/0, - /*num_samplers=*/0); - SDL_GPUShader* frag = device.loadShader("postfx.frag.spv", - SDL_GPU_SHADERSTAGE_FRAGMENT, - /*num_uniform_buffers=*/1, - /*num_samplers=*/1); - if ((vert == nullptr) || (frag == nullptr)) { - if (vert != nullptr) { - SDL_ReleaseGPUShader(owner_, vert); + auto GpuPostFxPipeline::init(const GpuDevice& device, + SDL_GPUTextureFormat target_format) -> bool { + owner_ = device.get(); + if (owner_ == nullptr) { + return false; } - if (frag != nullptr) { - SDL_ReleaseGPUShader(owner_, frag); + + // Vertex shader: sense UBO ni vertex buffer (emet 3 vèrtexs hardcodejats). + // Fragment shader: 1 sampler (escena) + 1 UBO (paràmetres postpro). +#ifdef __APPLE__ + SDL_GPUShader* vert = createShaderMSL(owner_, + Msl::POSTFX_VERT_MSL, + "postfx_vs", + SDL_GPU_SHADERSTAGE_VERTEX, + /*num_samplers=*/0, + /*num_uniform_buffers=*/0); + SDL_GPUShader* frag = createShaderMSL(owner_, + Msl::POSTFX_FRAG_MSL, + "postfx_fs", + SDL_GPU_SHADERSTAGE_FRAGMENT, + /*num_samplers=*/1, + /*num_uniform_buffers=*/1); +#else + SDL_GPUShader* vert = createShaderSPIRV(owner_, + POSTFX_VERT_SPV, + POSTFX_VERT_SPV_SIZE, + "main", + SDL_GPU_SHADERSTAGE_VERTEX, + /*num_samplers=*/0, + /*num_uniform_buffers=*/0); + SDL_GPUShader* frag = createShaderSPIRV(owner_, + POSTFX_FRAG_SPV, + POSTFX_FRAG_SPV_SIZE, + "main", + SDL_GPU_SHADERSTAGE_FRAGMENT, + /*num_samplers=*/1, + /*num_uniform_buffers=*/1); +#endif + + if ((vert == nullptr) || (frag == nullptr)) { + if (vert != nullptr) { + SDL_ReleaseGPUShader(owner_, vert); + } + if (frag != nullptr) { + SDL_ReleaseGPUShader(owner_, frag); + } + std::cerr << "[GpuPostFxPipeline] Error cargando shaders postfx: " << SDL_GetError() << '\n'; + return false; } - std::cerr << "[GpuPostFxPipeline] Error cargando shaders postfx\n"; - return false; + + // Sense vertex input: els tres vèrtexs del triangle es generen al shader. + SDL_GPUVertexInputState vertex_input{}; + vertex_input.vertex_buffer_descriptions = nullptr; + vertex_input.num_vertex_buffers = 0; + vertex_input.vertex_attributes = nullptr; + vertex_input.num_vertex_attributes = 0; + + // Color target del postpro = swapchain. Sense blending: el postpro reescriu + // píxels directament (la mescla amb l'escena ja es fa dins del shader). + SDL_GPUColorTargetDescription color_target{}; + color_target.format = target_format; + color_target.blend_state.enable_blend = false; + color_target.blend_state.color_write_mask = + SDL_GPU_COLORCOMPONENT_R | SDL_GPU_COLORCOMPONENT_G | + SDL_GPU_COLORCOMPONENT_B | SDL_GPU_COLORCOMPONENT_A; + + SDL_GPUGraphicsPipelineTargetInfo target_info{}; + target_info.color_target_descriptions = &color_target; + target_info.num_color_targets = 1; + target_info.has_depth_stencil_target = false; + + SDL_GPUGraphicsPipelineCreateInfo info{}; + info.vertex_shader = vert; + info.fragment_shader = frag; + info.vertex_input_state = vertex_input; + info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL; + info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE; + info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE; + info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1; + info.depth_stencil_state = {}; + info.target_info = target_info; + + pipeline_ = SDL_CreateGPUGraphicsPipeline(owner_, &info); + + SDL_ReleaseGPUShader(owner_, vert); + SDL_ReleaseGPUShader(owner_, frag); + + if (pipeline_ == nullptr) { + std::cerr << "[GpuPostFxPipeline] SDL_CreateGPUGraphicsPipeline: " + << SDL_GetError() << '\n'; + return false; + } + return true; } - // Sin vertex input: los tres vértices del triángulo se generan en el shader. - SDL_GPUVertexInputState vertex_input{}; - vertex_input.vertex_buffer_descriptions = nullptr; - vertex_input.num_vertex_buffers = 0; - vertex_input.vertex_attributes = nullptr; - vertex_input.num_vertex_attributes = 0; - - // Color target del postpro = swapchain. Sin blending: el postpro reescribe - // píxeles directamente (la mezcla con la escena ya se hizo dentro del shader). - SDL_GPUColorTargetDescription color_target{}; - color_target.format = target_format; - color_target.blend_state.enable_blend = false; - color_target.blend_state.color_write_mask = - SDL_GPU_COLORCOMPONENT_R | SDL_GPU_COLORCOMPONENT_G | - SDL_GPU_COLORCOMPONENT_B | SDL_GPU_COLORCOMPONENT_A; - - SDL_GPUGraphicsPipelineTargetInfo target_info{}; - target_info.color_target_descriptions = &color_target; - target_info.num_color_targets = 1; - target_info.has_depth_stencil_target = false; - - SDL_GPUGraphicsPipelineCreateInfo info{}; - info.vertex_shader = vert; - info.fragment_shader = frag; - info.vertex_input_state = vertex_input; - info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL; - info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE; - info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE; - info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1; - info.depth_stencil_state = {}; - info.target_info = target_info; - - pipeline_ = SDL_CreateGPUGraphicsPipeline(owner_, &info); - - SDL_ReleaseGPUShader(owner_, vert); - SDL_ReleaseGPUShader(owner_, frag); - - if (pipeline_ == nullptr) { - std::cerr << "[GpuPostFxPipeline] SDL_CreateGPUGraphicsPipeline: " - << SDL_GetError() << '\n'; - return false; + void GpuPostFxPipeline::destroy() { + if ((pipeline_ != nullptr) && (owner_ != nullptr)) { + SDL_ReleaseGPUGraphicsPipeline(owner_, pipeline_); + } + pipeline_ = nullptr; + owner_ = nullptr; } - return true; -} - -void GpuPostFxPipeline::destroy() { - if ((pipeline_ != nullptr) && (owner_ != nullptr)) { - SDL_ReleaseGPUGraphicsPipeline(owner_, pipeline_); - } - pipeline_ = nullptr; - owner_ = nullptr; -} } // namespace Rendering::GPU diff --git a/source/core/rendering/gpu/msl/line_frag.msl.h b/source/core/rendering/gpu/msl/line_frag.msl.h new file mode 100644 index 0000000..6330ee4 --- /dev/null +++ b/source/core/rendering/gpu/msl/line_frag.msl.h @@ -0,0 +1,34 @@ +// line_frag.msl.h - Metal Shading Language del fragment shader de línies +// © 2026 JailDesigner +// +// IMPORTANT: mantenir sincronitzat a mà amb shaders/line.frag.glsl. SDL3 GPU +// compila aquest string MSL en runtime; no hi ha generador automàtic. +// +// Aplica antialias geomètric via smoothstep sobre edge_dist (±1 als laterals, +// 0 al centre del quad). Sense uniforms ni samplers. + +#pragma once +#ifdef __APPLE__ + +namespace Rendering::GPU::Msl { + +inline constexpr const char* LINE_FRAG_MSL = R"( +#include +using namespace metal; + +struct VOut { + float4 pos [[position]]; + float4 color; + float edge_dist; +}; + +fragment float4 line_fs(VOut in [[stage_in]]) { + float d = abs(in.edge_dist); + float alpha = 1.0 - smoothstep(0.7, 1.0, d); + return float4(in.color.rgb, in.color.a * alpha); +} +)"; + +} // namespace Rendering::GPU::Msl + +#endif // __APPLE__ diff --git a/source/core/rendering/gpu/msl/line_vert.msl.h b/source/core/rendering/gpu/msl/line_vert.msl.h new file mode 100644 index 0000000..7070b76 --- /dev/null +++ b/source/core/rendering/gpu/msl/line_vert.msl.h @@ -0,0 +1,54 @@ +// line_vert.msl.h - Metal Shading Language del vertex shader de línies +// © 2026 JailDesigner +// +// IMPORTANT: mantenir sincronitzat a mà amb shaders/line.vert.glsl. SDL3 GPU +// compila aquest string MSL en runtime; no hi ha generador automàtic. Qualsevol +// canvi al UBO o al layout de vèrtex cal replicar-lo ací al mateix commit. +// +// Mapping SDL3 GPU → Metal (segons src/gpu/metal/SDL_gpu_metal.m upstream): +// - Vertex buffers d'usuari: [[buffer(14 + slot)]] (METAL_FIRST_VERTEX_BUFFER_SLOT=14). +// A través de [[stage_in]] amb [[attribute(N)]], Metal resol automàticament +// a partir del MTLVertexDescriptor del pipeline state (transparent al MSL). +// - Vertex UBO slot 0 SDL → [[buffer(0)]] MSL (sense offset). + +#pragma once +#ifdef __APPLE__ + +namespace Rendering::GPU::Msl { + +inline constexpr const char* LINE_VERT_MSL = R"( +#include +using namespace metal; + +struct VIn { + float2 in_position [[attribute(0)]]; + float4 in_color [[attribute(1)]]; + float in_edge_dist[[attribute(2)]]; +}; + +struct VOut { + float4 pos [[position]]; + float4 color; + float edge_dist; +}; + +struct LineUBO { + float2 viewport_size; + float2 _padding; +}; + +vertex VOut line_vs(VIn in [[stage_in]], + constant LineUBO& ubo [[buffer(0)]]) { + float2 ndc = (in.in_position / ubo.viewport_size) * 2.0 - 1.0; + ndc.y = -ndc.y; + VOut out; + out.pos = float4(ndc, 0.0, 1.0); + out.color = in.in_color; + out.edge_dist = in.in_edge_dist; + return out; +} +)"; + +} // namespace Rendering::GPU::Msl + +#endif // __APPLE__ diff --git a/source/core/rendering/gpu/msl/postfx_frag.msl.h b/source/core/rendering/gpu/msl/postfx_frag.msl.h new file mode 100644 index 0000000..bea0969 --- /dev/null +++ b/source/core/rendering/gpu/msl/postfx_frag.msl.h @@ -0,0 +1,90 @@ +// postfx_frag.msl.h - Metal Shading Language del fragment shader del postpro +// © 2026 JailDesigner +// +// IMPORTANT: mantenir sincronitzat a mà amb shaders/postfx.frag.glsl. SDL3 GPU +// compila aquest string MSL en runtime; no hi ha generador automàtic. Qualsevol +// canvi al struct PostFxUniforms (gpu_postfx_pipeline.hpp), al GLSL o al MSL +// cal replicar-lo a totes tres al mateix commit. +// +// Composició final: bloom 5×5 amb high-pass, flicker sinusoidal global, +// background pulse sumat. Recursos: +// - texture2d scene [[texture(0)]] + sampler [[sampler(0)]] +// - constant PostFxUBO& ubo [[buffer(0)]] (slot 0 SDL → buffer(0) MSL) +// +// L'struct PostFxUBO té layout idèntic a PostFxUniforms (5×vec4 = 80 bytes). + +#pragma once +#ifdef __APPLE__ + +namespace Rendering::GPU::Msl { + +inline constexpr const char* POSTFX_FRAG_MSL = R"( +#include +using namespace metal; + +struct PostVOut { + float4 pos [[position]]; + float2 uv; +}; + +struct PostFxUBO { + float time; + float bloom_intensity; + float bloom_threshold; + float bloom_radius_px; + + float flicker_amplitude; + float flicker_frequency_hz; + float background_pulse_freq_hz; + float pad_a; + + float4 background_min; + float4 background_max; + + float2 texel_size; + float2 pad_b; +}; + +constant float TAU = 6.28318530718; + +fragment float4 postfx_fs(PostVOut in [[stage_in]], + texture2d scene [[texture(0)]], + sampler samp [[sampler(0)]], + constant PostFxUBO& ubo [[buffer(0)]]) { + // === BLOOM === + float3 src = scene.sample(samp, in.uv).rgb; + float3 bloom = float3(0.0); + float total_weight = 0.0; + for (int dy = -2; dy <= 2; ++dy) { + for (int dx = -2; dx <= 2; ++dx) { + float2 offset = float2(float(dx), float(dy)) * ubo.texel_size * ubo.bloom_radius_px; + float3 c = scene.sample(samp, in.uv + offset).rgb; + float luma = max(c.r, max(c.g, c.b)); + float high_pass = max(0.0, luma - ubo.bloom_threshold); + float w = exp(-float(dx * dx + dy * dy) / 4.0); + bloom += c * high_pass * w; + total_weight += w; + } + } + if (total_weight > 0.0) { + bloom /= total_weight; + } + bloom *= ubo.bloom_intensity; + + // === FLICKER === + float pulse = (sin(ubo.time * ubo.flicker_frequency_hz * TAU) * 0.5) + 0.5; + float flicker = 1.0 - (ubo.flicker_amplitude * (1.0 - pulse)); + + // === BACKGROUND PULSE === + float bg_pulse = (sin(ubo.time * ubo.background_pulse_freq_hz * TAU) * 0.5) + 0.5; + float3 background = mix(ubo.background_min.rgb, ubo.background_max.rgb, bg_pulse); + + // === COMPOSICIÓ === + float3 lines_and_glow = (src + bloom) * flicker; + return float4(background + lines_and_glow, 1.0); +} +)"; + +} // namespace Rendering::GPU::Msl + +#endif // __APPLE__ diff --git a/source/core/rendering/gpu/msl/postfx_vert.msl.h b/source/core/rendering/gpu/msl/postfx_vert.msl.h new file mode 100644 index 0000000..a365cc7 --- /dev/null +++ b/source/core/rendering/gpu/msl/postfx_vert.msl.h @@ -0,0 +1,38 @@ +// postfx_vert.msl.h - Metal Shading Language del vertex shader del postpro +// © 2026 JailDesigner +// +// IMPORTANT: mantenir sincronitzat a mà amb shaders/postfx.vert.glsl. SDL3 GPU +// compila aquest string MSL en runtime; no hi ha generador automàtic. +// +// Fullscreen triangle: 3 vèrtexs en (-1,-1), (3,-1), (-1,3) generats per +// [[vertex_id]]. UV.y invertida per compensar la diferència de convenció +// entre clip-space del line shader (Y-flip) i el mostreig SDL_gpu (origen +// top-left). Sense uniforms ni vertex buffers (DrawPrimitives vertex_count=3). + +#pragma once +#ifdef __APPLE__ + +namespace Rendering::GPU::Msl { + +inline constexpr const char* POSTFX_VERT_MSL = R"( +#include +using namespace metal; + +struct PostVOut { + float4 pos [[position]]; + float2 uv; +}; + +vertex PostVOut postfx_vs(uint vid [[vertex_id]]) { + const float2 positions[3] = { {-1.0, -1.0}, { 3.0, -1.0}, {-1.0, 3.0} }; + const float2 uvs[3] = { { 0.0, 1.0}, { 2.0, 1.0}, { 0.0, -1.0} }; + PostVOut out; + out.pos = float4(positions[vid], 0.0, 1.0); + out.uv = uvs[vid]; + return out; +} +)"; + +} // namespace Rendering::GPU::Msl + +#endif // __APPLE__ diff --git a/source/core/rendering/gpu/shader_factory.hpp b/source/core/rendering/gpu/shader_factory.hpp new file mode 100644 index 0000000..fe32f81 --- /dev/null +++ b/source/core/rendering/gpu/shader_factory.hpp @@ -0,0 +1,60 @@ +// shader_factory.hpp - Helpers per crear SDL_GPUShader segons plataforma +// © 2026 JailDesigner +// +// En __APPLE__ s'utilitza MSL (Metal Shading Language) embedit com a string +// literal C++. En la resta de plataformes (Linux/Windows) s'utilitza SPIR-V +// embedit com a uint8_t[] en headers generats per CMake. La selecció és +// compile-time via #ifdef. + +#pragma once + +#include + +#include +#include +#include + +namespace Rendering::GPU { + +#ifdef __APPLE__ + inline auto createShaderMSL(SDL_GPUDevice* device, + const char* msl_source, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers) -> SDL_GPUShader* { + SDL_GPUShaderCreateInfo info{}; + info.code = reinterpret_cast(msl_source); + info.code_size = std::strlen(msl_source) + 1; + info.entrypoint = entrypoint; + info.format = SDL_GPU_SHADERFORMAT_MSL; + info.stage = stage; + info.num_samplers = num_samplers; + info.num_uniform_buffers = num_uniform_buffers; + info.num_storage_buffers = 0; + info.num_storage_textures = 0; + return SDL_CreateGPUShader(device, &info); + } +#else + inline auto createShaderSPIRV(SDL_GPUDevice* device, + const std::uint8_t* spv_code, + std::size_t spv_size, + const char* entrypoint, + SDL_GPUShaderStage stage, + Uint32 num_samplers, + Uint32 num_uniform_buffers) -> SDL_GPUShader* { + SDL_GPUShaderCreateInfo info{}; + info.code = spv_code; + info.code_size = spv_size; + info.entrypoint = entrypoint; + info.format = SDL_GPU_SHADERFORMAT_SPIRV; + info.stage = stage; + info.num_samplers = num_samplers; + info.num_uniform_buffers = num_uniform_buffers; + info.num_storage_buffers = 0; + info.num_storage_textures = 0; + return SDL_CreateGPUShader(device, &info); + } +#endif + +} // namespace Rendering::GPU diff --git a/source/core/rendering/gpu/spv/line_frag_spv.h b/source/core/rendering/gpu/spv/line_frag_spv.h new file mode 100644 index 0000000..d3a9022 --- /dev/null +++ b/source/core/rendering/gpu/spv/line_frag_spv.h @@ -0,0 +1,670 @@ +#pragma once +#include +#include +static const uint8_t LINE_FRAG_SPV[] = { + 0x03, + 0x02, + 0x23, + 0x07, + 0x00, + 0x00, + 0x01, + 0x00, + 0x0b, + 0x00, + 0x0d, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x02, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x4c, + 0x53, + 0x4c, + 0x2e, + 0x73, + 0x74, + 0x64, + 0x2e, + 0x34, + 0x35, + 0x30, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x08, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x69, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x03, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x03, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0x3f, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x33, + 0x33, + 0x33, + 0x3f, + 0x17, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x05, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x08, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x31, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x07, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0xfd, + 0x00, + 0x01, + 0x00, + 0x38, + 0x00, + 0x01, + 0x00, +}; +static const size_t LINE_FRAG_SPV_SIZE = 664; diff --git a/source/core/rendering/gpu/spv/line_vert_spv.h b/source/core/rendering/gpu/spv/line_vert_spv.h new file mode 100644 index 0000000..084e0a0 --- /dev/null +++ b/source/core/rendering/gpu/spv/line_vert_spv.h @@ -0,0 +1,1182 @@ +#pragma once +#include +#include +static const uint8_t LINE_VERT_SPV[] = { + 0x03, + 0x02, + 0x23, + 0x07, + 0x00, + 0x00, + 0x01, + 0x00, + 0x0b, + 0x00, + 0x0d, + 0x00, + 0x3b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x02, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x4c, + 0x53, + 0x4c, + 0x2e, + 0x73, + 0x74, + 0x64, + 0x2e, + 0x34, + 0x35, + 0x30, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x69, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0x35, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x03, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x03, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x35, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x03, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x04, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x04, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0x3f, + 0x15, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x04, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x06, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x32, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x32, + 0x00, + 0x00, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x34, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x34, + 0x00, + 0x00, + 0x00, + 0x35, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3a, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x05, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x3a, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x7f, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x07, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x2d, + 0x00, + 0x00, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x2d, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x31, + 0x00, + 0x00, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x31, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x00, + 0x00, + 0x35, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x00, + 0x00, + 0xfd, + 0x00, + 0x01, + 0x00, + 0x38, + 0x00, + 0x01, + 0x00, +}; +static const size_t LINE_VERT_SPV_SIZE = 1176; diff --git a/source/core/rendering/gpu/spv/postfx_frag_spv.h b/source/core/rendering/gpu/spv/postfx_frag_spv.h new file mode 100644 index 0000000..d05e970 --- /dev/null +++ b/source/core/rendering/gpu/spv/postfx_frag_spv.h @@ -0,0 +1,3246 @@ +#pragma once +#include +#include +static const uint8_t POSTFX_FRAG_SPV[] = { + 0x03, + 0x02, + 0x23, + 0x07, + 0x00, + 0x00, + 0x01, + 0x00, + 0x0b, + 0x00, + 0x0d, + 0x00, + 0xd8, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x02, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x4c, + 0x53, + 0x4c, + 0x2e, + 0x73, + 0x74, + 0x64, + 0x2e, + 0x34, + 0x35, + 0x30, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x07, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x69, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0xbc, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x03, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x03, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x30, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0xbc, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x03, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x09, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x00, + 0x03, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0xfe, + 0xff, + 0xff, + 0xff, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x02, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x0e, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x38, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x37, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x38, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x3a, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x3b, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x3f, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x76, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x87, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x8a, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x00, + 0x00, + 0xdb, + 0x0f, + 0xc9, + 0x40, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x91, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3f, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x95, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0x3f, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x96, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0xa0, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0xa9, + 0x00, + 0x00, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0xaa, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0xae, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0xbb, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0xbb, + 0x00, + 0x00, + 0x00, + 0xbc, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xd7, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0xbe, + 0x36, + 0x00, + 0x05, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xc9, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0xcd, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x07, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0xce, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x07, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x79, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0xb3, + 0x00, + 0x05, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x27, + 0x00, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0xf6, + 0x00, + 0x04, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0x27, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x07, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xce, + 0x00, + 0x00, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xcd, + 0x00, + 0x00, + 0x00, + 0xc9, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x07, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0xb3, + 0x00, + 0x05, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0xf6, + 0x00, + 0x04, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x35, + 0x00, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x05, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x00, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0x35, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x3b, + 0x00, + 0x00, + 0x00, + 0x3c, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x3a, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x00, + 0x00, + 0x3c, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x3f, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x42, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x43, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x00, + 0x00, + 0x42, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x45, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x43, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x05, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x49, + 0x00, + 0x00, + 0x00, + 0x45, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x4a, + 0x00, + 0x00, + 0x00, + 0x49, + 0x00, + 0x00, + 0x00, + 0x49, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x00, + 0x00, + 0x49, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x52, + 0x00, + 0x00, + 0x00, + 0x49, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x55, + 0x00, + 0x00, + 0x00, + 0x49, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x07, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x56, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x52, + 0x00, + 0x00, + 0x00, + 0x55, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x07, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x00, + 0x00, + 0x56, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x5a, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5b, + 0x00, + 0x00, + 0x00, + 0x5a, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5c, + 0x00, + 0x00, + 0x00, + 0x57, + 0x00, + 0x00, + 0x00, + 0x5b, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x07, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x5d, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x5c, + 0x00, + 0x00, + 0x00, + 0x84, + 0x00, + 0x05, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x61, + 0x00, + 0x00, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0x84, + 0x00, + 0x05, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x64, + 0x00, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x05, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x65, + 0x00, + 0x00, + 0x00, + 0x61, + 0x00, + 0x00, + 0x00, + 0x64, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x66, + 0x00, + 0x00, + 0x00, + 0x65, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x66, + 0x00, + 0x00, + 0x00, + 0xd7, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x6a, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x69, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x4a, + 0x00, + 0x00, + 0x00, + 0x5d, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x00, + 0x00, + 0x00, + 0x6a, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x71, + 0x00, + 0x00, + 0x00, + 0xcd, + 0x00, + 0x00, + 0x00, + 0x6f, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x74, + 0x00, + 0x00, + 0x00, + 0xce, + 0x00, + 0x00, + 0x00, + 0x6a, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x05, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x77, + 0x00, + 0x00, + 0x00, + 0xcb, + 0x00, + 0x00, + 0x00, + 0x76, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x05, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x79, + 0x00, + 0x00, + 0x00, + 0xc7, + 0x00, + 0x00, + 0x00, + 0x76, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0xba, + 0x00, + 0x05, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0xf7, + 0x00, + 0x03, + 0x00, + 0x7d, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xfa, + 0x00, + 0x04, + 0x00, + 0x7b, + 0x00, + 0x00, + 0x00, + 0x7c, + 0x00, + 0x00, + 0x00, + 0x7d, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x7c, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0xc8, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x00, + 0x00, + 0xc9, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0xf9, + 0x00, + 0x02, + 0x00, + 0x7d, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x7d, + 0x00, + 0x00, + 0x00, + 0xf5, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xca, + 0x00, + 0x00, + 0x00, + 0xc9, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x00, + 0x00, + 0x7c, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x82, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x76, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x00, + 0x00, + 0x82, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x00, + 0x00, + 0xca, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x87, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x89, + 0x00, + 0x00, + 0x00, + 0x88, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x8b, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x8a, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x8c, + 0x00, + 0x00, + 0x00, + 0x8b, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x8d, + 0x00, + 0x00, + 0x00, + 0x89, + 0x00, + 0x00, + 0x00, + 0x8c, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x8f, + 0x00, + 0x00, + 0x00, + 0x8d, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x90, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x8f, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x92, + 0x00, + 0x00, + 0x00, + 0x90, + 0x00, + 0x00, + 0x00, + 0x91, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x97, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0x96, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x98, + 0x00, + 0x00, + 0x00, + 0x97, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9a, + 0x00, + 0x00, + 0x00, + 0x91, + 0x00, + 0x00, + 0x00, + 0x92, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9b, + 0x00, + 0x00, + 0x00, + 0x98, + 0x00, + 0x00, + 0x00, + 0x9a, + 0x00, + 0x00, + 0x00, + 0x83, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x9c, + 0x00, + 0x00, + 0x00, + 0x95, + 0x00, + 0x00, + 0x00, + 0x9b, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0xa1, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0xa0, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa2, + 0x00, + 0x00, + 0x00, + 0xa1, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa3, + 0x00, + 0x00, + 0x00, + 0x89, + 0x00, + 0x00, + 0x00, + 0xa2, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa4, + 0x00, + 0x00, + 0x00, + 0xa3, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa5, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0xa4, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa6, + 0x00, + 0x00, + 0x00, + 0xa5, + 0x00, + 0x00, + 0x00, + 0x91, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xa7, + 0x00, + 0x00, + 0x00, + 0xa6, + 0x00, + 0x00, + 0x00, + 0x91, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0xaa, + 0x00, + 0x00, + 0x00, + 0xab, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0xa9, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0xac, + 0x00, + 0x00, + 0x00, + 0xab, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xad, + 0x00, + 0x00, + 0x00, + 0xac, + 0x00, + 0x00, + 0x00, + 0xac, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0xaa, + 0x00, + 0x00, + 0x00, + 0xaf, + 0x00, + 0x00, + 0x00, + 0x39, + 0x00, + 0x00, + 0x00, + 0xae, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0xb0, + 0x00, + 0x00, + 0x00, + 0xaf, + 0x00, + 0x00, + 0x00, + 0x4f, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb1, + 0x00, + 0x00, + 0x00, + 0xb0, + 0x00, + 0x00, + 0x00, + 0xb0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb3, + 0x00, + 0x00, + 0x00, + 0xa7, + 0x00, + 0x00, + 0x00, + 0xa7, + 0x00, + 0x00, + 0x00, + 0xa7, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x08, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0xad, + 0x00, + 0x00, + 0x00, + 0xb1, + 0x00, + 0x00, + 0x00, + 0xb3, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xb8, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x85, + 0x00, + 0x00, + 0x00, + 0x8e, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xba, + 0x00, + 0x00, + 0x00, + 0xb8, + 0x00, + 0x00, + 0x00, + 0x9c, + 0x00, + 0x00, + 0x00, + 0x81, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0xb4, + 0x00, + 0x00, + 0x00, + 0xba, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xc0, + 0x00, + 0x00, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xc1, + 0x00, + 0x00, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0xc2, + 0x00, + 0x00, + 0x00, + 0xbf, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x07, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0xc3, + 0x00, + 0x00, + 0x00, + 0xc0, + 0x00, + 0x00, + 0x00, + 0xc1, + 0x00, + 0x00, + 0x00, + 0xc2, + 0x00, + 0x00, + 0x00, + 0x95, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0xbc, + 0x00, + 0x00, + 0x00, + 0xc3, + 0x00, + 0x00, + 0x00, + 0xfd, + 0x00, + 0x01, + 0x00, + 0x38, + 0x00, + 0x01, + 0x00, +}; +static const size_t POSTFX_FRAG_SPV_SIZE = 3240; diff --git a/source/core/rendering/gpu/spv/postfx_vert_spv.h b/source/core/rendering/gpu/spv/postfx_vert_spv.h new file mode 100644 index 0000000..ff86164 --- /dev/null +++ b/source/core/rendering/gpu/spv/postfx_vert_spv.h @@ -0,0 +1,1110 @@ +#pragma once +#include +#include +static const uint8_t POSTFX_VERT_SPV[] = { + 0x03, + 0x02, + 0x23, + 0x07, + 0x00, + 0x00, + 0x01, + 0x00, + 0x0b, + 0x00, + 0x0d, + 0x00, + 0x33, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x02, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x06, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x47, + 0x4c, + 0x53, + 0x4c, + 0x2e, + 0x73, + 0x74, + 0x64, + 0x2e, + 0x34, + 0x35, + 0x30, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x6d, + 0x61, + 0x69, + 0x6e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x03, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x48, + 0x00, + 0x05, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x47, + 0x00, + 0x04, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x03, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x04, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0xbf, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + 0x40, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x06, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x11, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0x3f, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x00, + 0x00, + 0x00, + 0x2c, + 0x00, + 0x06, + 0x00, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x16, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x19, + 0x00, + 0x00, + 0x00, + 0x17, + 0x00, + 0x04, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x04, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x06, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x1d, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x1e, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x1f, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x04, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x04, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x23, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x04, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x2e, + 0x00, + 0x00, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x36, + 0x00, + 0x05, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0xf8, + 0x00, + 0x02, + 0x00, + 0x05, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3b, + 0x00, + 0x04, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x1a, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x21, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x24, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x27, + 0x00, + 0x00, + 0x00, + 0x0c, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x27, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x51, + 0x00, + 0x05, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x28, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x50, + 0x00, + 0x07, + 0x00, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x2a, + 0x00, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x15, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x2c, + 0x00, + 0x00, + 0x00, + 0x2d, + 0x00, + 0x00, + 0x00, + 0x20, + 0x00, + 0x00, + 0x00, + 0x22, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x2d, + 0x00, + 0x00, + 0x00, + 0x2b, + 0x00, + 0x00, + 0x00, + 0x41, + 0x00, + 0x05, + 0x00, + 0x26, + 0x00, + 0x00, + 0x00, + 0x31, + 0x00, + 0x00, + 0x00, + 0x13, + 0x00, + 0x00, + 0x00, + 0x25, + 0x00, + 0x00, + 0x00, + 0x3d, + 0x00, + 0x04, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x32, + 0x00, + 0x00, + 0x00, + 0x31, + 0x00, + 0x00, + 0x00, + 0x3e, + 0x00, + 0x03, + 0x00, + 0x2f, + 0x00, + 0x00, + 0x00, + 0x32, + 0x00, + 0x00, + 0x00, + 0xfd, + 0x00, + 0x01, + 0x00, + 0x38, + 0x00, + 0x01, + 0x00, +}; +static const size_t POSTFX_VERT_SPV_SIZE = 1104; diff --git a/tools/shaders/compile_spirv.cmake b/tools/shaders/compile_spirv.cmake new file mode 100644 index 0000000..f351110 --- /dev/null +++ b/tools/shaders/compile_spirv.cmake @@ -0,0 +1,99 @@ +# compile_spirv.cmake +# Compila shaders GLSL a SPIR-V i genera headers C++ embedibles. +# Multiplataforma: Windows, macOS, Linux (no requereix bash ni xxd). +# +# Invocat per CMakeLists.txt amb: +# cmake -D GLSLC= -D SHADERS_DIR= -D HEADERS_DIR= -P compile_spirv.cmake +# +# També es pot executar manualment des de l'arrel del projecte: +# cmake -D GLSLC=glslc -D SHADERS_DIR=shaders \ +# -D HEADERS_DIR=source/core/rendering/gpu/spv \ +# -P tools/shaders/compile_spirv.cmake + +cmake_minimum_required(VERSION 3.10) +cmake_policy(SET CMP0007 NEW) + +# Llista de shaders a compilar: font relativa a SHADERS_DIR +set(SHADER_SOURCES + "line.vert.glsl" + "line.frag.glsl" + "postfx.vert.glsl" + "postfx.frag.glsl" +) + +# Nom de la variable C++ per a cada shader (mateix ordre). +# UPPER_CASE perquè són constexpr globals (.clang-tidy ho exigeix). +set(SHADER_VARS + "LINE_VERT_SPV" + "LINE_FRAG_SPV" + "POSTFX_VERT_SPV" + "POSTFX_FRAG_SPV" +) + +# Flags extra per a cada shader (necessaris perquè .vert.glsl/.frag.glsl no s'infereixen) +set(SHADER_FLAGS + "-fshader-stage=vert" + "-fshader-stage=frag" + "-fshader-stage=vert" + "-fshader-stage=frag" +) + +list(LENGTH SHADER_SOURCES NUM_SHADERS) +math(EXPR LAST_IDX "${NUM_SHADERS} - 1") + +foreach(IDX RANGE ${LAST_IDX}) + list(GET SHADER_SOURCES ${IDX} SRC_NAME) + list(GET SHADER_VARS ${IDX} VAR) + list(GET SHADER_FLAGS ${IDX} EXTRA_FLAG) + + # Derivem el nom del header a partir de la variable: LINE_VERT_SPV → line_vert_spv.h + string(TOLOWER "${VAR}" HDR_BASE) + set(SRC "${SHADERS_DIR}/${SRC_NAME}") + set(SPV "${HEADERS_DIR}/${HDR_BASE}.spv") + set(HDR "${HEADERS_DIR}/${HDR_BASE}.h") + + message(STATUS "Compilant ${SRC} ...") + + if(EXTRA_FLAG) + execute_process( + COMMAND "${GLSLC}" "${EXTRA_FLAG}" -O "${SRC}" -o "${SPV}" + RESULT_VARIABLE GLSLC_RESULT + ERROR_VARIABLE GLSLC_ERROR + ) + else() + execute_process( + COMMAND "${GLSLC}" -O "${SRC}" -o "${SPV}" + RESULT_VARIABLE GLSLC_RESULT + ERROR_VARIABLE GLSLC_ERROR + ) + endif() + + if(NOT GLSLC_RESULT EQUAL 0) + message(FATAL_ERROR "glslc ha fallat per a ${SRC}:\n${GLSLC_ERROR}") + endif() + + # Llegim el binari SPV com a hex (sense separadors) i el dividim en bytes. + file(READ "${SPV}" HEX_DATA HEX) + string(REGEX MATCHALL ".." BYTES "${HEX_DATA}") + list(LENGTH BYTES NUM_BYTES) + + set(ARRAY_BODY "") + foreach(BYTE ${BYTES}) + string(APPEND ARRAY_BODY " 0x${BYTE},\n") + endforeach() + + file(WRITE "${HDR}" + "#pragma once\n" + "#include \n" + "#include \n" + "static const uint8_t ${VAR}[] = {\n" + "${ARRAY_BODY}" + "};\n" + "static const size_t ${VAR}_SIZE = ${NUM_BYTES};\n" + ) + + file(REMOVE "${SPV}") + message(STATUS " -> ${HDR} (${NUM_BYTES} bytes)") +endforeach() + +message(STATUS "Shaders SPIR-V compilats correctament.") From 6629e9b9aa0ef0394d4f868976b9ca4c3d468d08 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 08:16:55 +0200 Subject: [PATCH 02/27] =?UTF-8?q?fix(warnings):=20cast=20RAND=5FMAX=20a=20?= =?UTF-8?q?float=20per=20evitar=20conversi=C3=B3=20impl=C3=ADcita?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/graphics/starfield.cpp | 6 +++--- source/game/entities/enemy.cpp | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/source/core/graphics/starfield.cpp b/source/core/graphics/starfield.cpp index a587ec2..01e5623 100644 --- a/source/core/graphics/starfield.cpp +++ b/source/core/graphics/starfield.cpp @@ -50,10 +50,10 @@ Starfield::Starfield(Rendering::Renderer* renderer, estrella.capa = capa_idx; // Angle aleatori - estrella.angle = (static_cast(rand()) / RAND_MAX) * 2.0F * Defaults::Math::PI; + estrella.angle = (static_cast(rand()) / static_cast(RAND_MAX)) * 2.0F * Defaults::Math::PI; // Distancia aleatòria (0.0 a 1.0) per omplir toda la pantalla - estrella.distancia_centre = static_cast(rand()) / RAND_MAX; + estrella.distancia_centre = static_cast(rand()) / static_cast(RAND_MAX); // Calcular posición desde la distancia float radi = estrella.distancia_centre * radi_max_; @@ -68,7 +68,7 @@ Starfield::Starfield(Rendering::Renderer* renderer, // Inicialitzar una estrella (nueva o regenerada) void Starfield::initStar(Estrella& estrella) const { // Angle aleatori des del point de fuga hacia fuera - estrella.angle = (static_cast(rand()) / RAND_MAX) * 2.0F * Defaults::Math::PI; + estrella.angle = (static_cast(rand()) / static_cast(RAND_MAX)) * 2.0F * Defaults::Math::PI; // Distancia inicial pequeña (5% del radi màxim) - neix prop del centro estrella.distancia_centre = 0.05F; diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index c23a846..1617366 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -153,7 +153,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) { // Rotación visual aleatoria (independiente del body) const float DROTACIO_RANGE = drotacio_max - drotacio_min; - drotacio_ = drotacio_min + ((static_cast(std::rand()) / RAND_MAX) * DROTACIO_RANGE); + drotacio_ = drotacio_min + ((static_cast(std::rand()) / static_cast(RAND_MAX)) * DROTACIO_RANGE); rotacio_ = 0.0F; // Estado de animación @@ -269,10 +269,10 @@ void Enemy::behaviorPentagon(float delta_time) { // Probabilidad de zigzag por segundo (calibrada para sensación equivalente // a la versión vieja que disparaba en cada toque de pared). constexpr float ZIGZAG_PROB_PER_SECOND = 0.8F; - const float RAND_VAL = static_cast(std::rand()) / RAND_MAX; + const float RAND_VAL = static_cast(std::rand()) / static_cast(RAND_MAX); if (RAND_VAL < ZIGZAG_PROB_PER_SECOND * delta_time) { const float CURRENT_ANGLE = velocityToAngle(body_.velocity); - const float DELTA = (static_cast(std::rand()) / RAND_MAX) * + const float DELTA = (static_cast(std::rand()) / static_cast(RAND_MAX)) * Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX; const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA); const float SPEED = body_.velocity.length(); @@ -339,7 +339,7 @@ void Enemy::updatePalpitation(float delta_time) { animacio_.palpitacio_activa = false; } } else { - const float RAND_VAL = static_cast(std::rand()) / RAND_MAX; + const float RAND_VAL = static_cast(std::rand()) / static_cast(RAND_MAX); const float TRIGGER_PROB = Defaults::Enemies::Animation::PALPITACIO_TRIGGER_PROB * delta_time; if (RAND_VAL < TRIGGER_PROB) { animacio_.palpitacio_activa = true; @@ -348,17 +348,17 @@ void Enemy::updatePalpitation(float delta_time) { const float FREQ_RANGE = Defaults::Enemies::Animation::PALPITACIO_FREQ_MAX - Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN; animacio_.palpitacio_frequencia = Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN + - ((static_cast(std::rand()) / RAND_MAX) * FREQ_RANGE); + ((static_cast(std::rand()) / static_cast(RAND_MAX)) * FREQ_RANGE); const float AMP_RANGE = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MAX - Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN; animacio_.palpitacio_amplitud = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN + - ((static_cast(std::rand()) / RAND_MAX) * AMP_RANGE); + ((static_cast(std::rand()) / static_cast(RAND_MAX)) * AMP_RANGE); const float DUR_RANGE = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MAX - Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN; animacio_.palpitacio_temps_restant = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN + - ((static_cast(std::rand()) / RAND_MAX) * DUR_RANGE); + ((static_cast(std::rand()) / static_cast(RAND_MAX)) * DUR_RANGE); } } } @@ -378,7 +378,7 @@ void Enemy::updateRotationAcceleration(float delta_time) { drotacio_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T); } } else { - const float RAND_VAL = static_cast(std::rand()) / RAND_MAX; + const float RAND_VAL = static_cast(std::rand()) / static_cast(RAND_MAX); const float TRIGGER_PROB = Defaults::Enemies::Animation::ROTACIO_ACCEL_TRIGGER_PROB * delta_time; if (RAND_VAL < TRIGGER_PROB) { animacio_.drotacio_t = 0.0F; @@ -386,13 +386,13 @@ void Enemy::updateRotationAcceleration(float delta_time) { const float MULT_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MAX - Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN; const float MULTIPLIER = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN + - ((static_cast(std::rand()) / RAND_MAX) * MULT_RANGE); + ((static_cast(std::rand()) / static_cast(RAND_MAX)) * MULT_RANGE); animacio_.drotacio_objetivo = animacio_.drotacio_base * MULTIPLIER; const float DUR_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MAX - Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN; animacio_.drotacio_duracio = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN + - ((static_cast(std::rand()) / RAND_MAX) * DUR_RANGE); + ((static_cast(std::rand()) / static_cast(RAND_MAX)) * DUR_RANGE); } } } From 9a79fb9774a57754da22d16ea96c833b169a4022 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 08:18:12 +0200 Subject: [PATCH 03/27] chore(shaders): regenera postfx_frag_spv.h --- .../core/rendering/gpu/spv/postfx_frag_spv.h | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/source/core/rendering/gpu/spv/postfx_frag_spv.h b/source/core/rendering/gpu/spv/postfx_frag_spv.h index d05e970..8bfc8aa 100644 --- a/source/core/rendering/gpu/spv/postfx_frag_spv.h +++ b/source/core/rendering/gpu/spv/postfx_frag_spv.h @@ -1714,22 +1714,6 @@ static const uint8_t POSTFX_FRAG_SPV[] = { 0x00, 0x00, 0x00, - 0x3d, - 0x00, - 0x04, - 0x00, - 0x0b, - 0x00, - 0x00, - 0x00, - 0x45, - 0x00, - 0x00, - 0x00, - 0x0d, - 0x00, - 0x00, - 0x00, 0x81, 0x00, 0x05, @@ -1762,7 +1746,7 @@ static const uint8_t POSTFX_FRAG_SPV[] = { 0x00, 0x00, 0x00, - 0x45, + 0x0e, 0x00, 0x00, 0x00, @@ -3243,4 +3227,4 @@ static const uint8_t POSTFX_FRAG_SPV[] = { 0x01, 0x00, }; -static const size_t POSTFX_FRAG_SPV_SIZE = 3240; +static const size_t POSTFX_FRAG_SPV_SIZE = 3224; From 4252f3327fa8ceaac25285fb09f8248156013dc9 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 08:24:22 +0200 Subject: [PATCH 04/27] =?UTF-8?q?fix(notifier):=20ESC=20nom=C3=A9s=20confi?= =?UTF-8?q?rma=20sobre=20el=20propi=20prompt=20de=20sortida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/system/global_events.cpp | 14 +++++++------- source/core/system/notifier.cpp | 10 +++++++++- source/core/system/notifier.hpp | 10 ++++++++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/source/core/system/global_events.cpp b/source/core/system/global_events.cpp index a2f2183..afd1e3b 100644 --- a/source/core/system/global_events.cpp +++ b/source/core/system/global_events.cpp @@ -60,18 +60,18 @@ namespace GlobalEvents { case SDL_SCANCODE_ESCAPE: { // Doble pulsació per confirmar sortida: la primera ESC - // dispara un toast d'avís; mentre el toast està entrant - // o aguantant (isActiveWindow), la segona ESC tanca el - // joc. Si el toast ha començat a sortir o ja ha - // desaparegut, ESC torna a obrir la finestra de - // confirmació sense tancar. + // dispara un toast d'avís; només si aquest toast concret + // (isExitPromptActive) segueix visible, la segona ESC + // tanca el joc. Si la notificació activa és una altra + // (zoom, fullscreen, vsync...), ESC obre el prompt de + // sortida en lloc de tancar. auto* notifier = System::Notifier::get(); - if (notifier != nullptr && !notifier->isActiveWindow()) { + if (notifier != nullptr && !notifier->isExitPromptActive()) { notifier->notifyExit("PREMEU ESC UN ALTRE COP PER EIXIR"); return true; } // Notifier inexistent (degradació elegant) o segona ESC - // dins la finestra activa: tanquem el joc. + // sobre el prompt de sortida: tanquem el joc. context.setNextScene(SceneType::EXIT); SceneManager::actual = SceneType::EXIT; return true; diff --git a/source/core/system/notifier.cpp b/source/core/system/notifier.cpp index e93fc04..b44f756 100644 --- a/source/core/system/notifier.cpp +++ b/source/core/system/notifier.cpp @@ -77,6 +77,7 @@ namespace System { current_text_ = text; current_color_ = text_color; hold_remaining_s_ = duration_s; + current_is_exit_ = false; const float TEXT_W = Graphics::VectorText::getTextWidth(text, TEXT_SCALE, TEXT_SPACING); const float TEXT_H = Graphics::VectorText::getTextHeight(TEXT_SCALE); @@ -100,7 +101,10 @@ namespace System { void Notifier::notifyInfo(const std::string& text) { notify(text, COLOR_INFO, DURATION_INFO); } void Notifier::notifyWarn(const std::string& text) { notify(text, COLOR_WARN, DURATION_WARN); } - void Notifier::notifyExit(const std::string& text) { notify(text, COLOR_EXIT, DURATION_EXIT); } + void Notifier::notifyExit(const std::string& text) { + notify(text, COLOR_EXIT, DURATION_EXIT); + current_is_exit_ = true; // notify() ho ha posat a false; restaurem. + } void Notifier::update(float delta_time) { switch (status_) { @@ -183,4 +187,8 @@ namespace System { return status_ == Status::ENTERING || status_ == Status::HOLDING; } + auto Notifier::isExitPromptActive() const -> bool { + return isActiveWindow() && current_is_exit_; + } + } // namespace System diff --git a/source/core/system/notifier.hpp b/source/core/system/notifier.hpp index 6bd3cda..b933ac7 100644 --- a/source/core/system/notifier.hpp +++ b/source/core/system/notifier.hpp @@ -47,10 +47,15 @@ namespace System { void draw() const; // Activa mentre el toast està entrant o aguantant. Quan està sortint - // o ja amagat, retorna false. Útil per a la lògica de doble-pulsació - // d'ESC: la segona pulsació només confirma sortida si encara aguanta. + // o ja amagat, retorna false. [[nodiscard]] auto isActiveWindow() const -> bool; + // Cert només si el toast actiu va ser disparat per notifyExit(). + // Per a la doble-pulsació d'ESC: la segona ESC confirma sortida + // únicament si la notificació visible és la de confirmació; si era + // de F1/F2/etc., ESC torna a obrir el prompt sense tancar. + [[nodiscard]] auto isExitPromptActive() const -> bool; + private: explicit Notifier(Rendering::Renderer* renderer); @@ -74,6 +79,7 @@ namespace System { float box_h_{0.0F}; float text_x_{0.0F}; // X esquerre del text dins la caixa float text_scale_{0.4F}; + bool current_is_exit_{false}; // true només si l'actiu ve de notifyExit() static std::unique_ptr instance; }; From 5d1dae1d863bcf752f11c8ec57a8af16fcfd2834 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 08:46:22 +0200 Subject: [PATCH 05/27] =?UTF-8?q?feat(render):=20resoluci=C3=B3=20d'offscr?= =?UTF-8?q?een=20configurable=20via=20YAML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separa el tamany lògic (1280×720) del render target offscreen. Llista tancada de 5 presets 16:9 (720p/900p/1080p/1440p/2160p) llegida de rendering.render_{width,height} amb fallback a 1280×720 si invàlida. Inclou API resizeRenderTarget() preparada per al menú de servei futur. --- source/core/config/engine_config.hpp | 7 +++ source/core/defaults/rendering.hpp | 27 ++++++++++++ .../core/rendering/gpu/gpu_frame_renderer.cpp | 30 ++++++++++--- .../core/rendering/gpu/gpu_frame_renderer.hpp | 30 ++++++++++--- source/core/rendering/sdl_manager.cpp | 43 ++++++++++++++++--- source/game/config_yaml.cpp | 30 ++++++++++++- 6 files changed, 150 insertions(+), 17 deletions(-) diff --git a/source/core/config/engine_config.hpp b/source/core/config/engine_config.hpp index 3890dfe..72ae20d 100644 --- a/source/core/config/engine_config.hpp +++ b/source/core/config/engine_config.hpp @@ -27,6 +27,13 @@ namespace Config { struct RenderingConfig { int vsync{1}; // 0=disabled, 1=enabled int antialias{1}; // 0=disabled, 1=enabled (AA geomètric a les línies, toggle F5) + // Resolució del render target offscreen (independent del tamany lògic + // 1280×720 del joc). Aquesta és la resolució real on rasteritzen les + // línies abans de l'escala final a la swapchain; pujar-la millora + // la nitidesa en finestres grans i fullscreen. Llista tancada de + // presets 16:9 — veure Defaults::Rendering::RESOLUTION_PRESETS. + int render_width{1280}; + int render_height{720}; }; struct KeyboardBindings { diff --git a/source/core/defaults/rendering.hpp b/source/core/defaults/rendering.hpp index 819b1fd..0480516 100644 --- a/source/core/defaults/rendering.hpp +++ b/source/core/defaults/rendering.hpp @@ -3,9 +3,36 @@ #pragma once +#include +#include + namespace Defaults::Rendering { constexpr int VSYNC_DEFAULT = 1; // 0=disabled, 1=enabled constexpr int ANTIALIAS_DEFAULT = 1; // 0=disabled, 1=enabled (AA geomètric a les línies) + // Resolució del render target offscreen. El tamany lògic del joc roman a + // 1280×720 (coordenades dels objectes); aquesta és la resolució física a + // la qual es rasteritzen les línies abans de la composició final. + struct ResolutionPreset { + int w; + int h; + }; + + constexpr std::array RESOLUTION_PRESETS{{ + {.w = 1280, .h = 720}, // HD 720p (default) + {.w = 1600, .h = 900}, // HD+ 900p + {.w = 1920, .h = 1080}, // Full HD 1080p + {.w = 2560, .h = 1440}, // QHD 1440p + {.w = 3840, .h = 2160} // 4K UHD 2160p + }}; + + constexpr int RENDER_WIDTH_DEFAULT = 1280; + constexpr int RENDER_HEIGHT_DEFAULT = 720; + + constexpr auto isValidRenderResolution(int w, int h) -> bool { + return std::ranges::any_of(RESOLUTION_PRESETS, + [w, h](const ResolutionPreset& preset) { return preset.w == w && preset.h == h; }); + } + } // namespace Defaults::Rendering diff --git a/source/core/rendering/gpu/gpu_frame_renderer.cpp b/source/core/rendering/gpu/gpu_frame_renderer.cpp index bdf6fc9..1a3bb78 100644 --- a/source/core/rendering/gpu/gpu_frame_renderer.cpp +++ b/source/core/rendering/gpu/gpu_frame_renderer.cpp @@ -14,9 +14,11 @@ namespace Rendering::GPU { GpuFrameRenderer::~GpuFrameRenderer() { destroy(); } - auto GpuFrameRenderer::init(SDL_Window* window, float logical_w, float logical_h) -> bool { + auto GpuFrameRenderer::init(SDL_Window* window, float logical_w, float logical_h, float render_w, float render_h) -> bool { logical_w_ = logical_w; logical_h_ = logical_h; + render_w_ = render_w; + render_h_ = render_h; if (!device_.init(window)) { return false; @@ -47,13 +49,15 @@ namespace Rendering::GPU { return false; } - // Textura offscreen del tamaño lógico del juego, COLOR_TARGET + SAMPLER. + // Textura offscreen del tamaño físico de render, COLOR_TARGET + SAMPLER. + // El tamaño lógico se aplica a los vértices vía UBO; el offscreen puede + // ser de mayor resolución para ganar nitidez tras el upscale a la swapchain. SDL_GPUTextureCreateInfo tex_info{}; tex_info.type = SDL_GPU_TEXTURETYPE_2D; tex_info.format = offscreen_format_; tex_info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; - tex_info.width = static_cast(logical_w_); - tex_info.height = static_cast(logical_h_); + tex_info.width = static_cast(render_w_); + tex_info.height = static_cast(render_h_); tex_info.layer_count_or_depth = 1; tex_info.num_levels = 1; tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1; @@ -107,6 +111,19 @@ namespace Rendering::GPU { indices_.clear(); } + auto GpuFrameRenderer::resizeRenderTarget(float render_w, float render_h) -> bool { + // Solo seguro fuera de un frame: si el cmd buffer está vivo y referencia + // la textura antigua, recrearla provocaría un cuelgue/UB. + if (isInsideFrame()) { + std::cerr << "[GpuFrameRenderer] resizeRenderTarget llamado dentro de frame, ignorado\n"; + return false; + } + destroyOffscreen(); + render_w_ = render_w; + render_h_ = render_h; + return createOffscreen(); + } + auto GpuFrameRenderer::beginFrame(float clear_r, float clear_g, float clear_b) -> bool { // Los clear_* se ignoran: el fondo lo pinta el postpro. Mantenemos la // firma para no romper el SDLManager. @@ -438,8 +455,9 @@ namespace Rendering::GPU { ubo.background_max_g = BG_MAX_G; ubo.background_max_b = BG_MAX_B; ubo.background_max_a = 1.0F; - ubo.texel_size_x = 1.0F / logical_w_; - ubo.texel_size_y = 1.0F / logical_h_; + // El sampling del bloom muestrea el offscreen → texel size del tamaño físico. + ubo.texel_size_x = 1.0F / render_w_; + ubo.texel_size_y = 1.0F / render_h_; ubo.pad_b = 0.0F; ubo.pad_c = 0.0F; diff --git a/source/core/rendering/gpu/gpu_frame_renderer.hpp b/source/core/rendering/gpu/gpu_frame_renderer.hpp index 0428e7e..fad67e3 100644 --- a/source/core/rendering/gpu/gpu_frame_renderer.hpp +++ b/source/core/rendering/gpu/gpu_frame_renderer.hpp @@ -59,12 +59,24 @@ namespace Rendering::GPU { GpuFrameRenderer(GpuFrameRenderer&&) = delete; auto operator=(GpuFrameRenderer&&) -> GpuFrameRenderer& = delete; - // Crea device + pipeline + offscreen + sampler. logical_w/h = tamaño - // en píxeles lógicos del juego (1280×720), usado como base del - // offscreen y de la transformación a NDC del shader de líneas. - [[nodiscard]] auto init(SDL_Window* window, float logical_w, float logical_h) -> bool; + // Crea device + pipeline + offscreen + sampler. + // logical_w/h: tamaño en píxeles lógicos del juego (1280×720). Lo + // consume el shader de líneas para transformar a NDC. + // render_w/h: tamaño físico del offscreen donde se rasterizan las + // líneas. Puede ser > logical para ganar nitidez al + // escalar a la swapchain (configurable vía YAML). + [[nodiscard]] auto init(SDL_Window* window, + float logical_w, + float logical_h, + float render_w, + float render_h) -> bool; void destroy(); + // Recrea el offscreen con un nuevo tamaño físico de render. Solo es + // seguro fuera de un frame (isInsideFrame() == false). Devuelve false + // si está dentro de frame o si la creación de la textura falla. + [[nodiscard]] auto resizeRenderTarget(float render_w, float render_h) -> bool; + // beginFrame: adquiere swapchain, abre render pass sobre offscreen // con clear a negro. Devuelve false si no hay textura disponible. // Los argumentos clear_r/g/b se ignoran (compatibilidad de API: el @@ -114,10 +126,18 @@ namespace Rendering::GPU { GpuLinePipeline line_pipeline_; GpuPostFxPipeline postfx_pipeline_; - // Tamaño lógico del juego (= tamaño del offscreen). + // Tamaño lógico del juego: espacio de coordenadas de las primitivas + // (vértices, UBO del line shader). Fijo a 1280×720. float logical_w_{1280.0F}; float logical_h_{720.0F}; + // Tamaño físico del offscreen (configurable). Independiente del lógico: + // las coordenadas en NDC son agnósticas a la resolución de salida, así + // que rasterizar a mayor render_w_/h_ da líneas más nítidas tras el + // upscale lineal a la swapchain. + float render_w_{1280.0F}; + float render_h_{720.0F}; + // Viewport del pase final en píxeles físicos. <0 = full window. float viewport_x_{0.0F}; float viewport_y_{0.0F}; diff --git a/source/core/rendering/sdl_manager.cpp b/source/core/rendering/sdl_manager.cpp index ffecacb..a672812 100644 --- a/source/core/rendering/sdl_manager.cpp +++ b/source/core/rendering/sdl_manager.cpp @@ -10,7 +10,9 @@ #include #include "core/config/postfx_config.hpp" -#include "core/defaults.hpp" +#include "core/defaults/game.hpp" +#include "core/defaults/rendering.hpp" +#include "core/defaults/window.hpp" #include "core/input/mouse.hpp" #include "core/rendering/coordinate_transform.hpp" #include "core/system/notifier.hpp" @@ -22,7 +24,9 @@ namespace { int width, int height, bool fullscreen, - int initial_vsync) -> bool { + int initial_vsync, + int render_width, + int render_height) -> bool { // Título estático estilo CCAE. El FPS y el estado de VSync los muestra // el DebugOverlay (toggle F11), no la barra de título. const std::string TITLE = std::format("© 2026 {} — JailDesigner", @@ -44,9 +48,13 @@ namespace { } // Inicializar el FrameRenderer (claim del window + pipeline de líneas). + // logical_*: espacio de coordenadas del juego (fijo 1280×720). + // render_*: resolución física del offscreen (configurable vía YAML). if (!gpu_renderer.init(window, static_cast(Defaults::Game::WIDTH), - static_cast(Defaults::Game::HEIGHT))) { + static_cast(Defaults::Game::HEIGHT), + static_cast(render_width), + static_cast(render_height))) { std::cerr << "Error inicialitzant GpuFrameRenderer\n"; SDL_DestroyWindow(window); return false; @@ -80,7 +88,30 @@ SDLManager::SDLManager(int width, int height, bool fullscreen, Config::EngineCon calculateMaxWindowSize(); - if (!initWindowAndGpu(&finestra_, gpu_renderer_, current_width_, current_height_, is_fullscreen_, cfg_->rendering.vsync)) { + // Validar la resolució de render del config: si no és un preset 16:9 + // conegut, fer fallback a 1280×720 i avisar. Això protegeix d'edicions + // manuals invàlides al YAML. + int effective_render_w = cfg_->rendering.render_width; + int effective_render_h = cfg_->rendering.render_height; + if (!Defaults::Rendering::isValidRenderResolution(effective_render_w, effective_render_h)) { + std::cerr << "Resolució de render invàlida (" << effective_render_w << "x" + << effective_render_h << "), fent fallback a " + << Defaults::Rendering::RENDER_WIDTH_DEFAULT << "x" + << Defaults::Rendering::RENDER_HEIGHT_DEFAULT << '\n'; + effective_render_w = Defaults::Rendering::RENDER_WIDTH_DEFAULT; + effective_render_h = Defaults::Rendering::RENDER_HEIGHT_DEFAULT; + cfg_->rendering.render_width = effective_render_w; + cfg_->rendering.render_height = effective_render_h; + } + + if (!initWindowAndGpu(&finestra_, + gpu_renderer_, + current_width_, + current_height_, + is_fullscreen_, + cfg_->rendering.vsync, + effective_render_w, + effective_render_h)) { SDL_Quit(); return; } @@ -97,7 +128,9 @@ SDLManager::SDLManager(int width, int height, bool fullscreen, Config::EngineCon std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_ << " (logic: " << Defaults::Game::WIDTH << "x" - << Defaults::Game::HEIGHT << ")"; + << Defaults::Game::HEIGHT + << ", render: " << effective_render_w << "x" << effective_render_h + << ")"; if (is_fullscreen_) { std::cout << " [FULLSCREEN]"; } diff --git a/source/game/config_yaml.cpp b/source/game/config_yaml.cpp index 18d9f6b..7f215b1 100644 --- a/source/game/config_yaml.cpp +++ b/source/game/config_yaml.cpp @@ -204,6 +204,8 @@ namespace ConfigYaml { // Rendering rendering.vsync = Defaults::Rendering::VSYNC_DEFAULT; + rendering.render_width = Defaults::Rendering::RENDER_WIDTH_DEFAULT; + rendering.render_height = Defaults::Rendering::RENDER_HEIGHT_DEFAULT; // Version version = std::string(Project::VERSION); @@ -275,6 +277,28 @@ namespace ConfigYaml { rendering.vsync = Defaults::Rendering::VSYNC_DEFAULT; } } + + // Resolució de render: validem el parell (w, h) contra la llista + // tancada de presets 16:9. Si falla l'una o l'altra, fem fallback + // dels dos camps al default per mantenir un parell vàlid. + int candidate_w = rendering.render_width; + int candidate_h = rendering.render_height; + readField(rend, "render_width", candidate_w, Defaults::Rendering::RENDER_WIDTH_DEFAULT); + readField(rend, "render_height", candidate_h, Defaults::Rendering::RENDER_HEIGHT_DEFAULT); + if (Defaults::Rendering::isValidRenderResolution(candidate_w, candidate_h)) { + rendering.render_width = candidate_w; + rendering.render_height = candidate_h; + } else { + if (console) { + std::cerr << "Resolució de render invàlida al YAML (" + << candidate_w << "x" << candidate_h + << "), fallback a " + << Defaults::Rendering::RENDER_WIDTH_DEFAULT << "x" + << Defaults::Rendering::RENDER_HEIGHT_DEFAULT << '\n'; + } + rendering.render_width = Defaults::Rendering::RENDER_WIDTH_DEFAULT; + rendering.render_height = Defaults::Rendering::RENDER_HEIGHT_DEFAULT; + } } } @@ -501,7 +525,11 @@ namespace ConfigYaml { file << "# RENDERITZACIÓ\n"; file << "rendering:\n"; - file << " vsync: " << rendering.vsync << " # 0=disabled, 1=enabled\n\n"; + file << " vsync: " << rendering.vsync << " # 0=disabled, 1=enabled\n"; + file << " render_width: " << rendering.render_width + << " # Presets 16:9: 1280, 1600, 1920, 2560, 3840 (fallback 1280)\n"; + file << " render_height: " << rendering.render_height + << " # Parell amb render_width (720, 900, 1080, 1440, 2160)\n\n"; // Guardar controls de jugadors savePlayer1ControlsToYaml(file); From 61ae211dab385674902453b28e977e45638f9fd4 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 08:52:19 +0200 Subject: [PATCH 06/27] chore(iwyu): subheaders concrets i pragma exports al umbrella MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reemplaça core/defaults.hpp pels subheaders concrets a director.cpp i config_yaml.cpp (silencia unused-includes de clangd). Marca el umbrella amb IWYU pragma: begin_exports/end_exports per evitar falsos positius als consumidors transitius. --- source/core/defaults.hpp | 2 ++ source/core/system/director.cpp | 3 ++- source/game/config_yaml.cpp | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 528a61b..a2c7670 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -8,6 +8,7 @@ #pragma once +// IWYU pragma: begin_exports #include "core/defaults/audio.hpp" #include "core/defaults/brightness.hpp" #include "core/defaults/controls.hpp" @@ -23,3 +24,4 @@ #include "core/defaults/title.hpp" #include "core/defaults/window.hpp" #include "core/defaults/zones.hpp" +// IWYU pragma: end_exports diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index afa3eca..4eea305 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -11,7 +11,8 @@ #include "core/audio/audio.hpp" #include "core/audio/audio_adapter.hpp" -#include "core/defaults.hpp" +#include "core/defaults/audio.hpp" +#include "core/defaults/window.hpp" #include "core/input/input.hpp" #include "core/input/mouse.hpp" #include "core/rendering/sdl_manager.hpp" diff --git a/source/game/config_yaml.cpp b/source/game/config_yaml.cpp index 7f215b1..d45d880 100644 --- a/source/game/config_yaml.cpp +++ b/source/game/config_yaml.cpp @@ -5,7 +5,8 @@ #include #include -#include "core/defaults.hpp" +#include "core/defaults/rendering.hpp" +#include "core/defaults/window.hpp" #include "external/fkyaml_node.hpp" #include "project.h" From 08100f60e8336844adfd8e412accdabcb698ec69 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 09:39:36 +0200 Subject: [PATCH 07/27] refactor(defaults): centralitza constants de bullet, ship, enemy, hud i notifier --- source/core/defaults.hpp | 2 + source/core/defaults/enemies.hpp | 22 +++- source/core/defaults/game.hpp | 1 + source/core/defaults/hud.hpp | 13 ++ source/core/defaults/notifier.hpp | 31 +++++ source/core/defaults/ship.hpp | 11 ++ source/core/system/notifier.cpp | 69 ++++------ source/game/entities/bullet.cpp | 8 +- source/game/entities/enemy.cpp | 23 ++-- source/game/entities/ship.cpp | 14 +- source/game/scenes/game_scene.cpp | 4 +- source/game/systems/init_hud_animator.cpp | 149 +++++++++++----------- 12 files changed, 197 insertions(+), 150 deletions(-) create mode 100644 source/core/defaults/hud.hpp create mode 100644 source/core/defaults/notifier.hpp diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index a2c7670..04b752a 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -16,7 +16,9 @@ #include "core/defaults/entities.hpp" #include "core/defaults/floating_score.hpp" #include "core/defaults/game.hpp" +#include "core/defaults/hud.hpp" #include "core/defaults/math.hpp" +#include "core/defaults/notifier.hpp" #include "core/defaults/palette.hpp" #include "core/defaults/physics.hpp" #include "core/defaults/rendering.hpp" diff --git a/source/core/defaults/enemies.hpp b/source/core/defaults/enemies.hpp index b4c81ee..d321966 100644 --- a/source/core/defaults/enemies.hpp +++ b/source/core/defaults/enemies.hpp @@ -7,19 +7,30 @@ namespace Defaults::Enemies { + // Cuerpo físico común (valores por defecto del constructor) + namespace Body { + constexpr float DEFAULT_MASS = 5.0F; // Más liviano que la nave (10.0) + constexpr float RESTITUTION = 1.0F; // Rebote elástico perfecto contra paredes + constexpr float LINEAR_DAMPING = 0.0F; // Sin fricción: mantienen velocidad + constexpr float ANGULAR_DAMPING = 0.0F; + } // namespace Body + // Pentagon (esquivador - zigzag evasion) namespace Pentagon { - constexpr float VELOCITAT = 35.0F; // px/s (slightly slower) - constexpr float CANVI_ANGLE_PROB = 0.20F; // 20% per wall hit (frequent zigzag) - constexpr float CANVI_ANGLE_MAX = 1.0F; // Max random angle change (rad) - constexpr float DROTACIO_MIN = 0.75F; // Min visual rotation (rad/s) [+50%] - constexpr float DROTACIO_MAX = 3.75F; // Max visual rotation (rad/s) [+50%] + constexpr float VELOCITAT = 35.0F; // px/s (slightly slower) + constexpr float MASS = 5.0F; // Masa estándar + constexpr float CANVI_ANGLE_PROB = 0.20F; // 20% per wall hit (frequent zigzag) + constexpr float CANVI_ANGLE_MAX = 1.0F; // Max random angle change (rad) + constexpr float ZIGZAG_PROB_PER_SECOND = 0.8F; // Probabilidad de zigzag por segundo + constexpr float DROTACIO_MIN = 0.75F; // Min visual rotation (rad/s) [+50%] + constexpr float DROTACIO_MAX = 3.75F; // Max visual rotation (rad/s) [+50%] constexpr const char* SHAPE_FILE = "enemy_pentagon.shp"; } // namespace Pentagon // Cuadrado (perseguidor - tracks player) namespace Cuadrado { constexpr float VELOCITAT = 40.0F; // px/s (medium speed) + constexpr float MASS = 8.0F; // Más pesado, "tanque" constexpr float TRACKING_STRENGTH = 0.5F; // Interpolation toward player (0.0-1.0) constexpr float TRACKING_INTERVAL = 1.0F; // Seconds between angle updates constexpr float DROTACIO_MIN = 0.3F; // Slow rotation [+50%] @@ -30,6 +41,7 @@ namespace Defaults::Enemies { // Molinillo (agressiu - fast straight lines, proximity spin-up) namespace Molinillo { constexpr float VELOCITAT = 50.0F; // px/s (fastest) + constexpr float MASS = 4.0F; // Más liviano, ágil constexpr float CANVI_ANGLE_PROB = 0.05F; // 5% per wall hit (rare direction change) constexpr float CANVI_ANGLE_MAX = 0.3F; // Small angle adjustments constexpr float DROTACIO_MIN = 3.0F; // Base rotation (rad/s) [+50%] diff --git a/source/core/defaults/game.hpp b/source/core/defaults/game.hpp index e5f4c9d..64d86ab 100644 --- a/source/core/defaults/game.hpp +++ b/source/core/defaults/game.hpp @@ -20,6 +20,7 @@ namespace Defaults::Game { constexpr bool FRIENDLY_FIRE_ENABLED = true; // Activar friendly fire constexpr float COLLISION_BULLET_PLAYER_AMPLIFIER = 1.0F; // Hitbox exacto (100%) constexpr float BULLET_GRACE_PERIOD = 0.2F; // Inmunidad post-disparo (s) + constexpr float BULLET_SPEED = 140.0F; // Velocidad escalar (px/s). Pascal: 7 px/frame × 20 FPS // Transición LEVEL_START (mensajes aleatorios PRE-level) constexpr float LEVEL_START_DURATION = 3.0F; // Duración total diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp new file mode 100644 index 0000000..f97843c --- /dev/null +++ b/source/core/defaults/hud.hpp @@ -0,0 +1,13 @@ +// hud.hpp - Configuració visual del HUD (marcador, etc.) +// © 2026 JailDesigner + +#pragma once + +namespace Defaults::Hud { + + // Marcador (scoreboard inferior). Usado por GameScene::drawScoreboard() + // y por la animación de entrada en init_hud_animator. + constexpr float SCOREBOARD_TEXT_SCALE = 0.85F; + constexpr float SCOREBOARD_TEXT_SPACING = 0.0F; + +} // namespace Defaults::Hud diff --git a/source/core/defaults/notifier.hpp b/source/core/defaults/notifier.hpp new file mode 100644 index 0000000..a3938df --- /dev/null +++ b/source/core/defaults/notifier.hpp @@ -0,0 +1,31 @@ +// notifier.hpp - Configuració del cuadre de notificacions toast (System::Notifier) +// © 2026 JailDesigner + +#pragma once + +#include + +namespace Defaults::Notifier { + + // Geometria del cuadre en coordenades lògiques (1280×720). + constexpr float CANVAS_WIDTH = 1280.0F; + constexpr float MARGIN_TOP = 40.0F; + constexpr float PADDING_H = 16.0F; + constexpr float PADDING_V = 10.0F; + constexpr float BORDER_THICKNESS = 2.0F; + constexpr float TEXT_SCALE = 0.55F; + constexpr float TEXT_SPACING = 2.0F; + constexpr float BORDER_BRIGHTNESS = 1.0F; + + // Cinemàtica del slide. + constexpr float SLIDE_DURATION_S = 0.30F; + + // Presets per als atajos semàntics. + constexpr SDL_Color COLOR_INFO{.r = 80, .g = 230, .b = 255, .a = 255}; + constexpr SDL_Color COLOR_WARN{.r = 255, .g = 180, .b = 40, .a = 255}; + constexpr SDL_Color COLOR_EXIT{.r = 255, .g = 80, .b = 80, .a = 255}; + constexpr float DURATION_INFO = 2.0F; + constexpr float DURATION_WARN = 3.0F; + constexpr float DURATION_EXIT = 3.0F; + +} // namespace Defaults::Notifier diff --git a/source/core/defaults/ship.hpp b/source/core/defaults/ship.hpp index 6156a3d..75e07b9 100644 --- a/source/core/defaults/ship.hpp +++ b/source/core/defaults/ship.hpp @@ -13,4 +13,15 @@ namespace Defaults::Ship { constexpr float BLINK_INVISIBLE_TIME = 0.1F; // Tiempo invisible (segundos) // Frecuencia total: 0.2s/ciclo = 5 Hz (~15 parpadeos en 3s) + // Cuerpo físico + constexpr float MASS = 10.0F; // Masa de referencia para choques + constexpr float RESTITUTION = 0.6F; // Rebote moderado contra paredes + constexpr float LINEAR_DAMPING = 1.5F; // Fricción exponencial (s⁻¹) + constexpr float ANGULAR_DAMPING = 0.0F; // Rotación 100% por input (no inercial) + + // Empuje visual: escala proporcional a la velocidad (0..200 px/s → 1.0..1.5) + // Mantiene la sensación del Pascal original. + constexpr float VISUAL_PUSH_DIVISOR = 33.33F; // SPEED / DIVISOR = empuje visual + constexpr float VISUAL_SCALE_DIVISOR = 12.0F; // SCALE = 1 + (PUSH / DIVISOR) + } // namespace Defaults::Ship diff --git a/source/core/system/notifier.cpp b/source/core/system/notifier.cpp index b44f756..ccf390d 100644 --- a/source/core/system/notifier.cpp +++ b/source/core/system/notifier.cpp @@ -2,24 +2,15 @@ #include "core/system/notifier.hpp" +#include "core/defaults.hpp" #include "core/rendering/gpu/gpu_frame_renderer.hpp" #include "core/utils/easing.hpp" namespace System { namespace { - // Geometria del cuadre en coordenades lògiques (1280×720). - constexpr float CANVAS_WIDTH = 1280.0F; - constexpr float MARGIN_TOP = 40.0F; - constexpr float PADDING_H = 16.0F; - constexpr float PADDING_V = 10.0F; - constexpr float BORDER_THICKNESS = 2.0F; - constexpr float TEXT_SCALE = 0.55F; - constexpr float TEXT_SPACING = 2.0F; - constexpr float BORDER_BRIGHTNESS = 1.0F; - - // Cinemàtica del slide. - constexpr float SLIDE_DURATION_S = 0.30F; + // Alias d'àmbit local per accedir compactament als defaults del notifier. + namespace Cfg = Defaults::Notifier; // Conversió color SDL → float [0,1]. constexpr auto toUnit(Uint8 v) -> float { @@ -47,14 +38,6 @@ namespace System { .b = toUnit(c.b) * DARKEN, .a = BG_ALPHA}; } - - // Presets per als atajos semàntics. - constexpr SDL_Color COLOR_INFO{.r = 80, .g = 230, .b = 255, .a = 255}; - constexpr SDL_Color COLOR_WARN{.r = 255, .g = 180, .b = 40, .a = 255}; - constexpr SDL_Color COLOR_EXIT{.r = 255, .g = 80, .b = 80, .a = 255}; - constexpr float DURATION_INFO = 2.0F; - constexpr float DURATION_WARN = 3.0F; - constexpr float DURATION_EXIT = 3.0F; } // namespace std::unique_ptr Notifier::instance; @@ -79,15 +62,15 @@ namespace System { hold_remaining_s_ = duration_s; current_is_exit_ = false; - const float TEXT_W = Graphics::VectorText::getTextWidth(text, TEXT_SCALE, TEXT_SPACING); - const float TEXT_H = Graphics::VectorText::getTextHeight(TEXT_SCALE); + const float TEXT_W = Graphics::VectorText::getTextWidth(text, Cfg::TEXT_SCALE, Cfg::TEXT_SPACING); + const float TEXT_H = Graphics::VectorText::getTextHeight(Cfg::TEXT_SCALE); - box_w_ = TEXT_W + (PADDING_H * 2.0F); - box_h_ = TEXT_H + (PADDING_V * 2.0F); - text_x_ = (CANVAS_WIDTH - TEXT_W) * 0.5F; + box_w_ = TEXT_W + (Cfg::PADDING_H * 2.0F); + box_h_ = TEXT_H + (Cfg::PADDING_V * 2.0F); + text_x_ = (Cfg::CANVAS_WIDTH - TEXT_W) * 0.5F; - y_on_ = MARGIN_TOP; - y_off_ = -(box_h_ + BORDER_THICKNESS); + y_on_ = Cfg::MARGIN_TOP; + y_off_ = -(box_h_ + Cfg::BORDER_THICKNESS); // Si ja es veu, reseteja el slide-in des de la posició actual perquè // la transició sembli continua. Si està amagat, arrenc des de fora. @@ -96,13 +79,13 @@ namespace System { } status_ = Status::ENTERING; slide_elapsed_s_ = 0.0F; - text_scale_ = TEXT_SCALE; + text_scale_ = Cfg::TEXT_SCALE; } - void Notifier::notifyInfo(const std::string& text) { notify(text, COLOR_INFO, DURATION_INFO); } - void Notifier::notifyWarn(const std::string& text) { notify(text, COLOR_WARN, DURATION_WARN); } + void Notifier::notifyInfo(const std::string& text) { notify(text, Cfg::COLOR_INFO, Cfg::DURATION_INFO); } + void Notifier::notifyWarn(const std::string& text) { notify(text, Cfg::COLOR_WARN, Cfg::DURATION_WARN); } void Notifier::notifyExit(const std::string& text) { - notify(text, COLOR_EXIT, DURATION_EXIT); + notify(text, Cfg::COLOR_EXIT, Cfg::DURATION_EXIT); current_is_exit_ = true; // notify() ho ha posat a false; restaurem. } @@ -110,12 +93,12 @@ namespace System { switch (status_) { case Status::ENTERING: { slide_elapsed_s_ += delta_time; - if (slide_elapsed_s_ >= SLIDE_DURATION_S) { + if (slide_elapsed_s_ >= Cfg::SLIDE_DURATION_S) { y_current_ = y_on_; status_ = Status::HOLDING; slide_elapsed_s_ = 0.0F; } else { - const float T = slide_elapsed_s_ / SLIDE_DURATION_S; + const float T = slide_elapsed_s_ / Cfg::SLIDE_DURATION_S; const float K = Utils::Easing::outCubic(T); y_current_ = y_off_ + ((y_on_ - y_off_) * K); } @@ -131,11 +114,11 @@ namespace System { } case Status::EXITING: { slide_elapsed_s_ += delta_time; - if (slide_elapsed_s_ >= SLIDE_DURATION_S) { + if (slide_elapsed_s_ >= Cfg::SLIDE_DURATION_S) { y_current_ = y_off_; status_ = Status::HIDDEN; } else { - const float T = slide_elapsed_s_ / SLIDE_DURATION_S; + const float T = slide_elapsed_s_ / Cfg::SLIDE_DURATION_S; const float K = Utils::Easing::inCubic(T); y_current_ = y_on_ + ((y_off_ - y_on_) * K); } @@ -152,7 +135,7 @@ namespace System { return; } - const float BOX_X = (CANVAS_WIDTH - box_w_) * 0.5F; + const float BOX_X = (Cfg::CANVAS_WIDTH - box_w_) * 0.5F; const float BOX_Y = y_current_; const UnitRGBA TC = textColorFloat(current_color_); const UnitRGBA BG = bgColorFloat(current_color_); @@ -167,19 +150,19 @@ namespace System { const float Y1 = BOX_Y; const float X2 = BOX_X + box_w_; const float Y2 = BOX_Y + box_h_; - gpu->pushLine(X1, Y1, X2, Y1, BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // top - gpu->pushLine(X1, Y2, X2, Y2, BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // bottom - gpu->pushLine(X1, Y1, X1, Y2, BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // left - gpu->pushLine(X2, Y1, X2, Y2, BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // right + gpu->pushLine(X1, Y1, X2, Y1, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // top + gpu->pushLine(X1, Y2, X2, Y2, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // bottom + gpu->pushLine(X1, Y1, X1, Y2, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // left + gpu->pushLine(X2, Y1, X2, Y2, Cfg::BORDER_THICKNESS, TC.r, TC.g, TC.b, TC.a); // right // 3. Text centrat dins la caixa, amb color explícit (l'alpha != 0 // li diu al renderShape que no agafe l'oscil·lador global de color). - const float TEXT_Y = BOX_Y + PADDING_V; + const float TEXT_Y = BOX_Y + Cfg::PADDING_V; text_.render(current_text_, Vec2{.x = text_x_, .y = TEXT_Y}, text_scale_, - TEXT_SPACING, - BORDER_BRIGHTNESS, + Cfg::TEXT_SPACING, + Cfg::BORDER_BRIGHTNESS, current_color_); } diff --git a/source/game/entities/bullet.cpp b/source/game/entities/bullet.cpp index 6434263..32186b4 100644 --- a/source/game/entities/bullet.cpp +++ b/source/game/entities/bullet.cpp @@ -16,12 +16,6 @@ #include "core/types.hpp" #include "game/constants.hpp" -namespace { - // Velocidad escalar de las balas (px/s). Conserva el feel del Pascal original - // (7 px/frame × 20 FPS = 140 px/s). - constexpr float BULLET_SPEED = 140.0F; -} // namespace - Bullet::Bullet(Rendering::Renderer* renderer) : Entity(renderer) { // Brightness específico para balas @@ -80,7 +74,7 @@ void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) { body_.angle = angle; const float DIR_X = std::cos(angle - (Constants::PI / 2.0F)); const float DIR_Y = std::sin(angle - (Constants::PI / 2.0F)); - body_.velocity = Vec2{.x = DIR_X * BULLET_SPEED, .y = DIR_Y * BULLET_SPEED}; + body_.velocity = Vec2{.x = DIR_X * Defaults::Game::BULLET_SPEED, .y = DIR_Y * Defaults::Game::BULLET_SPEED}; body_.angular_velocity = 0.0F; body_.clearAccumulators(); diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index 1617366..033b942 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -41,16 +41,16 @@ namespace { Enemy::Enemy(Rendering::Renderer* renderer) : Entity(renderer), - tracking_strength_(0.5F) { + tracking_strength_(Defaults::Enemies::Cuadrado::TRACKING_STRENGTH) { brightness_ = Defaults::Brightness::ENEMIC; // Configuración del cuerpo físico — defaults para enemy genérico. // init() ajusta velocidad y masa según el tipo (Pentagon/Quadrat/Molinillo). - body_.setMass(5.0F); // Más liviano que la nave (10.0) - body_.radius = 0.0F; // 0 hasta spawn (no colisiona inactivo) - body_.restitution = 1.0F; // Rebote elástico perfecto contra paredes - body_.linear_damping = 0.0F; // Sin fricción: mantienen velocidad - body_.angular_damping = 0.0F; // Idem + body_.setMass(Defaults::Enemies::Body::DEFAULT_MASS); + body_.radius = 0.0F; // 0 hasta spawn (no colisiona inactivo) + body_.restitution = Defaults::Enemies::Body::RESTITUTION; + body_.linear_damping = Defaults::Enemies::Body::LINEAR_DAMPING; + body_.angular_damping = Defaults::Enemies::Body::ANGULAR_DAMPING; } void Enemy::init(EnemyType type, const Vec2* ship_pos) { @@ -60,7 +60,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) { float base_speed = 0.0F; float drotacio_min = 0.0F; float drotacio_max = 0.0F; - float type_mass = 5.0F; + float type_mass = Defaults::Enemies::Body::DEFAULT_MASS; switch (type_) { case EnemyType::PENTAGON: @@ -68,7 +68,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) { base_speed = Defaults::Enemies::Pentagon::VELOCITAT; drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN; drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX; - type_mass = 5.0F; + type_mass = Defaults::Enemies::Pentagon::MASS; break; case EnemyType::QUADRAT: @@ -76,7 +76,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) { base_speed = Defaults::Enemies::Cuadrado::VELOCITAT; drotacio_min = Defaults::Enemies::Cuadrado::DROTACIO_MIN; drotacio_max = Defaults::Enemies::Cuadrado::DROTACIO_MAX; - type_mass = 8.0F; // Más pesado, "tanque" + type_mass = Defaults::Enemies::Cuadrado::MASS; tracking_timer_ = 0.0F; break; @@ -85,7 +85,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) { base_speed = Defaults::Enemies::Molinillo::VELOCITAT; drotacio_min = Defaults::Enemies::Molinillo::DROTACIO_MIN; drotacio_max = Defaults::Enemies::Molinillo::DROTACIO_MAX; - type_mass = 4.0F; // Más liviano, ágil + type_mass = Defaults::Enemies::Molinillo::MASS; break; default: @@ -268,9 +268,8 @@ void Enemy::behaviorPentagon(float delta_time) { // Probabilidad de zigzag por segundo (calibrada para sensación equivalente // a la versión vieja que disparaba en cada toque de pared). - constexpr float ZIGZAG_PROB_PER_SECOND = 0.8F; const float RAND_VAL = static_cast(std::rand()) / static_cast(RAND_MAX); - if (RAND_VAL < ZIGZAG_PROB_PER_SECOND * delta_time) { + if (RAND_VAL < Defaults::Enemies::Pentagon::ZIGZAG_PROB_PER_SECOND * delta_time) { const float CURRENT_ANGLE = velocityToAngle(body_.velocity); const float DELTA = (static_cast(std::rand()) / static_cast(RAND_MAX)) * Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX; diff --git a/source/game/entities/ship.cpp b/source/game/entities/ship.cpp index f4b129e..f021eb4 100644 --- a/source/game/entities/ship.cpp +++ b/source/game/entities/ship.cpp @@ -25,11 +25,11 @@ Ship::Ship(Rendering::Renderer* renderer, const char* shape_file) brightness_ = Defaults::Brightness::NAU; // Configuración del cuerpo físico - body_.setMass(10.0F); // Masa de referencia para choques - body_.radius = Defaults::Entities::SHIP_RADIUS; // Radio de colisión - body_.restitution = 0.6F; // Rebote moderado contra paredes - body_.linear_damping = 1.5F; // Fricción exponencial (s⁻¹) - body_.angular_damping = 0.0F; // La rotación es 100% por input, no inercial + body_.setMass(Defaults::Ship::MASS); + body_.radius = Defaults::Entities::SHIP_RADIUS; + body_.restitution = Defaults::Ship::RESTITUTION; + body_.linear_damping = Defaults::Ship::LINEAR_DAMPING; + body_.angular_damping = Defaults::Ship::ANGULAR_DAMPING; // Cargar shape compartida desde archivo shape_ = Graphics::ShapeLoader::load(shape_file); @@ -154,8 +154,8 @@ void Ship::draw() const { // Efecto visual de empuje: escala proporcional a la velocidad. // 0..200 px/s → escala 1.0..1.5 (manteniendo la sensación del Pascal original). const float SPEED = getSpeed(); - const float VISUAL_PUSH = SPEED / 33.33F; - const float SCALE = 1.0F + (VISUAL_PUSH / 12.0F); + const float VISUAL_PUSH = SPEED / Defaults::Ship::VISUAL_PUSH_DIVISOR; + const float SCALE = 1.0F + (VISUAL_PUSH / Defaults::Ship::VISUAL_SCALE_DIVISOR); Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, Defaults::Palette::SHIP); } diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 2c7170c..136b833 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -674,8 +674,8 @@ void GameScene::drawScoreboard() { std::string text = buildScoreboard(); // Parámetros de renderització - const float SCALE = 0.85F; - const float SPACING = 0.0F; + const float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE; + const float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING; // Calcular centro de la zona del marcador const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD; diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index a5e9c04..480315a 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -13,87 +13,88 @@ namespace Systems::InitHud { -auto computeRangeProgress(float global_progress, - float ratio_init, - float ratio_end) -> float { - if (ratio_init >= ratio_end) { - return (global_progress >= ratio_end) ? 1.0F : 0.0F; - } - if (global_progress < ratio_init) { - return 0.0F; - } - if (global_progress > ratio_end) { - return 1.0F; - } - return (global_progress - ratio_init) / (ratio_end - ratio_init); -} - -auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2 { - const float EASED = Easing::easeOutQuad(progress); - const SDL_FRect& zone = Defaults::Zones::PLAYAREA; - // Y inicial: 50 px bajo la zona de juego. - const float Y_INI = zone.y + zone.h + 50.0F; - const float Y_ANIM = Y_INI + ((final_position.y - Y_INI) * EASED); - return Vec2{.x = final_position.x, .y = Y_ANIM}; -} - -void drawBordersAnimated(Rendering::Renderer* renderer, float progress) { - const SDL_FRect& zone = Defaults::Zones::PLAYAREA; - const float EASED = Easing::easeOutQuad(progress); - - const int X1 = static_cast(zone.x); - const int Y1 = static_cast(zone.y); - const int X2 = static_cast(zone.x + zone.w); - const int Y2 = static_cast(zone.y + zone.h); - const int CX = (X1 + X2) / 2; - - constexpr float PHASE_1_END = 0.33F; - constexpr float PHASE_2_END = 0.66F; - - // Fase 1: línea superior crece desde el centro hacia los lados. - if (EASED > 0.0F) { - const float P = std::min(EASED / PHASE_1_END, 1.0F); - const int X_LEFT = static_cast(CX - ((CX - X1) * P)); - const int X_RIGHT = static_cast(CX + ((X2 - CX) * P)); - Rendering::linea(renderer, CX, Y1, X_LEFT, Y1); - Rendering::linea(renderer, CX, Y1, X_RIGHT, Y1); + auto computeRangeProgress(float global_progress, + float ratio_init, + float ratio_end) -> float { + if (ratio_init >= ratio_end) { + return (global_progress >= ratio_end) ? 1.0F : 0.0F; + } + if (global_progress < ratio_init) { + return 0.0F; + } + if (global_progress > ratio_end) { + return 1.0F; + } + return (global_progress - ratio_init) / (ratio_end - ratio_init); } - // Fase 2: laterales bajan. - if (EASED > PHASE_1_END) { - const float P = std::min((EASED - PHASE_1_END) / (PHASE_2_END - PHASE_1_END), 1.0F); - const int Y_BOTTOM = static_cast(Y1 + ((Y2 - Y1) * P)); - Rendering::linea(renderer, X1, Y1, X1, Y_BOTTOM); - Rendering::linea(renderer, X2, Y1, X2, Y_BOTTOM); + auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2 { + const float EASED = Easing::easeOutQuad(progress); + const SDL_FRect& zone = Defaults::Zones::PLAYAREA; + // Y inicial: 50 px bajo la zona de juego. + const float Y_INI = zone.y + zone.h + 50.0F; + const float Y_ANIM = Y_INI + ((final_position.y - Y_INI) * EASED); + return Vec2{.x = final_position.x, .y = Y_ANIM}; } - // Fase 3: línea inferior crece desde los lados hacia el centro. - if (EASED > PHASE_2_END) { - const float P = (EASED - PHASE_2_END) / (1.0F - PHASE_2_END); - const int X_LEFT = static_cast(X1 + ((CX - X1) * P)); - const int X_RIGHT = static_cast(X2 - ((X2 - CX) * P)); - Rendering::linea(renderer, X1, Y2, X_LEFT, Y2); - Rendering::linea(renderer, X2, Y2, X_RIGHT, Y2); + void drawBordersAnimated(Rendering::Renderer* renderer, float progress) { + const SDL_FRect& zone = Defaults::Zones::PLAYAREA; + const float EASED = Easing::easeOutQuad(progress); + + const int X1 = static_cast(zone.x); + const int Y1 = static_cast(zone.y); + const int X2 = static_cast(zone.x + zone.w); + const int Y2 = static_cast(zone.y + zone.h); + const int CX = (X1 + X2) / 2; + + constexpr float PHASE_1_END = 0.33F; + constexpr float PHASE_2_END = 0.66F; + + // Fase 1: línea superior crece desde el centro hacia los lados. + if (EASED > 0.0F) { + const float P = std::min(EASED / PHASE_1_END, 1.0F); + const int X_LEFT = static_cast(CX - ((CX - X1) * P)); + const int X_RIGHT = static_cast(CX + ((X2 - CX) * P)); + Rendering::linea(renderer, CX, Y1, X_LEFT, Y1); + Rendering::linea(renderer, CX, Y1, X_RIGHT, Y1); + } + + // Fase 2: laterales bajan. + if (EASED > PHASE_1_END) { + const float P = std::min((EASED - PHASE_1_END) / (PHASE_2_END - PHASE_1_END), 1.0F); + const int Y_BOTTOM = static_cast(Y1 + ((Y2 - Y1) * P)); + Rendering::linea(renderer, X1, Y1, X1, Y_BOTTOM); + Rendering::linea(renderer, X2, Y1, X2, Y_BOTTOM); + } + + // Fase 3: línea inferior crece desde los lados hacia el centro. + if (EASED > PHASE_2_END) { + const float P = (EASED - PHASE_2_END) / (1.0F - PHASE_2_END); + const int X_LEFT = static_cast(X1 + ((CX - X1) * P)); + const int X_RIGHT = static_cast(X2 - ((X2 - CX) * P)); + Rendering::linea(renderer, X1, Y2, X_LEFT, Y2); + Rendering::linea(renderer, X2, Y2, X_RIGHT, Y2); + } } -} -void drawScoreboardAnimated(const Graphics::VectorText& text, - const std::string& scoreboard_text, - float progress) { - const float EASED = Easing::easeOutQuad(progress); + void drawScoreboardAnimated(const Graphics::VectorText& text, + const std::string& scoreboard_text, + float progress) { + const float EASED = Easing::easeOutQuad(progress); - constexpr float SCALE = 0.85F; - constexpr float SPACING = 0.0F; - const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD; - const float CENTRE_X = scoreboard_zone.w / 2.0F; - const float Y_FINAL = scoreboard_zone.y + (scoreboard_zone.h / 2.0F); - // Posición inicial: fuera de la pantalla por debajo. - const auto Y_INI = static_cast(Defaults::Game::HEIGHT); - const float Y_ANIM = Y_INI + ((Y_FINAL - Y_INI) * EASED); + constexpr float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE; + constexpr float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING; + const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD; + const float CENTRE_X = scoreboard_zone.w / 2.0F; + const float Y_FINAL = scoreboard_zone.y + (scoreboard_zone.h / 2.0F); + // Posición inicial: fuera de la pantalla por debajo. + const auto Y_INI = static_cast(Defaults::Game::HEIGHT); + const float Y_ANIM = Y_INI + ((Y_FINAL - Y_INI) * EASED); - text.renderCentered(scoreboard_text, - Vec2{.x = CENTRE_X, .y = Y_ANIM}, - SCALE, SPACING); -} + text.renderCentered(scoreboard_text, + Vec2{.x = CENTRE_X, .y = Y_ANIM}, + SCALE, + SPACING); + } } // namespace Systems::InitHud From 7139dea7f6412c61e0b22fc7b36b547bb3524c57 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 09:45:55 +0200 Subject: [PATCH 08/27] refactor(defaults): centralitza init hud, tips, hit timer, line thickness i debug overlay --- source/core/defaults/game.hpp | 10 ++- source/core/defaults/hud.hpp | 28 ++++++++ source/core/defaults/physics.hpp | 3 + source/core/defaults/rendering.hpp | 4 ++ source/core/rendering/line_renderer.cpp | 80 ++++++++++++----------- source/core/system/debug_overlay.cpp | 38 +++++------ source/game/scenes/game_scene.cpp | 15 +++-- source/game/systems/init_hud_animator.cpp | 8 +-- 8 files changed, 111 insertions(+), 75 deletions(-) diff --git a/source/core/defaults/game.hpp b/source/core/defaults/game.hpp index 64d86ab..bc85c45 100644 --- a/source/core/defaults/game.hpp +++ b/source/core/defaults/game.hpp @@ -10,9 +10,13 @@ namespace Defaults::Game { constexpr int HEIGHT = 720; // Regles de partida - constexpr int STARTING_LIVES = 3; // Initial lives - constexpr float DEATH_DURATION = 3.0F; // Seconds of death animation - constexpr float GAME_OVER_DURATION = 5.0F; // Seconds to display game over + constexpr int STARTING_LIVES = 3; // Initial lives + constexpr float DEATH_DURATION = 3.0F; // Seconds of death animation + constexpr float GAME_OVER_DURATION = 5.0F; // Seconds to display game over + + // Valores centinela del temporitzador de mort per-jugador. + constexpr float HIT_TIMER_INACTIVE_PLAYER = 999.0F; // Jugador permanentment inactiu + constexpr float HIT_TIMER_TRIGGER_DEATH = 0.001F; // Trigger inicial post-impacte (>0 sense disparar regla) constexpr float COLLISION_SHIP_ENEMY_AMPLIFIER = 0.80F; // 80% hitbox (generous) constexpr float COLLISION_BULLET_ENEMY_AMPLIFIER = 1.15F; // 115% hitbox (generous) diff --git a/source/core/defaults/hud.hpp b/source/core/defaults/hud.hpp index f97843c..072a536 100644 --- a/source/core/defaults/hud.hpp +++ b/source/core/defaults/hud.hpp @@ -10,4 +10,32 @@ namespace Defaults::Hud { constexpr float SCOREBOARD_TEXT_SCALE = 0.85F; constexpr float SCOREBOARD_TEXT_SPACING = 0.0F; + // Animación de entrada del HUD (init_hud_animator). + namespace InitAnim { + // Spawn vertical de la nave: 50 px bajo la PLAYAREA (sale desde fuera). + constexpr float SHIP_SPAWN_Y_OFFSET = 50.0F; + + // Bordes: ratios de las tres fases (top → laterales → bottom). + constexpr float BORDER_PHASE_1_END = 0.33F; // Fin de la fase top + constexpr float BORDER_PHASE_2_END = 0.66F; // Fin de la fase laterales + } // namespace InitAnim + + // Indicadores ("tips") sobre los enemigos enganchados a la nave. + // Offset local al frame de la nave (apunta hacia delante, eje Y negativo). + namespace Tips { + constexpr float LOCAL_X = 0.0F; + constexpr float LOCAL_Y = -12.0F; + } // namespace Tips + + // Overlay de debug (FPS, métriques) en coordenades lògiques (1280×720). + namespace DebugOverlay { + constexpr float X = 12.0F; + constexpr float Y_FPS = 12.0F; + constexpr float LINE_HEIGHT = 18.0F; // separació entre línies (scale 0.4 → ~16 px alt) + constexpr float TEXT_SCALE = 0.4F; + constexpr float TEXT_SPACING = 2.0F; + constexpr float BRIGHTNESS = 1.0F; + constexpr float FPS_UPDATE_INTERVAL = 0.5F; // Cadencia d'actualització del FPS visible + } // namespace DebugOverlay + } // namespace Defaults::Hud diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 77704f5..7a204ba 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -26,6 +26,9 @@ namespace Defaults::Physics { constexpr float FACTOR_HERENCIA_MAX = 1.0F; // Màxim 100% del drotacio heredat constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²) + // Velocity heredada de la nau a l'explosió (80% del feel original). + constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F; + // Angular velocity sin for trajectory inheritance // Excess above this threshold is converted to tangential linear velocity // Prevents "vortex trap" problem with high-rotation enemies diff --git a/source/core/defaults/rendering.hpp b/source/core/defaults/rendering.hpp index 0480516..c87d119 100644 --- a/source/core/defaults/rendering.hpp +++ b/source/core/defaults/rendering.hpp @@ -11,6 +11,10 @@ namespace Defaults::Rendering { constexpr int VSYNC_DEFAULT = 1; // 0=disabled, 1=enabled constexpr int ANTIALIAS_DEFAULT = 1; // 0=disabled, 1=enabled (AA geomètric a les línies) + // Grosor global per defecte de les línies. 1.5 dóna línia visible i crujent; + // 1.0 es veu massa fi en pantalles grans. Configurable via setLineThickness. + constexpr float LINE_THICKNESS_DEFAULT = 1.5F; + // Resolució del render target offscreen. El tamany lògic del joc roman a // 1280×720 (coordenades dels objectes); aquesta és la resolució física a // la qual es rasteritzen les línies abans de la composició final. diff --git a/source/core/rendering/line_renderer.cpp b/source/core/rendering/line_renderer.cpp index 1e27e94..1f079b3 100644 --- a/source/core/rendering/line_renderer.cpp +++ b/source/core/rendering/line_renderer.cpp @@ -3,52 +3,56 @@ #include "core/rendering/line_renderer.hpp" +#include "core/defaults.hpp" + namespace Rendering { -// Color global compartido para líneas sin paleta propia (HUD, debug, texto -// genérico). Equivale al "color máximo" de la antigua oscilación CPU: verde -// fósforo CRT. El pulso de brillo lo aplica ahora el shader de postpro. -SDL_Color g_current_line_color = {100, 255, 100, 255}; + // Color global compartido para líneas sin paleta propia (HUD, debug, texto + // genérico). Equivale al "color máximo" de la antigua oscilación CPU: verde + // fósforo CRT. El pulso de brillo lo aplica ahora el shader de postpro. + SDL_Color g_current_line_color = {100, 255, 100, 255}; -// Grosor global por defecto. Configurable via setLineThickness. -// 1.5 da una línea visible y crujiente; 1.0 se ve demasiado fino en pantallas grandes. -float g_current_line_thickness = 1.5F; + // Grosor global por defecto. Configurable via setLineThickness. + float g_current_line_thickness = Defaults::Rendering::LINE_THICKNESS_DEFAULT; -void linea(Renderer* renderer, - int x1, int y1, int x2, int y2, - float brightness, - float thickness, - SDL_Color color) { - if (renderer == nullptr) { - return; + void linea(Renderer* renderer, + int x1, + int y1, + int x2, + int y2, + float brightness, + float thickness, + SDL_Color color) { + if (renderer == nullptr) { + return; + } + + // Coords lógicas (1280×720). El shader hace el mapeo a NDC; el viewport + // del SDLManager hace el letterbox a píxeles físicos. + const auto FX1 = static_cast(x1); + const auto FY1 = static_cast(y1); + const auto FX2 = static_cast(x2); + const auto FY2 = static_cast(y2); + + // color.alpha==0 → usar color global (verde fósforo). alpha>0 → color directo. + const SDL_Color SOURCE = (color.a > 0) ? color : g_current_line_color; + const float R = (static_cast(SOURCE.r) * brightness) / 255.0F; + const float G = (static_cast(SOURCE.g) * brightness) / 255.0F; + const float B = (static_cast(SOURCE.b) * brightness) / 255.0F; + + const float W = (thickness > 0.0F) ? thickness : g_current_line_thickness; + + renderer->pushLine(FX1, FY1, FX2, FY2, W, R, G, B, 1.0F); } - // Coords lógicas (1280×720). El shader hace el mapeo a NDC; el viewport - // del SDLManager hace el letterbox a píxeles físicos. - const auto FX1 = static_cast(x1); - const auto FY1 = static_cast(y1); - const auto FX2 = static_cast(x2); - const auto FY2 = static_cast(y2); + void setLineColor(SDL_Color color) { g_current_line_color = color; } - // color.alpha==0 → usar color global (verde fósforo). alpha>0 → color directo. - const SDL_Color SOURCE = (color.a > 0) ? color : g_current_line_color; - const float R = (static_cast(SOURCE.r) * brightness) / 255.0F; - const float G = (static_cast(SOURCE.g) * brightness) / 255.0F; - const float B = (static_cast(SOURCE.b) * brightness) / 255.0F; - - const float W = (thickness > 0.0F) ? thickness : g_current_line_thickness; - - renderer->pushLine(FX1, FY1, FX2, FY2, W, R, G, B, 1.0F); -} - -void setLineColor(SDL_Color color) { g_current_line_color = color; } - -void setLineThickness(float thickness) { - if (thickness > 0.0F) { - g_current_line_thickness = thickness; + void setLineThickness(float thickness) { + if (thickness > 0.0F) { + g_current_line_thickness = thickness; + } } -} -auto getLineThickness() -> float { return g_current_line_thickness; } + auto getLineThickness() -> float { return g_current_line_thickness; } } // namespace Rendering diff --git a/source/core/system/debug_overlay.cpp b/source/core/system/debug_overlay.cpp index e8f2577..5434143 100644 --- a/source/core/system/debug_overlay.cpp +++ b/source/core/system/debug_overlay.cpp @@ -4,21 +4,13 @@ #include +#include "core/defaults.hpp" #include "core/types.hpp" namespace System { namespace { - // Posición y tamaño del overlay en coordenadas lógicas (1280×720). - constexpr float OVERLAY_X = 12.0F; - constexpr float OVERLAY_Y_FPS = 12.0F; - constexpr float OVERLAY_LINE_HEIGHT = 18.0F; // separación entre líneas (scale 0.4 → ~16 px alto) - constexpr float OVERLAY_SCALE = 0.4F; - constexpr float OVERLAY_SPACING = 2.0F; - constexpr float OVERLAY_BRIGHTNESS = 1.0F; - - // Cadencia de actualización del valor de FPS mostrado. - constexpr float FPS_UPDATE_INTERVAL = 0.5F; + namespace Cfg = Defaults::Hud::DebugOverlay; } // namespace DebugOverlay::DebugOverlay(Rendering::Renderer* renderer, @@ -30,7 +22,7 @@ namespace System { fps_accumulator_ += delta_time; fps_frame_count_++; - if (fps_accumulator_ >= FPS_UPDATE_INTERVAL) { + if (fps_accumulator_ >= Cfg::FPS_UPDATE_INTERVAL) { fps_display_ = static_cast(fps_frame_count_ / fps_accumulator_); fps_frame_count_ = 0; fps_accumulator_ = 0.0F; @@ -47,20 +39,20 @@ namespace System { const std::string AA_TEXT = std::string("AA: ") + (rendering_cfg_->antialias == 1 ? "ON" : "OFF"); text_.render(FPS_TEXT, - Vec2{.x = OVERLAY_X, .y = OVERLAY_Y_FPS}, - OVERLAY_SCALE, - OVERLAY_SPACING, - OVERLAY_BRIGHTNESS); + Vec2{.x = Cfg::X, .y = Cfg::Y_FPS}, + Cfg::TEXT_SCALE, + Cfg::TEXT_SPACING, + Cfg::BRIGHTNESS); text_.render(VSYNC_TEXT, - Vec2{.x = OVERLAY_X, .y = OVERLAY_Y_FPS + OVERLAY_LINE_HEIGHT}, - OVERLAY_SCALE, - OVERLAY_SPACING, - OVERLAY_BRIGHTNESS); + Vec2{.x = Cfg::X, .y = Cfg::Y_FPS + Cfg::LINE_HEIGHT}, + Cfg::TEXT_SCALE, + Cfg::TEXT_SPACING, + Cfg::BRIGHTNESS); text_.render(AA_TEXT, - Vec2{.x = OVERLAY_X, .y = OVERLAY_Y_FPS + (2.0F * OVERLAY_LINE_HEIGHT)}, - OVERLAY_SCALE, - OVERLAY_SPACING, - OVERLAY_BRIGHTNESS); + Vec2{.x = Cfg::X, .y = Cfg::Y_FPS + (2.0F * Cfg::LINE_HEIGHT)}, + Cfg::TEXT_SCALE, + Cfg::TEXT_SPACING, + Cfg::BRIGHTNESS); } } // namespace System diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 136b833..9bdb281 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -107,8 +107,8 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context) } else { // Jugador inactiu: marcar como a mort permanent ships_[i].markHit(); - hit_timer_per_player_[i] = 999.0F; // Valor sentinella (permanent inactiu) - lives_per_player_[i] = 0; // Sin vides + hit_timer_per_player_[i] = Defaults::Game::HIT_TIMER_INACTIVE_PLAYER; + lives_per_player_[i] = 0; // Sin vides std::cout << "[GameScene] Jugador " << (i + 1) << " inactiu\n"; } } @@ -628,8 +628,9 @@ void GameScene::tocado(uint8_t player_id) { const Vec2& ship_pos = ships_[player_id].getCenter(); float ship_angle = ships_[player_id].getAngle(); Vec2 vel_nau = ships_[player_id].getVelocityVector(); - // Reduir a 80% la velocity heretada per la ship (més realista) - Vec2 vel_nau_80 = {.x = vel_nau.x * 0.8F, .y = vel_nau.y * 0.8F}; + // Reduir la velocity heretada per la ship segons defaults (més realista) + constexpr float INHERIT = Defaults::Physics::Debris::SHIP_VELOCITY_INHERITANCE; + Vec2 vel_nau_80 = {.x = vel_nau.x * INHERIT, .y = vel_nau.y * INHERIT}; debris_manager_.explode( ships_[player_id].getShape(), // Ship shape (3 lines) @@ -646,7 +647,7 @@ void GameScene::tocado(uint8_t player_id) { ); // Start death timer (non-zero to avoid re-triggering) - hit_timer_per_player_[player_id] = 0.001F; + hit_timer_per_player_[player_id] = Defaults::Game::HIT_TIMER_TRIGGER_DEATH; } // Phase 2 is automatic (debris updates in update()) // Phase 3 is handled in update() when hit_timer_per_player_ >= DEATH_DURATION @@ -828,8 +829,8 @@ void GameScene::fireBullet(uint8_t player_id) { const Vec2& ship_centre = ships_[player_id].getCenter(); float ship_angle = ships_[player_id].getAngle(); - constexpr float LOCAL_TIP_X = 0.0F; - constexpr float LOCAL_TIP_Y = -12.0F; + constexpr float LOCAL_TIP_X = Defaults::Hud::Tips::LOCAL_X; + constexpr float LOCAL_TIP_Y = Defaults::Hud::Tips::LOCAL_Y; float cos_a = std::cos(ship_angle); float sin_a = std::sin(ship_angle); float tip_x = (LOCAL_TIP_X * cos_a) - (LOCAL_TIP_Y * sin_a) + ship_centre.x; diff --git a/source/game/systems/init_hud_animator.cpp b/source/game/systems/init_hud_animator.cpp index 480315a..75e81eb 100644 --- a/source/game/systems/init_hud_animator.cpp +++ b/source/game/systems/init_hud_animator.cpp @@ -31,8 +31,8 @@ namespace Systems::InitHud { auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2 { const float EASED = Easing::easeOutQuad(progress); const SDL_FRect& zone = Defaults::Zones::PLAYAREA; - // Y inicial: 50 px bajo la zona de juego. - const float Y_INI = zone.y + zone.h + 50.0F; + // Y inicial: bajo la zona de juego (sale desde fuera). + const float Y_INI = zone.y + zone.h + Defaults::Hud::InitAnim::SHIP_SPAWN_Y_OFFSET; const float Y_ANIM = Y_INI + ((final_position.y - Y_INI) * EASED); return Vec2{.x = final_position.x, .y = Y_ANIM}; } @@ -47,8 +47,8 @@ namespace Systems::InitHud { const int Y2 = static_cast(zone.y + zone.h); const int CX = (X1 + X2) / 2; - constexpr float PHASE_1_END = 0.33F; - constexpr float PHASE_2_END = 0.66F; + constexpr float PHASE_1_END = Defaults::Hud::InitAnim::BORDER_PHASE_1_END; + constexpr float PHASE_2_END = Defaults::Hud::InitAnim::BORDER_PHASE_2_END; // Fase 1: línea superior crece desde el centro hacia los lados. if (EASED > 0.0F) { From bb21191c5b4e7122275ce7d9e17b64dffc158d4e Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 09:48:20 +0200 Subject: [PATCH 09/27] refactor(defaults): substitueix els 999.0F restants per HIT_TIMER_INACTIVE_PLAYER --- source/game/scenes/game_scene.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 9bdb281..f7cc050 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -201,8 +201,8 @@ void GameScene::stepMidGameJoin() { // Solo se permite join si hay al menos un jugador vivo (no se puede // hacer join en pantalla vacía). const bool ALGU_VIU = - (match_config_.jugador1_actiu && hit_timer_per_player_[0] != 999.0F) || - (match_config_.jugador2_actiu && hit_timer_per_player_[1] != 999.0F); + (match_config_.jugador1_actiu && hit_timer_per_player_[0] != Defaults::Game::HIT_TIMER_INACTIVE_PLAYER) || + (match_config_.jugador2_actiu && hit_timer_per_player_[1] != Defaults::Game::HIT_TIMER_INACTIVE_PLAYER); if (!ALGU_VIU) { return; } @@ -211,7 +211,7 @@ void GameScene::stepMidGameJoin() { for (uint8_t pid = 0; pid < 2; pid++) { const bool ACTIU = (pid == 0) ? match_config_.jugador1_actiu : match_config_.jugador2_actiu; - const bool MUERTO_SIN_VIDAS = hit_timer_per_player_[pid] == 999.0F; + const bool MUERTO_SIN_VIDAS = hit_timer_per_player_[pid] == Defaults::Game::HIT_TIMER_INACTIVE_PLAYER; if (ACTIU && !MUERTO_SIN_VIDAS) { continue; // jugador ya está jugando } @@ -284,7 +284,7 @@ auto GameScene::stepGameOver(float delta_time) -> bool { void GameScene::stepDeathSequence(float delta_time) { bool algun_mort = false; for (uint8_t i = 0; i < 2; i++) { - if (hit_timer_per_player_[i] <= 0.0F || hit_timer_per_player_[i] >= 999.0F) { + if (hit_timer_per_player_[i] <= 0.0F || hit_timer_per_player_[i] >= Defaults::Game::HIT_TIMER_INACTIVE_PLAYER) { continue; } algun_mort = true; @@ -304,7 +304,7 @@ void GameScene::stepDeathSequence(float delta_time) { } // Sin vidas: marcar definitivamente muerto y comprobar transición a CONTINUE. - hit_timer_per_player_[i] = 999.0F; + hit_timer_per_player_[i] = Defaults::Game::HIT_TIMER_INACTIVE_PLAYER; const bool P1_DEAD = !match_config_.jugador1_actiu || lives_per_player_[0] <= 0; const bool P2_DEAD = !match_config_.jugador2_actiu || lives_per_player_[1] <= 0; if (P1_DEAD && P2_DEAD) { From 23bcd0816fbc062e09170ba98b47eef3768a4147 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 09:55:07 +0200 Subject: [PATCH 10/27] =?UTF-8?q?tune(bullet):=20augmenta=20velocitat=20de?= =?UTF-8?q?=20la=20bala=20(=C3=975)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/defaults/game.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/defaults/game.hpp b/source/core/defaults/game.hpp index bc85c45..604448a 100644 --- a/source/core/defaults/game.hpp +++ b/source/core/defaults/game.hpp @@ -24,7 +24,7 @@ namespace Defaults::Game { constexpr bool FRIENDLY_FIRE_ENABLED = true; // Activar friendly fire constexpr float COLLISION_BULLET_PLAYER_AMPLIFIER = 1.0F; // Hitbox exacto (100%) constexpr float BULLET_GRACE_PERIOD = 0.2F; // Inmunidad post-disparo (s) - constexpr float BULLET_SPEED = 140.0F; // Velocidad escalar (px/s). Pascal: 7 px/frame × 20 FPS + constexpr float BULLET_SPEED = 700.0F; // Velocidad escalar (px/s). Pascal: 7 px/frame × 20 FPS // Transición LEVEL_START (mensajes aleatorios PRE-level) constexpr float LEVEL_START_DURATION = 3.0F; // Duración total From d169a1997c7fbe2c1df9cbd046ccd9f9041dd47f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 10:20:42 +0200 Subject: [PATCH 11/27] feat(enemy): afegeix estat "wounded" amb timer i API base (Fase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Defaults::Palette::WOUNDED ({255,215,0}) dorat per a parpadeig - Defaults::Enemies::Wounded::{DURATION, BLINK_HZ} - Enemy: wounded_timer_, wound_expired_this_frame_ - API: herir(), isWounded(), getWoundedTimer(), woundExpiredThisFrame(), consumeWoundExpired(), applyImpulse() - update() decrementa timer i marca expiració al creuar 0 - destruir() reseteja l'estat wounded Sense efectes visuals ni canvis de comportament: cap callsite invoca encara herir() ni applyImpulse(). Build verda i smoke test xvfb OK. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/defaults/enemies.hpp | 6 ++ source/core/defaults/palette.hpp | 1 + source/game/entities/enemy.cpp | 21 ++++ source/game/entities/enemy.hpp | 176 +++++++++++++++++-------------- 4 files changed, 123 insertions(+), 81 deletions(-) diff --git a/source/core/defaults/enemies.hpp b/source/core/defaults/enemies.hpp index d321966..1d6a960 100644 --- a/source/core/defaults/enemies.hpp +++ b/source/core/defaults/enemies.hpp @@ -70,6 +70,12 @@ namespace Defaults::Enemies { constexpr float ROTACIO_ACCEL_MULTIPLIER_MAX = 4.0F; // Max speed multiplier [more dramatic] } // namespace Animation + // Wounded state (entre primer impacto y explosión) + namespace Wounded { + constexpr float DURATION = 1.0F; // Segundos en estado herido antes de explotar + constexpr float BLINK_HZ = 10.0F; // Frecuencia de parpadeo color tipo ↔ dorado + } // namespace Wounded + // Spawn safety and invulnerability system namespace Spawn { // Safe spawn distance from player diff --git a/source/core/defaults/palette.hpp b/source/core/defaults/palette.hpp index 2c3eae5..46753de 100644 --- a/source/core/defaults/palette.hpp +++ b/source/core/defaults/palette.hpp @@ -15,5 +15,6 @@ namespace Defaults::Palette { constexpr SDL_Color PENTAGON = {.r = 120, .g = 170, .b = 255, .a = 255}; // Azul "esquivador" constexpr SDL_Color QUADRAT = {.r = 255, .g = 110, .b = 110, .a = 255}; // Rojo "tank" constexpr SDL_Color MOLINILLO = {.r = 255, .g = 130, .b = 255, .a = 255}; // Magenta agresivo + constexpr SDL_Color WOUNDED = {.r = 255, .g = 215, .b = 0, .a = 255}; // Dorado: enemigo herido } // namespace Defaults::Palette diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index 033b942..a8d9f28 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -177,6 +177,17 @@ void Enemy::update(float delta_time) { return; } + // Decremento de timer "herido"; al cruzar 0 marca expiración para que el + // system layer dispare la explosión diferida. + wound_expired_this_frame_ = false; + if (wounded_timer_ > 0.0F) { + wounded_timer_ -= delta_time; + if (wounded_timer_ <= 0.0F) { + wounded_timer_ = 0.0F; + wound_expired_this_frame_ = true; + } + } + // Decremento de invulnerabilidad + LERP de brightness if (timer_invulnerabilitat_ > 0.0F) { timer_invulnerabilitat_ -= delta_time; @@ -242,6 +253,16 @@ void Enemy::destruir() { body_.velocity = Vec2{}; body_.angular_velocity = 0.0F; body_.radius = 0.0F; // No colisiona mientras está inactivo + wounded_timer_ = 0.0F; + wound_expired_this_frame_ = false; +} + +void Enemy::herir() { + wounded_timer_ = Defaults::Enemies::Wounded::DURATION; +} + +void Enemy::applyImpulse(const Vec2& impulse) { + body_.applyImpulse(impulse); } void Enemy::setVelocity(float speed) { diff --git a/source/game/entities/enemy.hpp b/source/game/entities/enemy.hpp index f0ab5ff..4d24d00 100644 --- a/source/game/entities/enemy.hpp +++ b/source/game/entities/enemy.hpp @@ -19,104 +19,118 @@ enum class EnemyType : uint8_t { // Estado de animación (palpitación + rotación acelerada) struct EnemyAnimation { - // Palpitación (efecto respiración) - bool palpitacio_activa = false; - float palpitacio_fase = 0.0F; - float palpitacio_frequencia = 2.0F; - float palpitacio_amplitud = 0.15F; - float palpitacio_temps_restant = 0.0F; + // Palpitación (efecto respiración) + bool palpitacio_activa = false; + float palpitacio_fase = 0.0F; + float palpitacio_frequencia = 2.0F; + float palpitacio_amplitud = 0.15F; + float palpitacio_temps_restant = 0.0F; - // Aceleración de rotación visual (modulación a largo plazo) - float drotacio_base = 0.0F; - float drotacio_objetivo = 0.0F; - float drotacio_t = 0.0F; - float drotacio_duracio = 0.0F; + // Aceleración de rotación visual (modulación a largo plazo) + float drotacio_base = 0.0F; + float drotacio_objetivo = 0.0F; + float drotacio_t = 0.0F; + float drotacio_duracio = 0.0F; }; class Enemy : public Entities::Entity { - public: - Enemy() - : Entity(nullptr) {} - explicit Enemy(Rendering::Renderer* renderer); + public: + Enemy() + : Entity(nullptr) {} + explicit Enemy(Rendering::Renderer* renderer); - void init() override { init(EnemyType::PENTAGON, nullptr); } - void init(EnemyType type, const Vec2* ship_pos = nullptr); - void update(float delta_time) override; - void postUpdate(float delta_time) override; - void draw() const override; + void init() override { init(EnemyType::PENTAGON, nullptr); } + void init(EnemyType type, const Vec2* ship_pos = nullptr); + void update(float delta_time) override; + void postUpdate(float delta_time) override; + void draw() const override; - // Override: Interfaz de Entity - [[nodiscard]] auto isActive() const -> bool override { return esta_; } + // Override: Interfaz de Entity + [[nodiscard]] auto isActive() const -> bool override { return esta_; } - // Override: Interfaz de colisión - [[nodiscard]] auto getCollisionRadius() const -> float override { - return Defaults::Entities::ENEMY_RADIUS; - } - [[nodiscard]] auto isCollidable() const -> bool override { - return esta_ && timer_invulnerabilitat_ <= 0.0F; - } + // Override: Interfaz de colisión + [[nodiscard]] auto getCollisionRadius() const -> float override { + return Defaults::Entities::ENEMY_RADIUS; + } + [[nodiscard]] auto isCollidable() const -> bool override { + return esta_ && timer_invulnerabilitat_ <= 0.0F; + } - // Marcar destruido (desactiva el cuerpo físicamente: radius=0) - void destruir(); + // Marcar destruido (desactiva el cuerpo físicamente: radius=0) + void destruir(); - // Getters - [[nodiscard]] auto getRotationDelta() const -> float { return drotacio_; } - [[nodiscard]] auto getVelocityVector() const -> Vec2 { return body_.velocity; } + // Getters + [[nodiscard]] auto getRotationDelta() const -> float { return drotacio_; } + [[nodiscard]] auto getVelocityVector() const -> Vec2 { return body_.velocity; } - // Set ship position reference for tracking behavior - void setShipPosition(const Vec2* ship_pos) { ship_position_ = ship_pos; } + // Set ship position reference for tracking behavior + void setShipPosition(const Vec2* ship_pos) { ship_position_ = ship_pos; } - // Stage system API (base stats) - [[nodiscard]] auto getBaseVelocity() const -> float; - [[nodiscard]] auto getBaseRotation() const -> float; - [[nodiscard]] auto getType() const -> EnemyType { return type_; } + // Stage system API (base stats) + [[nodiscard]] auto getBaseVelocity() const -> float; + [[nodiscard]] auto getBaseRotation() const -> float; + [[nodiscard]] auto getType() const -> EnemyType { return type_; } - // Setters para multiplicadores de dificultad (stage system). - // Establecen la velocidad escalar deseada manteniendo la dirección - // actual del body_.velocity. - void setVelocity(float speed); - void setRotation(float rot) { - drotacio_ = rot; - animacio_.drotacio_base = rot; - } - void setTrackingStrength(float strength); + // Setters para multiplicadores de dificultad (stage system). + // Establecen la velocidad escalar deseada manteniendo la dirección + // actual del body_.velocity. + void setVelocity(float speed); + void setRotation(float rot) { + drotacio_ = rot; + animacio_.drotacio_base = rot; + } + void setTrackingStrength(float strength); - // Invulnerabilidad - [[nodiscard]] auto isInvulnerable() const -> bool { return timer_invulnerabilitat_ > 0.0F; } - [[nodiscard]] auto getInvulnerabilityTime() const -> float { return timer_invulnerabilitat_; } + // Invulnerabilidad + [[nodiscard]] auto isInvulnerable() const -> bool { return timer_invulnerabilitat_ > 0.0F; } + [[nodiscard]] auto getInvulnerabilityTime() const -> float { return timer_invulnerabilitat_; } - private: - // Miembros específicos (heredados: renderer_, shape_, center_, angle_, brightness_, body_). - // Inicializados en la declaración: el ctor por defecto deja al enemy en estado "inactivo - // como pentágono", coherente con lo que harán init() o el ctor con renderer al activarlo. - float drotacio_{0.0F}; // Velocidad angular visual (rad/s) — solo decoración, separada de body_.angular_velocity - float rotacio_{0.0F}; // Rotación visual acumulada (no afecta movimiento) - bool esta_{false}; + // Estado "herido": entre primer impacto de bala y explosión diferida. + void herir(); + [[nodiscard]] auto isWounded() const -> bool { return wounded_timer_ > 0.0F; } + [[nodiscard]] auto getWoundedTimer() const -> float { return wounded_timer_; } + [[nodiscard]] auto woundExpiredThisFrame() const -> bool { return wound_expired_this_frame_; } + void consumeWoundExpired() { wound_expired_this_frame_ = false; } - EnemyType type_{EnemyType::PENTAGON}; - EnemyAnimation animacio_; + // Aplica un impulso (cambio inmediato de velocidad mass-aware) al cuerpo físico. + void applyImpulse(const Vec2& impulse); - // Comportamiento type-specific - float tracking_timer_{0.0F}; // Quadrat: tiempo desde último update de dirección - const Vec2* ship_position_{nullptr}; // Puntero a posición de la nave (para tracking) - float tracking_strength_{0.0F}; // Quadrat: intensidad de tracking (0.0-1.5), default 0.5 - float direction_change_timer_{0.0F}; // Pentagon: tiempo para próximo cambio de dirección + private: + // Miembros específicos (heredados: renderer_, shape_, center_, angle_, brightness_, body_). + // Inicializados en la declaración: el ctor por defecto deja al enemy en estado "inactivo + // como pentágono", coherente con lo que harán init() o el ctor con renderer al activarlo. + float drotacio_{0.0F}; // Velocidad angular visual (rad/s) — solo decoración, separada de body_.angular_velocity + float rotacio_{0.0F}; // Rotación visual acumulada (no afecta movimiento) + bool esta_{false}; - // Invulnerabilidad post-spawn - float timer_invulnerabilitat_{0.0F}; + EnemyType type_{EnemyType::PENTAGON}; + EnemyAnimation animacio_; - // Métodos privados - void updateAnimation(float delta_time); - void updatePalpitation(float delta_time); - void updateRotationAcceleration(float delta_time); - void behaviorPentagon(float delta_time); - void behaviorQuadrat(float delta_time); - void behaviorMolinillo(float delta_time); - [[nodiscard]] auto computeCurrentScale() const -> float; - // Estático: solo opera sobre ship_pos pasado; no consulta estado del enemy. - static auto attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool; + // Comportamiento type-specific + float tracking_timer_{0.0F}; // Quadrat: tiempo desde último update de dirección + const Vec2* ship_position_{nullptr}; // Puntero a posición de la nave (para tracking) + float tracking_strength_{0.0F}; // Quadrat: intensidad de tracking (0.0-1.5), default 0.5 + float direction_change_timer_{0.0F}; // Pentagon: tiempo para próximo cambio de dirección - // Helper: setear body_.velocity desde un ángulo y magnitud. - // angle_movement=0 apunta hacia arriba (eje Y negativo SDL). - void setVelocityFromAngle(float angle_movement, float speed); + // Invulnerabilidad post-spawn + float timer_invulnerabilitat_{0.0F}; + + // Estado "herido": timer cuenta atrás; al cruzar 0 se marca expiración. + float wounded_timer_{0.0F}; + bool wound_expired_this_frame_{false}; + + // Métodos privados + void updateAnimation(float delta_time); + void updatePalpitation(float delta_time); + void updateRotationAcceleration(float delta_time); + void behaviorPentagon(float delta_time); + void behaviorQuadrat(float delta_time); + void behaviorMolinillo(float delta_time); + [[nodiscard]] auto computeCurrentScale() const -> float; + // Estático: solo opera sobre ship_pos pasado; no consulta estado del enemy. + static auto attemptSafeSpawn(const Vec2& ship_pos, float& out_x, float& out_y) -> bool; + + // Helper: setear body_.velocity desde un ángulo y magnitud. + // angle_movement=0 apunta hacia arriba (eje Y negativo SDL). + void setVelocityFromAngle(float angle_movement, float speed); }; From dc2824a095266b8ec4a8da89e47010e51d96ddcf Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 10:22:25 +0200 Subject: [PATCH 12/27] feat(collision): la bala transmet impulse mass-aware al enemic (Fase 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Defaults::Physics::Bullet::IMPACT_IMPULSE (50 px·s placeholder) - detectBulletEnemy: calcula normal bullet→enemy, normalitza (fallback a direcció de bala o (0,-1) si estan solapats) i crida enemy.applyImpulse(normal * IMPACT_IMPULSE) abans de destruir. El destruir() immediat encara zera la velocity, així que l'efecte visual no es nota: serà visible quan la Fase 3 difereixi la mort. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/defaults/physics.hpp | 5 +++++ source/game/systems/collision_system.cpp | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 7a204ba..218cdba 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -10,6 +10,11 @@ namespace Defaults::Physics { constexpr float MAX_VELOCITY = 120.0F; // px/s constexpr float FRICTION = 20.0F; // px/s² + // Bullet — impacto físico contra enemigo (impulse mass-aware). + namespace Bullet { + constexpr float IMPACT_IMPULSE = 50.0F; // Magnitud del impulse en la dirección bullet→enemy + } // namespace Bullet + // Explosions (debris physics) namespace Debris { constexpr float VELOCITAT_BASE = 80.0F; // Velocidad inicial (px/s) diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index ff1f6f9..f610d4c 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -23,6 +23,17 @@ namespace Systems::Collision { // *** COLISIÓN bullet → enemy *** const Vec2& enemy_pos = enemy.getCenter(); + // Empuje físico: impulse en la dirección bullet→enemy (fallback a la + // dirección de la bala si están exactamente solapados). + Vec2 normal = enemy_pos - bullet.getCenter(); + if (normal.lengthSquared() > 0.000001F) { + normal = normal.normalized(); + } else { + const Vec2 BVEL = bullet.getBody().velocity; + normal = (BVEL.lengthSquared() > 0.0F) ? BVEL.normalized() : Vec2{.x = 0.0F, .y = -1.0F}; + } + enemy.applyImpulse(normal * Defaults::Physics::Bullet::IMPACT_IMPULSE); + // 1. Puntos según tipo int points = 0; switch (enemy.getType()) { From 5cb547db0ae49be751bf75e3bbb75bdbf42071e0 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 10:26:13 +0200 Subject: [PATCH 13/27] feat(collision): primer impacte fereix, segon mata; mort diferida via timer (Fase 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE (placeholder 1.0). - Enemy::herir(shooter_id) emmagatzema last_hit_by_ per a atribució posterior. - collision_system: helper anònim explodeNow(ctx, enemy, shooter_id) que llegeix velocity/dades ABANS de destruir() (corregeix bug latent: el codi anterior llegia getVelocityVector() després de destruir, que zera velocity → l'explosió mai heretava inèrcia). - detectBulletEnemy: primer impacte aplica impulse + herir(); segon impacte sobre enemy ferit dispara explodeNow immediata. - processWoundedDeaths: explota enemics amb wound timer expirat aquest frame. - detectAll: processWoundedDeaths abans de detectBulletEnemy (les expiracions maten primer; les bales del mateix frame ja no toquen el cos destruït). Puntos s'atribueixen a la mort real, no a l'impacte inicial. Build neta i smoke test xvfb OK. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/defaults/physics.hpp | 4 + source/game/entities/enemy.cpp | 4 +- source/game/entities/enemy.hpp | 5 +- source/game/systems/collision_system.cpp | 139 +++++++++++++++-------- source/game/systems/collision_system.hpp | 38 ++++--- 5 files changed, 120 insertions(+), 70 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 218cdba..0f4ec3b 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -34,6 +34,10 @@ namespace Defaults::Physics { // Velocity heredada de la nau a l'explosió (80% del feel original). constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F; + // Velocity heredada de l'enemic a l'explosió (palanca per a tuneo). + // 1.0 = inèrcia completa; >1.0 amplifica la deriva; <1.0 la atenua. + constexpr float ENEMY_VELOCITY_INHERITANCE = 1.0F; + // Angular velocity sin for trajectory inheritance // Excess above this threshold is converted to tangential linear velocity // Prevents "vortex trap" problem with high-rotation enemies diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index a8d9f28..7766297 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -255,10 +255,12 @@ void Enemy::destruir() { body_.radius = 0.0F; // No colisiona mientras está inactivo wounded_timer_ = 0.0F; wound_expired_this_frame_ = false; + last_hit_by_ = 0xFF; } -void Enemy::herir() { +void Enemy::herir(uint8_t shooter_id) { wounded_timer_ = Defaults::Enemies::Wounded::DURATION; + last_hit_by_ = shooter_id; } void Enemy::applyImpulse(const Vec2& impulse) { diff --git a/source/game/entities/enemy.hpp b/source/game/entities/enemy.hpp index 4d24d00..dcfa89b 100644 --- a/source/game/entities/enemy.hpp +++ b/source/game/entities/enemy.hpp @@ -86,11 +86,13 @@ class Enemy : public Entities::Entity { [[nodiscard]] auto getInvulnerabilityTime() const -> float { return timer_invulnerabilitat_; } // Estado "herido": entre primer impacto de bala y explosión diferida. - void herir(); + // shooter_id: id del jugador que herí; 0xFF = sin atribución (cadena, etc.). + void herir(uint8_t shooter_id = 0xFF); [[nodiscard]] auto isWounded() const -> bool { return wounded_timer_ > 0.0F; } [[nodiscard]] auto getWoundedTimer() const -> float { return wounded_timer_; } [[nodiscard]] auto woundExpiredThisFrame() const -> bool { return wound_expired_this_frame_; } void consumeWoundExpired() { wound_expired_this_frame_ = false; } + [[nodiscard]] auto getLastHitBy() const -> uint8_t { return last_hit_by_; } // Aplica un impulso (cambio inmediato de velocidad mass-aware) al cuerpo físico. void applyImpulse(const Vec2& impulse); @@ -118,6 +120,7 @@ class Enemy : public Entities::Entity { // Estado "herido": timer cuenta atrás; al cruzar 0 se marca expiración. float wounded_timer_{0.0F}; bool wound_expired_this_frame_{false}; + uint8_t last_hit_by_{0xFF}; // 0xFF = sin atribución // Métodos privados void updateAnimation(float delta_time); diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index f610d4c..3041df8 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -10,9 +10,75 @@ namespace Systems::Collision { + namespace { + constexpr uint8_t NO_SHOOTER = 0xFF; + + // Lookup tabla puntos / color por tipo de enemy (mantiene la lógica + // anterior pero centralizada para reutilizar entre paths). + auto scoreForType(EnemyType type) -> int { + switch (type) { + case EnemyType::PENTAGON: + return Defaults::Enemies::Scoring::PENTAGON_SCORE; + case EnemyType::QUADRAT: + return Defaults::Enemies::Scoring::QUADRAT_SCORE; + case EnemyType::MOLINILLO: + return Defaults::Enemies::Scoring::MOLINILLO_SCORE; + } + return 0; + } + + auto colorForType(EnemyType type) -> SDL_Color { + switch (type) { + case EnemyType::PENTAGON: + return Defaults::Palette::PENTAGON; + case EnemyType::QUADRAT: + return Defaults::Palette::QUADRAT; + case EnemyType::MOLINILLO: + return Defaults::Palette::MOLINILLO; + } + return SDL_Color{}; + } + + // Mata al enemy con explosión: floating score, debris con velocity heredada, + // sonido. Si shooter_id ≠ NO_SHOOTER, suma puntos a ese jugador. + // CRUCIAL: leer velocity/datos ANTES de destruir() (que zera la velocity). + void explodeNow(Context& ctx, Enemy& enemy, uint8_t shooter_id) { + const Vec2 ENEMY_POS = enemy.getCenter(); + const Vec2 ENEMY_VEL = enemy.getVelocityVector(); + const float DROTACIO = enemy.getRotationDelta(); + const float BRIGHTNESS = enemy.getBrightness(); + const auto SHAPE = enemy.getShape(); + const EnemyType TYPE = enemy.getType(); + + const int POINTS = scoreForType(TYPE); + const SDL_Color COLOR = colorForType(TYPE); + + if (shooter_id != NO_SHOOTER) { + ctx.score_per_player[shooter_id] += POINTS; + } + ctx.floating_score_manager.crear(POINTS, ENEMY_POS); + + enemy.destruir(); + + constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (explosión suave) + const Vec2 INHERITED_VEL = ENEMY_VEL * Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE; + ctx.debris_manager.explode( + SHAPE, + ENEMY_POS, + 0.0F, // angle (rotación interna del enemy) + 1.0F, // escala + VELOCITAT_EXPLOSIO, + BRIGHTNESS, + INHERITED_VEL, + DROTACIO, + 0.0F, // sin herencia visual + Defaults::Sound::EXPLOSION, + COLOR); + } + } // anonymous namespace + void detectBulletEnemy(Context& ctx) { constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER; - constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (explosión suau) for (auto& bullet : ctx.bullets) { for (auto& enemy : ctx.enemies) { @@ -21,11 +87,9 @@ namespace Systems::Collision { } // *** COLISIÓN bullet → enemy *** - const Vec2& enemy_pos = enemy.getCenter(); - // Empuje físico: impulse en la dirección bullet→enemy (fallback a la // dirección de la bala si están exactamente solapados). - Vec2 normal = enemy_pos - bullet.getCenter(); + Vec2 normal = enemy.getCenter() - bullet.getCenter(); if (normal.lengthSquared() > 0.000001F) { normal = normal.normalized(); } else { @@ -34,59 +98,33 @@ namespace Systems::Collision { } enemy.applyImpulse(normal * Defaults::Physics::Bullet::IMPACT_IMPULSE); - // 1. Puntos según tipo - int points = 0; - switch (enemy.getType()) { - case EnemyType::PENTAGON: - points = Defaults::Enemies::Scoring::PENTAGON_SCORE; - break; - case EnemyType::QUADRAT: - points = Defaults::Enemies::Scoring::QUADRAT_SCORE; - break; - case EnemyType::MOLINILLO: - points = Defaults::Enemies::Scoring::MOLINILLO_SCORE; - break; + const uint8_t SHOOTER = bullet.getOwnerId(); + + if (enemy.isWounded()) { + // Segundo impacto sobre enemy ya herido → muerte instantánea, + // puntos al nuevo shooter. + explodeNow(ctx, enemy, SHOOTER); + } else { + // Primer impacto → entra en estado herido (explosión diferida). + enemy.herir(SHOOTER); } - uint8_t owner_id = bullet.getOwnerId(); - ctx.score_per_player[owner_id] += points; - ctx.floating_score_manager.crear(points, enemy_pos); - - // 2. Destruir enemy + crear explosión (debris hereda color del enemy) - SDL_Color enemy_color{}; - switch (enemy.getType()) { - case EnemyType::PENTAGON: - enemy_color = Defaults::Palette::PENTAGON; - break; - case EnemyType::QUADRAT: - enemy_color = Defaults::Palette::QUADRAT; - break; - case EnemyType::MOLINILLO: - enemy_color = Defaults::Palette::MOLINILLO; - break; - } - enemy.destruir(); - Vec2 vel_enemic = enemy.getVelocityVector(); - ctx.debris_manager.explode( - enemy.getShape(), - enemy_pos, - 0.0F, // angle (la rotación es interna del enemy) - 1.0F, // escala - VELOCITAT_EXPLOSIO, - enemy.getBrightness(), - vel_enemic, - enemy.getRotationDelta(), - 0.0F, // sin herencia visual - Defaults::Sound::EXPLOSION, - enemy_color); - - // 3. Desactivar bullet (solo destruye 1 enemy) bullet.desactivar(); - break; + break; // Una bala impacta a un enemy y muere } } } + void processWoundedDeaths(Context& ctx) { + for (auto& enemy : ctx.enemies) { + if (!enemy.woundExpiredThisFrame()) { + continue; + } + enemy.consumeWoundExpired(); + explodeNow(ctx, enemy, enemy.getLastHitBy()); + } + } + void detectShipEnemy(Context& ctx) { constexpr float AMPLIFIER = Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER; @@ -158,6 +196,7 @@ namespace Systems::Collision { } void detectAll(Context& ctx) { + processWoundedDeaths(ctx); // expiran ANTES de ser tocadas por bala este frame detectBulletEnemy(ctx); detectShipEnemy(ctx); detectBulletPlayer(ctx); diff --git a/source/game/systems/collision_system.hpp b/source/game/systems/collision_system.hpp index a7ef980..8e67400 100644 --- a/source/game/systems/collision_system.hpp +++ b/source/game/systems/collision_system.hpp @@ -26,9 +26,9 @@ namespace Systems::Collision { -// Todo lo que las detecciones necesitan leer/modificar. Vive en GameScene; -// se le pasa por referencia (no copia, no ownership). -struct Context { + // Todo lo que las detecciones necesitan leer/modificar. Vive en GameScene; + // se le pasa por referencia (no copia, no ownership). + struct Context { std::array& ships; std::array& enemies; std::array(Defaults::Entities::MAX_BALES) * 2>& bullets; @@ -40,24 +40,26 @@ struct Context { const GameConfig::MatchConfig& match_config; // Trigger de muerte del jugador (GameScene::tocado). std::function on_player_hit; -}; + }; -// Detecta colisiones bullet → enemy. Si hit: -// - destruye el enemy (radius=0 en physics body) -// - crea debris + floating score -// - desactiva la bullet -// - suma puntos al shooter -void detectBulletEnemy(Context& ctx); + // Detecta colisiones bullet → enemy. Si hit: + // - Primer impacto: aplica impulse, marca al enemy como "herido", desactiva bullet. + // - Segundo impacto (enemy ya herido): explosión inmediata + puntos al shooter. + void detectBulletEnemy(Context& ctx); -// Detecta colisiones ship → enemy. Si hit, llama on_player_hit(player_id). -void detectShipEnemy(Context& ctx); + // Procesa enemigos cuyo wound timer ha expirado este frame: explosión + puntos + // al `last_hit_by_` del enemy (si está set). + void processWoundedDeaths(Context& ctx); -// Detecta colisiones bullet → player (friendly fire / self-hit). -// Self-hit: el shooter pierde 1 vida. Teammate-hit: la víctima pierde 1, el -// atacante gana 1. En ambos casos, llama on_player_hit y desactiva bullet. -void detectBulletPlayer(Context& ctx); + // Detecta colisiones ship → enemy. Si hit, llama on_player_hit(player_id). + void detectShipEnemy(Context& ctx); -// Las tres en orden lógico del frame. -void detectAll(Context& ctx); + // Detecta colisiones bullet → player (friendly fire / self-hit). + // Self-hit: el shooter pierde 1 vida. Teammate-hit: la víctima pierde 1, el + // atacante gana 1. En ambos casos, llama on_player_hit y desactiva bullet. + void detectBulletPlayer(Context& ctx); + + // Las tres en orden lógico del frame. + void detectAll(Context& ctx); } // namespace Systems::Collision From 7b24bfae942b1a578c486e0ae22453d3293dee01 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 10:27:56 +0200 Subject: [PATCH 14/27] =?UTF-8?q?feat(enemy):=20parpadeig=20dorat=20quan?= =?UTF-8?q?=20est=C3=A0=20herit=20(Fase=204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enemy::draw() ara, si wounded_timer_ > 0, alterna entre el color del tipus i Defaults::Palette::WOUNDED (dorat) a Wounded::BLINK_HZ usant fmod sobre el periode del cicle — patró reutilitzat del Ship::draw() d'invulnerabilitat però aplicat a color en lloc de visibilitat. A 10 Hz amb DURATION=1s dóna ~10 parpadeigs visibles abans d'explotar. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/game/entities/enemy.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index 7766297..d531a12 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -245,6 +245,17 @@ void Enemy::draw() const { color = Defaults::Palette::MOLINILLO; break; } + + // Parpadeo dorado mientras está herido: alterna color de tipo ↔ dorado + // a Wounded::BLINK_HZ usando el timer (fmod sobre el periodo). + if (wounded_timer_ > 0.0F) { + const float CYCLE = 1.0F / Defaults::Enemies::Wounded::BLINK_HZ; + const float T = std::fmod(wounded_timer_, CYCLE); + if (T < (CYCLE / 2.0F)) { + color = Defaults::Palette::WOUNDED; + } + } + Rendering::renderShape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_, color); } From 2cf5292b169bc2f323c98f4ca4af0abe6227a5fd Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 10:29:29 +0200 Subject: [PATCH 15/27] =?UTF-8?q?feat(collision):=20cadena=20herit?= =?UTF-8?q?=E2=86=92sa=20via=20fregada=20f=C3=ADsica=20(Fase=206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Systems::Collision::detectWoundedChain itera parells d'enemics: si exactament un està herit i toquen (Physics::checkCollision), el sa entra en estat herit propagant last_hit_by_ → la cascada de morts segueix acreditant el shooter original. El rebot físic ja el gestiona PhysicsWorld; aquí només propaguem l'estat. Hook a detectAll just després de detectBulletEnemy: les balles tenen prioritat sobre la cadena del mateix frame. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/game/systems/collision_system.cpp | 31 ++++++++++++++++++++++++ source/game/systems/collision_system.hpp | 6 +++++ 2 files changed, 37 insertions(+) diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index 3041df8..a65e862 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -125,6 +125,36 @@ namespace Systems::Collision { } } + void detectWoundedChain(Context& ctx) { + const std::size_t N = ctx.enemies.size(); + for (std::size_t i = 0; i < N; i++) { + Enemy& a = ctx.enemies[i]; + if (!a.isCollidable()) { + continue; + } + for (std::size_t j = i + 1; j < N; j++) { + Enemy& b = ctx.enemies[j]; + if (!b.isCollidable()) { + continue; + } + const bool A_WOUNDED = a.isWounded(); + const bool B_WOUNDED = b.isWounded(); + if (A_WOUNDED == B_WOUNDED) { + continue; // ambos sanos o ambos heridos: nada que propagar + } + if (!Physics::checkCollision(a, b, 1.0F)) { + continue; + } + // El sano queda herido, propagando el shooter original. + if (A_WOUNDED) { + b.herir(a.getLastHitBy()); + } else { + a.herir(b.getLastHitBy()); + } + } + } + } + void detectShipEnemy(Context& ctx) { constexpr float AMPLIFIER = Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER; @@ -198,6 +228,7 @@ namespace Systems::Collision { void detectAll(Context& ctx) { processWoundedDeaths(ctx); // expiran ANTES de ser tocadas por bala este frame detectBulletEnemy(ctx); + detectWoundedChain(ctx); // un herit pot ferir a un sa al fregar-lo detectShipEnemy(ctx); detectBulletPlayer(ctx); } diff --git a/source/game/systems/collision_system.hpp b/source/game/systems/collision_system.hpp index 8e67400..46c172b 100644 --- a/source/game/systems/collision_system.hpp +++ b/source/game/systems/collision_system.hpp @@ -51,6 +51,12 @@ namespace Systems::Collision { // al `last_hit_by_` del enemy (si está set). void processWoundedDeaths(Context& ctx); + // Si un enemy herido colisiona con uno sano (ni herido ni invulnerable), + // el sano también queda herido (efecto cadena). Propaga `last_hit_by_` para + // que el shooter original siga acreditándose la muerte en cascada. El rebote + // físico ya lo resuelve PhysicsWorld; aquí solo propagamos el estado. + void detectWoundedChain(Context& ctx); + // Detecta colisiones ship → enemy. Si hit, llama on_player_hit(player_id). void detectShipEnemy(Context& ctx); From ff5dfab94de1eaeaa021bbc938b0956d6d3195a2 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 10:37:23 +0200 Subject: [PATCH 16/27] =?UTF-8?q?tune(bullet):=20empuje=20cuasi-f=C3=ADsic?= =?UTF-8?q?o=20(momento=20real=20de=20la=20bala)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sustitueix IMPACT_IMPULSE (magnitud arbitrària radial) per IMPACT_MOMENTUM_FACTOR (factor de transferència del moment de la bala). El impulse ara és bullet.body.velocity * (bullet.body.mass * factor), és a dir el moment lineal real de la bala, dirigit cap a on viatjava. Amb factor=1.0 i la bala (m=0.5, v=700 px/s): - Pentagon (m=5) → Δv = 70 px/s (doble de la seva velocity base) - Quadrat (m=8) → Δv = 44 px/s - Molinillo (m=4) → Δv = 88 px/s Visiblement notable durant el segon de "ferit" abans de l'explosió. El factor és tunable per pujar/baixar segons gusts. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/defaults/physics.hpp | 5 ++++- source/game/systems/collision_system.cpp | 16 ++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 0f4ec3b..f6f5dad 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -11,8 +11,11 @@ namespace Defaults::Physics { constexpr float FRICTION = 20.0F; // px/s² // Bullet — impacto físico contra enemigo (impulse mass-aware). + // Model: el impulse és el moment lineal de la bala (m·v) multiplicat per + // un factor de transferència [0..1]. 1.0 = transfereix tot el moment + // (col·lisió perfectament inelàstica), 0.5 = transfereix la meitat. namespace Bullet { - constexpr float IMPACT_IMPULSE = 50.0F; // Magnitud del impulse en la dirección bullet→enemy + constexpr float IMPACT_MOMENTUM_FACTOR = 1.0F; // Factor de transferència de moment bala→enemic } // namespace Bullet // Explosions (debris physics) diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index a65e862..a26b7fe 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -87,16 +87,12 @@ namespace Systems::Collision { } // *** COLISIÓN bullet → enemy *** - // Empuje físico: impulse en la dirección bullet→enemy (fallback a la - // dirección de la bala si están exactamente solapados). - Vec2 normal = enemy.getCenter() - bullet.getCenter(); - if (normal.lengthSquared() > 0.000001F) { - normal = normal.normalized(); - } else { - const Vec2 BVEL = bullet.getBody().velocity; - normal = (BVEL.lengthSquared() > 0.0F) ? BVEL.normalized() : Vec2{.x = 0.0F, .y = -1.0F}; - } - enemy.applyImpulse(normal * Defaults::Physics::Bullet::IMPACT_IMPULSE); + // Empuje físico cuasi-realista: el impulse és el moment de la bala + // (m·v) multiplicat pel factor de transferència. Direcció = vector + // velocity de la bala (cap a on viatjava). + const Vec2 IMPULSE = bullet.getBody().velocity * + (bullet.getBody().mass * Defaults::Physics::Bullet::IMPACT_MOMENTUM_FACTOR); + enemy.applyImpulse(IMPULSE); const uint8_t SHOOTER = bullet.getOwnerId(); From e3af88ea8c2d79d3302983b21a04ab1a6550d9b5 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 11:33:03 +0200 Subject: [PATCH 17/27] =?UTF-8?q?tune(enemy):=20m=C3=A9s=20empenta=20+=20c?= =?UTF-8?q?os=20inert=20quan=20est=C3=A0=20herit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IMPACT_MOMENTUM_FACTOR: 1.0 → 2.0 (doble del moment de la bala). Pentagon Δv = 140 px/s (≈4× la seva velocity base), prou clar. - Enemy::update: salta el switch de behavior (Pentagon zigzag, Quadrat tracking, Molinillo proximity-spin) mentre wounded_timer_>0. El enemic herit és un "cos mort" inert: només respon a la inèrcia del impulse rebut i a les col·lisions físiques resoltes per PhysicsWorld. Abans, el Quadrat renormalitzava la velocity cada 1s cap al ship, esborrant la inèrcia. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/defaults/physics.hpp | 2 +- source/game/entities/enemy.cpp | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index f6f5dad..0d415eb 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -15,7 +15,7 @@ namespace Defaults::Physics { // un factor de transferència [0..1]. 1.0 = transfereix tot el moment // (col·lisió perfectament inelàstica), 0.5 = transfereix la meitat. namespace Bullet { - constexpr float IMPACT_MOMENTUM_FACTOR = 1.0F; // Factor de transferència de moment bala→enemic + constexpr float IMPACT_MOMENTUM_FACTOR = 2.0F; // Factor de transferència de moment bala→enemic } // namespace Bullet // Explosions (debris physics) diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index d531a12..c0626be 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -201,17 +201,21 @@ void Enemy::update(float delta_time) { brightness_ = START + ((END - START) * SMOOTH_T); } - // Comportamiento por tipo (ajusta body_.velocity, NO mueve posición) - switch (type_) { - case EnemyType::PENTAGON: - behaviorPentagon(delta_time); - break; - case EnemyType::QUADRAT: - behaviorQuadrat(delta_time); - break; - case EnemyType::MOLINILLO: - behaviorMolinillo(delta_time); - break; + // Comportamiento por tipo (ajusta body_.velocity, NO mueve posición). + // Skip cuando está herido: el enemy és un "cos mort" inert, només + // respon a la inèrcia del impulse rebut i a les col·lisions físiques. + if (!isWounded()) { + switch (type_) { + case EnemyType::PENTAGON: + behaviorPentagon(delta_time); + break; + case EnemyType::QUADRAT: + behaviorQuadrat(delta_time); + break; + case EnemyType::MOLINILLO: + behaviorMolinillo(delta_time); + break; + } } // Animaciones (palpitación + rotación acelerada) From 44aa4e76e23157785210b654162127c9cb3de308 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 11:45:59 +0200 Subject: [PATCH 18/27] =?UTF-8?q?fix(physics):=20salta=20body-body=20colli?= =?UTF-8?q?sion=20quan=20algun=20cos=20t=C3=A9=20radius=3D0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolveBodyPair afegeix early-out per a parells on a.radius<=0 o b.radius<=0. Honra el comentari de bullet.cpp:30 ("radius=0 → sin colisión física, cinemática pura") que abans no s'aplicava: amb bala radius=0 + enemic radius=ENEMY_RADIUS, SUM_R era enemic radius i el body-body disparava si la bala (a 700 px/s) penetrava el cos l'enemic entre frames. Símptomes corregits: - Pentagon: la bala "rebotava espectacularment" en lloc d'impactar. - Quadrat: rebut un impulse double del cantó de la física que es sumava (o cancel·lava, segons l'angle) al manual, fent l'efecte inconsistent. Ara la gameplay collision (Physics::checkCollision amb entity radius, que ja és més generós) és l'única que tracta el parell bala-enemic. A més: IMPACT_MOMENTUM_FACTOR 2.0 → 3.0 per compensar la pèrdua del rebot físic i donar més empenta: - Pentagon (m=5) Δv = 210 px/s - Quadrat (m=8) Δv = 131 px/s - Molinillo (m=4) Δv = 262 px/s Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/defaults/physics.hpp | 2 +- source/core/physics/physics_world.cpp | 287 +++++++++++++------------- 2 files changed, 148 insertions(+), 141 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 0d415eb..03603f4 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -15,7 +15,7 @@ namespace Defaults::Physics { // un factor de transferència [0..1]. 1.0 = transfereix tot el moment // (col·lisió perfectament inelàstica), 0.5 = transfereix la meitat. namespace Bullet { - constexpr float IMPACT_MOMENTUM_FACTOR = 2.0F; // Factor de transferència de moment bala→enemic + constexpr float IMPACT_MOMENTUM_FACTOR = 3.0F; // Factor de transferència de moment bala→enemic } // namespace Bullet // Explosions (debris physics) diff --git a/source/core/physics/physics_world.cpp b/source/core/physics/physics_world.cpp index 4ccd551..7a8d733 100644 --- a/source/core/physics/physics_world.cpp +++ b/source/core/physics/physics_world.cpp @@ -10,172 +10,179 @@ namespace Physics { -void PhysicsWorld::addBody(RigidBody* body) { - if (body == nullptr) { - return; - } - if (std::ranges::find(bodies_, body) == bodies_.end()) { - bodies_.push_back(body); - } -} - -void PhysicsWorld::removeBody(RigidBody* body) { - std::erase(bodies_, body); -} - -void PhysicsWorld::update(float dt) { - integrate(dt); - if (has_bounds_) { - resolveBoundsCollisions(); - } - resolveBodyCollisions(); -} - -// Integración semi-implícita de Euler: -// v(t+dt) = v(t) + (F/m) * dt -// x(t+dt) = x(t) + v(t+dt) * dt -// Más estable que Euler explícito para juegos. Damping exponencial. -void PhysicsWorld::integrate(float dt) { - for (auto* body : bodies_) { - if (body == nullptr || body->isStatic()) { - continue; + void PhysicsWorld::addBody(RigidBody* body) { + if (body == nullptr) { + return; } - - // Aplicar fuerzas acumuladas → aceleración - const Vec2 ACCELERATION = body->force_accumulator * body->inverse_mass; - body->velocity += ACCELERATION * dt; - - // Damping exponencial: equivalente a v *= exp(-damping * dt) - // Aproximación lineal cuando damping*dt es pequeño. - if (body->linear_damping > 0.0F) { - const float DAMP = std::exp(-body->linear_damping * dt); - body->velocity *= DAMP; + if (std::ranges::find(bodies_, body) == bodies_.end()) { + bodies_.push_back(body); } - if (body->angular_damping > 0.0F) { - const float DAMP = std::exp(-body->angular_damping * dt); - body->angular_velocity *= DAMP; - } - - // Actualizar posición y rotación - body->position += body->velocity * dt; - body->angle += body->angular_velocity * dt; - - body->clearAccumulators(); } -} -// Rebote contra los 4 bordes del rectángulo bounds_. -// Refleja la componente normal de la velocidad por la restitución. -void PhysicsWorld::resolveBoundsCollisions() { - const float MIN_X = bounds_.x; - const float MAX_X = bounds_.x + bounds_.w; - const float MIN_Y = bounds_.y; - const float MAX_Y = bounds_.y + bounds_.h; + void PhysicsWorld::removeBody(RigidBody* body) { + std::erase(bodies_, body); + } - for (auto* body : bodies_) { - if (body == nullptr || body->isStatic()) { - continue; + void PhysicsWorld::update(float dt) { + integrate(dt); + if (has_bounds_) { + resolveBoundsCollisions(); } - const float R = body->radius; + resolveBodyCollisions(); + } - // Pared izquierda - if (body->position.x - R < MIN_X) { - body->position.x = MIN_X + R; - if (body->velocity.x < 0.0F) { - body->velocity.x = -body->velocity.x * body->restitution; + // Integración semi-implícita de Euler: + // v(t+dt) = v(t) + (F/m) * dt + // x(t+dt) = x(t) + v(t+dt) * dt + // Más estable que Euler explícito para juegos. Damping exponencial. + void PhysicsWorld::integrate(float dt) { + for (auto* body : bodies_) { + if (body == nullptr || body->isStatic()) { + continue; } - } - // Pared derecha - if (body->position.x + R > MAX_X) { - body->position.x = MAX_X - R; - if (body->velocity.x > 0.0F) { - body->velocity.x = -body->velocity.x * body->restitution; + + // Aplicar fuerzas acumuladas → aceleración + const Vec2 ACCELERATION = body->force_accumulator * body->inverse_mass; + body->velocity += ACCELERATION * dt; + + // Damping exponencial: equivalente a v *= exp(-damping * dt) + // Aproximación lineal cuando damping*dt es pequeño. + if (body->linear_damping > 0.0F) { + const float DAMP = std::exp(-body->linear_damping * dt); + body->velocity *= DAMP; } - } - // Pared superior - if (body->position.y - R < MIN_Y) { - body->position.y = MIN_Y + R; - if (body->velocity.y < 0.0F) { - body->velocity.y = -body->velocity.y * body->restitution; + if (body->angular_damping > 0.0F) { + const float DAMP = std::exp(-body->angular_damping * dt); + body->angular_velocity *= DAMP; } + + // Actualizar posición y rotación + body->position += body->velocity * dt; + body->angle += body->angular_velocity * dt; + + body->clearAccumulators(); } - // Pared inferior - if (body->position.y + R > MAX_Y) { - body->position.y = MAX_Y - R; - if (body->velocity.y > 0.0F) { - body->velocity.y = -body->velocity.y * body->restitution; + } + + // Rebote contra los 4 bordes del rectángulo bounds_. + // Refleja la componente normal de la velocidad por la restitución. + void PhysicsWorld::resolveBoundsCollisions() { + const float MIN_X = bounds_.x; + const float MAX_X = bounds_.x + bounds_.w; + const float MIN_Y = bounds_.y; + const float MAX_Y = bounds_.y + bounds_.h; + + for (auto* body : bodies_) { + if (body == nullptr || body->isStatic()) { + continue; + } + const float R = body->radius; + + // Pared izquierda + if (body->position.x - R < MIN_X) { + body->position.x = MIN_X + R; + if (body->velocity.x < 0.0F) { + body->velocity.x = -body->velocity.x * body->restitution; + } + } + // Pared derecha + if (body->position.x + R > MAX_X) { + body->position.x = MAX_X - R; + if (body->velocity.x > 0.0F) { + body->velocity.x = -body->velocity.x * body->restitution; + } + } + // Pared superior + if (body->position.y - R < MIN_Y) { + body->position.y = MIN_Y + R; + if (body->velocity.y < 0.0F) { + body->velocity.y = -body->velocity.y * body->restitution; + } + } + // Pared inferior + if (body->position.y + R > MAX_Y) { + body->position.y = MAX_Y - R; + if (body->velocity.y > 0.0F) { + body->velocity.y = -body->velocity.y * body->restitution; + } } } } -} -// Colisiones cuerpo-cuerpo: O(n²) círculo-círculo + resolución por impulso. -// Para 15 enemigos + 6 balas + 2 naves = ~23 cuerpos → 253 pares. Sobra. -// -// Fórmula del impulso elástico (referencia: Chris Hecker / Box2D): -// j = -(1 + e) * (v_rel · n) / (1/m_a + 1/m_b) -// donde n es la normal del contacto (de a hacia b) y v_rel = v_a - v_b. -void PhysicsWorld::resolveBodyCollisions() { - const std::size_t COUNT = bodies_.size(); - for (std::size_t i = 0; i < COUNT; ++i) { - for (std::size_t j = i + 1; j < COUNT; ++j) { - auto* a = bodies_[i]; - auto* b = bodies_[j]; - if (a != nullptr && b != nullptr) { - resolveBodyPair(*a, *b); + // Colisiones cuerpo-cuerpo: O(n²) círculo-círculo + resolución por impulso. + // Para 15 enemigos + 6 balas + 2 naves = ~23 cuerpos → 253 pares. Sobra. + // + // Fórmula del impulso elástico (referencia: Chris Hecker / Box2D): + // j = -(1 + e) * (v_rel · n) / (1/m_a + 1/m_b) + // donde n es la normal del contacto (de a hacia b) y v_rel = v_a - v_b. + void PhysicsWorld::resolveBodyCollisions() { + const std::size_t COUNT = bodies_.size(); + for (std::size_t i = 0; i < COUNT; ++i) { + for (std::size_t j = i + 1; j < COUNT; ++j) { + auto* a = bodies_[i]; + auto* b = bodies_[j]; + if (a != nullptr && b != nullptr) { + resolveBodyPair(*a, *b); + } } } } -} -void PhysicsWorld::resolveBodyPair(RigidBody& a, RigidBody& b) { - // Dos cuerpos estáticos no necesitan resolución - if (a.isStatic() && b.isStatic()) { - return; - } + void PhysicsWorld::resolveBodyPair(RigidBody& a, RigidBody& b) { + // Dos cuerpos estáticos no necesitan resolución + if (a.isStatic() && b.isStatic()) { + return; + } - const Vec2 DELTA = b.position - a.position; - const float DIST_SQ = DELTA.lengthSquared(); - const float SUM_R = a.radius + b.radius; - if (DIST_SQ > SUM_R * SUM_R || DIST_SQ <= 0.0F) { - return; - } + // Un cuerpo con radius=0 es cinemático puro (ej. la bala) y no participa + // en body-body. La detecció de gameplay (Physics::checkCollision) usa + // el radius de l'entity (no el del body) i s'encarrega d'aquesta parella. + if (a.radius <= 0.0F || b.radius <= 0.0F) { + return; + } - const float DIST = std::sqrt(DIST_SQ); - const Vec2 NORMAL = DELTA / DIST; // de A hacia B + const Vec2 DELTA = b.position - a.position; + const float DIST_SQ = DELTA.lengthSquared(); + const float SUM_R = a.radius + b.radius; + if (DIST_SQ > SUM_R * SUM_R || DIST_SQ <= 0.0F) { + return; + } + + const float DIST = std::sqrt(DIST_SQ); + const Vec2 NORMAL = DELTA / DIST; // de A hacia B + + // Corrección posicional (resolver penetración) + const float PENETRATION = SUM_R - DIST; + const float TOTAL_INV_MASS = a.inverse_mass + b.inverse_mass; + if (TOTAL_INV_MASS > 0.0F) { + const Vec2 CORRECTION = NORMAL * (PENETRATION / TOTAL_INV_MASS); + if (!a.isStatic()) { + a.position -= CORRECTION * a.inverse_mass; + } + if (!b.isStatic()) { + b.position += CORRECTION * b.inverse_mass; + } + } + + // Velocidad relativa proyectada sobre la normal + const Vec2 V_REL = b.velocity - a.velocity; + const float VEL_ALONG_NORMAL = V_REL.dot(NORMAL); + // Si se están separando, no aplicar impulso + if (VEL_ALONG_NORMAL > 0.0F) { + return; + } + + // Restitución promedio (Box2D usa max; promedio es más permisivo) + const float E = (a.restitution + b.restitution) * 0.5F; + const float J = -(1.0F + E) * VEL_ALONG_NORMAL / TOTAL_INV_MASS; + const Vec2 IMPULSE = NORMAL * J; - // Corrección posicional (resolver penetración) - const float PENETRATION = SUM_R - DIST; - const float TOTAL_INV_MASS = a.inverse_mass + b.inverse_mass; - if (TOTAL_INV_MASS > 0.0F) { - const Vec2 CORRECTION = NORMAL * (PENETRATION / TOTAL_INV_MASS); if (!a.isStatic()) { - a.position -= CORRECTION * a.inverse_mass; + a.velocity -= IMPULSE * a.inverse_mass; } if (!b.isStatic()) { - b.position += CORRECTION * b.inverse_mass; + b.velocity += IMPULSE * b.inverse_mass; } } - // Velocidad relativa proyectada sobre la normal - const Vec2 V_REL = b.velocity - a.velocity; - const float VEL_ALONG_NORMAL = V_REL.dot(NORMAL); - // Si se están separando, no aplicar impulso - if (VEL_ALONG_NORMAL > 0.0F) { - return; - } - - // Restitución promedio (Box2D usa max; promedio es más permisivo) - const float E = (a.restitution + b.restitution) * 0.5F; - const float J = -(1.0F + E) * VEL_ALONG_NORMAL / TOTAL_INV_MASS; - const Vec2 IMPULSE = NORMAL * J; - - if (!a.isStatic()) { - a.velocity -= IMPULSE * a.inverse_mass; - } - if (!b.isStatic()) { - b.velocity += IMPULSE * b.inverse_mass; - } -} - } // namespace Physics From efd18ff852d09b717606b6a3b12c369374ccc0bb Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 12:07:50 +0200 Subject: [PATCH 19/27] =?UTF-8?q?feat(debris):=20vida=20h=C3=ADbrida=20(m?= =?UTF-8?q?=C3=ADnima=20+=20umbral=20velocidad)=20+=20multiplier=20para=20?= =?UTF-8?q?enemigos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/defaults/physics.hpp | 18 +- source/game/effects/debris.hpp | 19 +- source/game/effects/debris_manager.cpp | 663 ++++++++++++----------- source/game/effects/debris_manager.hpp | 40 +- source/game/systems/collision_system.cpp | 5 +- 5 files changed, 388 insertions(+), 357 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 03603f4..617a73b 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -25,9 +25,15 @@ namespace Defaults::Physics { constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²) constexpr float ROTACIO_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s) constexpr float ROTACIO_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s) - constexpr float TEMPS_VIDA = 2.0F; // Duració màxima (segons) - enemy/bullet debris - constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris lifetime (matches DEATH_DURATION) - constexpr float SHRINK_RATE = 0.5F; // Reducció de mida (factor/s) + constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa + constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION) + constexpr float SHRINK_RATE = 0.5F; // Reducció de mida (factor sobre min_lifetime) + + // Política de mort: passat el min_lifetime, el fragment mor quan la + // seva velocity cau per sota d'aquest llindar. Així els fragments + // ràpids no "popen" en moviment. + constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update + constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE; // Herència de velocity angular (trayectorias curvas) constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat @@ -41,6 +47,12 @@ namespace Defaults::Physics { // 1.0 = inèrcia completa; >1.0 amplifica la deriva; <1.0 la atenua. constexpr float ENEMY_VELOCITY_INHERITANCE = 1.0F; + // Tuneig específic de l'explosió d'enemic (overrides als defaults + // que es passen com a paràmetres opcionals a explode()). + constexpr float ENEMY_LIFETIME = 3.0F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més + constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més + constexpr int ENEMY_SEGMENT_MULTIPLIER = 3; // Còpies de cada segment (5 cares × 3 = 15 trossos) + // Angular velocity sin for trajectory inheritance // Excess above this threshold is converted to tangential linear velocity // Prevents "vortex trap" problem with high-rotation enemies diff --git a/source/game/effects/debris.hpp b/source/game/effects/debris.hpp index c13d18d..bc8f365 100644 --- a/source/game/effects/debris.hpp +++ b/source/game/effects/debris.hpp @@ -8,15 +8,15 @@ namespace Effects { -// Debris: un segment de línia que vola perpendicular a sí mismo -// Representa un fragment de una shape destruïda (ship, enemy, bullet) -struct Debris { + // Debris: un segment de línia que vola perpendicular a sí mismo + // Representa un fragment de una shape destruïda (ship, enemy, bullet) + struct Debris { // Geometria del segment (2 points en coordenades mundials) Vec2 p1; // Vec2 inicial del segment Vec2 p2; // Vec2 final del segment // Física - Vec2 velocity; // Velocidad en px/s (components x, y) + Vec2 velocity; // Velocidad en px/s (components x, y) float acceleration; // Aceleración negativa (fricció) en px/s² // Rotación @@ -25,9 +25,12 @@ struct Debris { float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s) // Estat de vida - float temps_vida; // Temps transcorregut (segons) - float temps_max; // Temps de vida màxim (segons) - bool active; // Está active? + // Política: viu sempre durant min_lifetime, després mor quan + // |velocity| < MIN_SPEED_TO_DIE (definit en Defaults). Així els + // fragments ràpids no "popen" en moviment. + float temps_vida; // Temps transcorregut (segons) + float min_lifetime; // Temps mínim garantit (segons) + bool active; // Està actiu? // Shrinking (reducció de distancia entre points) float factor_shrink; // Factor de reducció per segon (0.0-1.0) @@ -35,6 +38,6 @@ struct Debris { // Rendering float brightness; // Factor de brightness (0.0-1.0, heretat de l'objecte original) SDL_Color color{}; // Color heredado del padre. alpha==0 → usa global oscilador -}; + }; } // namespace Effects diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index de47ae8..0c263e0 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -14,375 +14,388 @@ namespace Effects { -// Helper: transformar point con rotación, scale i traslación -// (Copiat de shape_renderer.cpp:12-34) -static auto transformPoint(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale) -> Vec2 { - // 1. Centrar el point respecte al centro de la shape - float centered_x = point.x - shape_centre.x; - float centered_y = point.y - shape_centre.y; + // Helper: transformar point con rotación, scale i traslación + // (Copiat de shape_renderer.cpp:12-34) + static auto transformPoint(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale) -> Vec2 { + // 1. Centrar el point respecte al centro de la shape + float centered_x = point.x - shape_centre.x; + float centered_y = point.y - shape_centre.y; - // 2. Aplicar scale al point centrat - float scaled_x = centered_x * scale; - float scaled_y = centered_y * scale; + // 2. Aplicar scale al point centrat + float scaled_x = centered_x * scale; + float scaled_y = centered_y * scale; - // 3. Aplicar rotación - float cos_a = std::cos(angle); - float sin_a = std::sin(angle); + // 3. Aplicar rotación + float cos_a = std::cos(angle); + float sin_a = std::sin(angle); - float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a); - float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a); + float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a); + float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a); - // 4. Aplicar traslación a posición mundial - return {.x = rotated_x + position.x, .y = rotated_y + position.y}; -} - -DebrisManager::DebrisManager(Rendering::Renderer* renderer) - : renderer_(renderer) { - // Inicialitzar todos los debris como inactius - for (auto& debris : debris_pool_) { - debris.active = false; - } -} - -void DebrisManager::explode(const std::shared_ptr& shape, - const Vec2& centro, - float angle, - float scale, - float velocitat_base, - float brightness, - const Vec2& velocitat_objecte, - float velocitat_angular, - float factor_herencia_visual, - const std::string& sound, - SDL_Color color) { - if (!shape || !shape->isValid()) { - return; + // 4. Aplicar traslación a posición mundial + return {.x = rotated_x + position.x, .y = rotated_y + position.y}; } - // Reproducir sonido de explosión - Audio::get()->playSound(sound, Audio::Group::GAME); + DebrisManager::DebrisManager(Rendering::Renderer* renderer) + : renderer_(renderer) { + // Inicialitzar todos los debris como inactius + for (auto& debris : debris_pool_) { + debris.active = false; + } + } - const Vec2& shape_centre = shape->getCenter(); + void DebrisManager::explode(const std::shared_ptr& shape, + const Vec2& centro, + float angle, + float scale, + float velocitat_base, + float brightness, + const Vec2& velocitat_objecte, + float velocitat_angular, + float factor_herencia_visual, + const std::string& sound, + SDL_Color color, + float lifetime, + float friction, + int segment_multiplier) { + if (!shape || !shape->isValid()) { + return; + } - for (const auto& primitive : shape->getPrimitives()) { - for (const auto& [local_p1, local_p2] : extractSegments(primitive)) { - // Transformar points locals → coordenades mundials - Vec2 world_p1 = transformPoint(local_p1, shape_centre, centro, angle, scale); - Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale); + // Reproducir sonido de explosión + Audio::get()->playSound(sound, Audio::Group::GAME); - // Si el pool es ple, no té sentit continuar amb la resta de segments - if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, - velocitat_objecte, velocitat_angular, - factor_herencia_visual, color)) { - return; + const Vec2& shape_centre = shape->getCenter(); + + // Multiplier: cada segment s'emet N vegades amb direccions aleatòries + // distintes (la variació ±15° de computeExplosionDirection ho garanteix). + const int COPIES = std::max(1, segment_multiplier); + + for (int copy = 0; copy < COPIES; copy++) { + for (const auto& primitive : shape->getPrimitives()) { + for (const auto& [local_p1, local_p2] : extractSegments(primitive)) { + // Transformar points locals → coordenades mundials + Vec2 world_p1 = transformPoint(local_p1, shape_centre, centro, angle, scale); + Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale); + + // Si el pool es ple, no té sentit continuar amb la resta de segments + if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction)) { + return; + } + } } } } -} -auto DebrisManager::extractSegments(const Graphics::ShapePrimitive& primitive) - -> std::vector> { - std::vector> segments; + auto DebrisManager::extractSegments(const Graphics::ShapePrimitive& primitive) + -> std::vector> { + std::vector> segments; - if (primitive.type == Graphics::PrimitiveType::POLYLINE) { - // Polyline: extreure segments consecutius - for (size_t i = 0; i + 1 < primitive.points.size(); i++) { - segments.emplace_back(primitive.points[i], primitive.points[i + 1]); + if (primitive.type == Graphics::PrimitiveType::POLYLINE) { + // Polyline: extreure segments consecutius + for (size_t i = 0; i + 1 < primitive.points.size(); i++) { + segments.emplace_back(primitive.points[i], primitive.points[i + 1]); + } + return segments; + } + // PrimitiveType::LINE: un únic segment (si té els 2 punts) + if (primitive.points.size() >= 2) { + segments.emplace_back(primitive.points[0], primitive.points[1]); } return segments; } - // PrimitiveType::LINE: un únic segment (si té els 2 punts) - if (primitive.points.size() >= 2) { - segments.emplace_back(primitive.points[0], primitive.points[1]); - } - return segments; -} -auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, - const Vec2& centro, float velocitat_base, float brightness, - const Vec2& velocitat_objecte, float velocitat_angular, - float factor_herencia_visual, SDL_Color color) -> bool { - Debris* debris = findFreeSlot(); - if (debris == nullptr) { - std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n"; - return false; - } - - // Geometria - debris->p1 = world_p1; - debris->p2 = world_p2; - - // Direcció radial (desde el centro hacia el segment) - Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro); - - // Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte) - float speed = - velocitat_base + - (((std::rand() / static_cast(RAND_MAX)) * 2.0F - 1.0F) * - Defaults::Physics::Debris::VARIACIO_VELOCITAT); - debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x; - debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y; - debris->acceleration = Defaults::Physics::Debris::ACCELERACIO; - - // Rotación de trayectoria (con conversió a tangencial si excedeix cap) - applyAngularVelocity(*debris, direccio, velocitat_angular); - - // Rotación visual (proporcional o aleatòria) - applyVisualRotation(*debris, velocitat_angular, factor_herencia_visual); - - debris->angle_rotacio = 0.0F; - - // Vida i shrinking - debris->temps_vida = 0.0F; - debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA; - debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE; - - // Visuals heretades - debris->brightness = brightness; - debris->color = color; - - debris->active = true; - return true; -} - -void DebrisManager::applyAngularVelocity(Debris& debris, const Vec2& direccio, - float velocitat_angular) { - if (std::abs(velocitat_angular) <= 0.01F) { - debris.velocitat_rot = 0.0F; // Nave: sin curvas - return; - } - - // FASE 1: Aplicar herència i variació - float factor_herencia = - Defaults::Physics::Debris::FACTOR_HERENCIA_MIN + - ((std::rand() / static_cast(RAND_MAX)) * - (Defaults::Physics::Debris::FACTOR_HERENCIA_MAX - - Defaults::Physics::Debris::FACTOR_HERENCIA_MIN)); - float velocitat_ang_heretada = velocitat_angular * factor_herencia; - float variacio = ((std::rand() / static_cast(RAND_MAX)) * 0.2F) - 0.1F; - velocitat_ang_heretada *= (1.0F + variacio); - - // FASE 2: Cap a la velocity màxima; l'excés es converteix en tangencial - constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_ROT_MAX; - float abs_ang = std::abs(velocitat_ang_heretada); - float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F; - - if (abs_ang <= CAP) { - debris.velocitat_rot = velocitat_ang_heretada; - return; - } - - // Excés: converteix l'excés de velocitat angular en velocitat tangencial lineal - float excess = abs_ang - CAP; - constexpr float RADIUS = 20.0F; // Radi típic de la shape (enemigos = 20 px) - float v_tangential = excess * RADIUS; - - // Direcció tangencial: perpendicular a la radial (90° CCW): tangent = (-dy, dx) - debris.velocity.x += -direccio.y * v_tangential; - debris.velocity.y += direccio.x * v_tangential; - - // Velocitat angular limitada al cap (preservant el signe) - debris.velocitat_rot = sign_ang * CAP; -} - -void DebrisManager::applyVisualRotation(Debris& debris, float velocitat_angular, - float factor_herencia_visual) { - if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) { - // Heredar rotación visual con factor proporcional + ±5% de variació - debris.velocitat_rot_visual = debris.velocitat_rot * factor_herencia_visual; - float variacio_visual = - ((std::rand() / static_cast(RAND_MAX)) * 0.1F) - 0.05F; - debris.velocitat_rot_visual *= (1.0F + variacio_visual); - return; - } - - // Rotación visual aleatòria (factor = 0.0 o sin velocidad angular) - debris.velocitat_rot_visual = - Defaults::Physics::Debris::ROTACIO_MIN + - ((std::rand() / static_cast(RAND_MAX)) * - (Defaults::Physics::Debris::ROTACIO_MAX - - Defaults::Physics::Debris::ROTACIO_MIN)); - - // 50% probabilitat de rotación en sentit contrari - if (std::rand() % 2 == 0) { - debris.velocitat_rot_visual = -debris.velocitat_rot_visual; - } -} - -void DebrisManager::update(float delta_time) { - for (auto& debris : debris_pool_) { - if (!debris.active) { - continue; + auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool { + Debris* debris = findFreeSlot(); + if (debris == nullptr) { + std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n"; + return false; } - // 1. Actualitzar time de vida - debris.temps_vida += delta_time; + // Geometria + debris->p1 = world_p1; + debris->p2 = world_p2; - // Desactivar si ha superat time màxim - if (debris.temps_vida >= debris.temps_max) { - debris.active = false; - continue; + // Direcció radial (desde el centro hacia el segment) + Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro); + + // Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte) + float speed = + velocitat_base + + (((std::rand() / static_cast(RAND_MAX)) * 2.0F - 1.0F) * + Defaults::Physics::Debris::VARIACIO_VELOCITAT); + debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x; + debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y; + debris->acceleration = friction; + + // Rotación de trayectoria (con conversió a tangencial si excedeix cap) + applyAngularVelocity(*debris, direccio, velocitat_angular); + + // Rotación visual (proporcional o aleatòria) + applyVisualRotation(*debris, velocitat_angular, factor_herencia_visual); + + debris->angle_rotacio = 0.0F; + + // Vida i shrinking — min_lifetime és el temps mínim garantit; després + // el fragment mor quan |velocity| < MIN_SPEED_TO_DIE. + debris->temps_vida = 0.0F; + debris->min_lifetime = lifetime; + debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE; + + // Visuals heretades + debris->brightness = brightness; + debris->color = color; + + debris->active = true; + return true; + } + + void DebrisManager::applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular) { + if (std::abs(velocitat_angular) <= 0.01F) { + debris.velocitat_rot = 0.0F; // Nave: sin curvas + return; } - // 2. Actualitzar velocity (desacceleració) - // Aplicar fricció en la direcció del movement - float speed = std::sqrt((debris.velocity.x * debris.velocity.x) + - (debris.velocity.y * debris.velocity.y)); + // FASE 1: Aplicar herència i variació + float factor_herencia = + Defaults::Physics::Debris::FACTOR_HERENCIA_MIN + + ((std::rand() / static_cast(RAND_MAX)) * + (Defaults::Physics::Debris::FACTOR_HERENCIA_MAX - + Defaults::Physics::Debris::FACTOR_HERENCIA_MIN)); + float velocitat_ang_heretada = velocitat_angular * factor_herencia; + float variacio = ((std::rand() / static_cast(RAND_MAX)) * 0.2F) - 0.1F; + velocitat_ang_heretada *= (1.0F + variacio); - if (speed > 1.0F) { - // Calcular direcció normalitzada - float dir_x = debris.velocity.x / speed; - float dir_y = debris.velocity.y / speed; + // FASE 2: Cap a la velocity màxima; l'excés es converteix en tangencial + constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_ROT_MAX; + float abs_ang = std::abs(velocitat_ang_heretada); + float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F; - // Aplicar aceleración negativa (fricció) - float nova_speed = speed + (debris.acceleration * delta_time); - nova_speed = std::max(nova_speed, 0.0F); - - debris.velocity.x = dir_x * nova_speed; - debris.velocity.y = dir_y * nova_speed; - } else { - // Velocidad mucho baixa, aturar - debris.velocity.x = 0.0F; - debris.velocity.y = 0.0F; + if (abs_ang <= CAP) { + debris.velocitat_rot = velocitat_ang_heretada; + return; } - // 2b. Rotar vector de velocity (trayectoria curva) - if (std::abs(debris.velocitat_rot) > 0.01F) { - // Calcular angle de rotación este frame - float dangle = debris.velocitat_rot * delta_time; + // Excés: converteix l'excés de velocitat angular en velocitat tangencial lineal + float excess = abs_ang - CAP; + constexpr float RADIUS = 20.0F; // Radi típic de la shape (enemigos = 20 px) + float v_tangential = excess * RADIUS; - // Rotar vector de velocity usant matriu de rotación 2D - float vel_x_old = debris.velocity.x; - float vel_y_old = debris.velocity.y; + // Direcció tangencial: perpendicular a la radial (90° CCW): tangent = (-dy, dx) + debris.velocity.x += -direccio.y * v_tangential; + debris.velocity.y += direccio.x * v_tangential; - float cos_a = std::cos(dangle); - float sin_a = std::sin(dangle); + // Velocitat angular limitada al cap (preservant el signe) + debris.velocitat_rot = sign_ang * CAP; + } - debris.velocity.x = (vel_x_old * cos_a) - (vel_y_old * sin_a); - debris.velocity.y = (vel_x_old * sin_a) + (vel_y_old * cos_a); + void DebrisManager::applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual) { + if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) { + // Heredar rotación visual con factor proporcional + ±5% de variació + debris.velocitat_rot_visual = debris.velocitat_rot * factor_herencia_visual; + float variacio_visual = + ((std::rand() / static_cast(RAND_MAX)) * 0.1F) - 0.05F; + debris.velocitat_rot_visual *= (1.0F + variacio_visual); + return; } - // 2c. Aplicar fricció angular (desacceleració gradual) - if (std::abs(debris.velocitat_rot) > 0.01F) { - float sign = (debris.velocitat_rot > 0) ? 1.0F : -1.0F; - float reduccion = - Defaults::Physics::Debris::FRICCIO_ANGULAR * delta_time; - debris.velocitat_rot -= sign * reduccion; + // Rotación visual aleatòria (factor = 0.0 o sin velocidad angular) + debris.velocitat_rot_visual = + Defaults::Physics::Debris::ROTACIO_MIN + + ((std::rand() / static_cast(RAND_MAX)) * + (Defaults::Physics::Debris::ROTACIO_MAX - + Defaults::Physics::Debris::ROTACIO_MIN)); - // Evitar canvi de signe (no pot passar de CW a CCW) - if ((debris.velocitat_rot > 0) != (sign > 0)) { - debris.velocitat_rot = 0.0F; + // 50% probabilitat de rotación en sentit contrari + if (std::rand() % 2 == 0) { + debris.velocitat_rot_visual = -debris.velocitat_rot_visual; + } + } + + void DebrisManager::update(float delta_time) { + for (auto& debris : debris_pool_) { + if (!debris.active) { + continue; + } + + // 1. Actualitzar time de vida + debris.temps_vida += delta_time; + + // Política de mort: viu sí o sí durant min_lifetime; després mor + // quan la velocity cau per sota d'un llindar. Així els fragments + // ràpids no desapareixen en moviment. + if (debris.temps_vida >= debris.min_lifetime) { + const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) + + (debris.velocity.y * debris.velocity.y); + if (SPEED_SQ < Defaults::Physics::Debris::MIN_SPEED_TO_DIE_SQ) { + debris.active = false; + continue; + } + } + + // 2. Actualitzar velocity (desacceleració) + // Aplicar fricció en la direcció del movement + float speed = std::sqrt((debris.velocity.x * debris.velocity.x) + + (debris.velocity.y * debris.velocity.y)); + + if (speed > 1.0F) { + // Calcular direcció normalitzada + float dir_x = debris.velocity.x / speed; + float dir_y = debris.velocity.y / speed; + + // Aplicar aceleración negativa (fricció) + float nova_speed = speed + (debris.acceleration * delta_time); + nova_speed = std::max(nova_speed, 0.0F); + + debris.velocity.x = dir_x * nova_speed; + debris.velocity.y = dir_y * nova_speed; + } else { + // Velocidad mucho baixa, aturar + debris.velocity.x = 0.0F; + debris.velocity.y = 0.0F; + } + + // 2b. Rotar vector de velocity (trayectoria curva) + if (std::abs(debris.velocitat_rot) > 0.01F) { + // Calcular angle de rotación este frame + float dangle = debris.velocitat_rot * delta_time; + + // Rotar vector de velocity usant matriu de rotación 2D + float vel_x_old = debris.velocity.x; + float vel_y_old = debris.velocity.y; + + float cos_a = std::cos(dangle); + float sin_a = std::sin(dangle); + + debris.velocity.x = (vel_x_old * cos_a) - (vel_y_old * sin_a); + debris.velocity.y = (vel_x_old * sin_a) + (vel_y_old * cos_a); + } + + // 2c. Aplicar fricció angular (desacceleració gradual) + if (std::abs(debris.velocitat_rot) > 0.01F) { + float sign = (debris.velocitat_rot > 0) ? 1.0F : -1.0F; + float reduccion = + Defaults::Physics::Debris::FRICCIO_ANGULAR * delta_time; + debris.velocitat_rot -= sign * reduccion; + + // Evitar canvi de signe (no pot passar de CW a CCW) + if ((debris.velocitat_rot > 0) != (sign > 0)) { + debris.velocitat_rot = 0.0F; + } + } + + // 3. Calcular centro del segment + Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F, + .y = (debris.p1.y + debris.p2.y) / 2.0F}; + + // 4. Actualitzar posición del centro + centro.x += debris.velocity.x * delta_time; + centro.y += debris.velocity.y * delta_time; + + // 5. Actualitzar rotación VISUAL + debris.angle_rotacio += debris.velocitat_rot_visual * delta_time; + + // 6. Aplicar shrinking (reducció de distancia entre points). + // El shrink es normalitza al min_lifetime (capat a 1.0) perquè els + // fragments que viuen més no es continuïn fent més petits per sempre. + const float SHRINK_T = std::min(debris.temps_vida / debris.min_lifetime, 1.0F); + float shrink_factor = 1.0F - (debris.factor_shrink * SHRINK_T); + shrink_factor = std::max(0.0F, shrink_factor); // No negatiu + + // Calcular distancia original entre points + float dx = debris.p2.x - debris.p1.x; + float dy = debris.p2.y - debris.p1.y; + + // 7. Reconstruir segment con nueva mida i rotación + float half_length = std::sqrt((dx * dx) + (dy * dy)) * shrink_factor / 2.0F; + float original_angle = std::atan2(dy, dx); + float new_angle = original_angle + debris.angle_rotacio; + + debris.p1.x = centro.x - (half_length * std::cos(new_angle)); + debris.p1.y = centro.y - (half_length * std::sin(new_angle)); + debris.p2.x = centro.x + (half_length * std::cos(new_angle)); + debris.p2.y = centro.y + (half_length * std::sin(new_angle)); + } + } + + void DebrisManager::draw() const { + for (const auto& debris : debris_pool_) { + if (!debris.active) { + continue; + } + + // Dibujar segmento con brightness y color heredados del padre. + Rendering::linea(renderer_, + static_cast(debris.p1.x), + static_cast(debris.p1.y), + static_cast(debris.p2.x), + static_cast(debris.p2.y), + debris.brightness, + 0.0F, + debris.color); + } + } + + auto DebrisManager::findFreeSlot() -> Debris* { + for (auto& debris : debris_pool_) { + if (!debris.active) { + return &debris; } } - - // 3. Calcular centro del segment - Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F, - .y = (debris.p1.y + debris.p2.y) / 2.0F}; - - // 4. Actualitzar posición del centro - centro.x += debris.velocity.x * delta_time; - centro.y += debris.velocity.y * delta_time; - - // 5. Actualitzar rotación VISUAL - debris.angle_rotacio += debris.velocitat_rot_visual * delta_time; - - // 6. Aplicar shrinking (reducció de distancia entre points) - float shrink_factor = - 1.0F - (debris.factor_shrink * debris.temps_vida / debris.temps_max); - shrink_factor = std::max(0.0F, shrink_factor); // No negatiu - - // Calcular distancia original entre points - float dx = debris.p2.x - debris.p1.x; - float dy = debris.p2.y - debris.p1.y; - - // 7. Reconstruir segment con nueva mida i rotación - float half_length = std::sqrt((dx * dx) + (dy * dy)) * shrink_factor / 2.0F; - float original_angle = std::atan2(dy, dx); - float new_angle = original_angle + debris.angle_rotacio; - - debris.p1.x = centro.x - (half_length * std::cos(new_angle)); - debris.p1.y = centro.y - (half_length * std::sin(new_angle)); - debris.p2.x = centro.x + (half_length * std::cos(new_angle)); - debris.p2.y = centro.y + (half_length * std::sin(new_angle)); + return nullptr; // Pool ple } -} -void DebrisManager::draw() const { - for (const auto& debris : debris_pool_) { - if (!debris.active) { - continue; + auto DebrisManager::computeExplosionDirection(const Vec2& p1, + const Vec2& p2, + const Vec2& centre_objecte) -> Vec2 { + // 1. Calcular centro del segment + float centro_seg_x = (p1.x + p2.x) / 2.0F; + float centro_seg_y = (p1.y + p2.y) / 2.0F; + + // 2. Calcular vector des del centro de l'objecte hacia el centro del segment + // Això garanteix que la direcció siempre apunte hacia fuera (direcció radial) + float dx = centro_seg_x - centre_objecte.x; + float dy = centro_seg_y - centre_objecte.y; + + // 3. Normalitzar (obtenir vector unitari) + float length = std::sqrt((dx * dx) + (dy * dy)); + if (length < 0.001F) { + // Segment al centro (cas extrem mucho improbable), retornar direcció aleatòria + float angle_rand = + (std::rand() / static_cast(RAND_MAX)) * 2.0F * Defaults::Math::PI; + return {.x = std::cos(angle_rand), .y = std::sin(angle_rand)}; } - // Dibujar segmento con brightness y color heredados del padre. - Rendering::linea(renderer_, - static_cast(debris.p1.x), - static_cast(debris.p1.y), - static_cast(debris.p2.x), - static_cast(debris.p2.y), - debris.brightness, 0.0F, debris.color); - } -} + dx /= length; + dy /= length; -auto DebrisManager::findFreeSlot() -> Debris* { - for (auto& debris : debris_pool_) { - if (!debris.active) { - return &debris; + // 4. Añadir variació aleatòria pequeña (±15°) per varietat visual + float angle_variacio = + ((std::rand() % 30) - 15) * Defaults::Math::PI / 180.0F; + + float cos_v = std::cos(angle_variacio); + float sin_v = std::sin(angle_variacio); + + float final_x = (dx * cos_v) - (dy * sin_v); + float final_y = (dx * sin_v) + (dy * cos_v); + + return {.x = final_x, .y = final_y}; + } + + void DebrisManager::reset() { + for (auto& debris : debris_pool_) { + debris.active = false; } } - return nullptr; // Pool ple -} -auto DebrisManager::computeExplosionDirection(const Vec2& p1, - const Vec2& p2, - const Vec2& centre_objecte) -> Vec2 { - // 1. Calcular centro del segment - float centro_seg_x = (p1.x + p2.x) / 2.0F; - float centro_seg_y = (p1.y + p2.y) / 2.0F; - - // 2. Calcular vector des del centro de l'objecte hacia el centro del segment - // Això garanteix que la direcció siempre apunte hacia fuera (direcció radial) - float dx = centro_seg_x - centre_objecte.x; - float dy = centro_seg_y - centre_objecte.y; - - // 3. Normalitzar (obtenir vector unitari) - float length = std::sqrt((dx * dx) + (dy * dy)); - if (length < 0.001F) { - // Segment al centro (cas extrem mucho improbable), retornar direcció aleatòria - float angle_rand = - (std::rand() / static_cast(RAND_MAX)) * 2.0F * Defaults::Math::PI; - return {.x = std::cos(angle_rand), .y = std::sin(angle_rand)}; - } - - dx /= length; - dy /= length; - - // 4. Añadir variació aleatòria pequeña (±15°) per varietat visual - float angle_variacio = - ((std::rand() % 30) - 15) * Defaults::Math::PI / 180.0F; - - float cos_v = std::cos(angle_variacio); - float sin_v = std::sin(angle_variacio); - - float final_x = (dx * cos_v) - (dy * sin_v); - float final_y = (dx * sin_v) + (dy * cos_v); - - return {.x = final_x, .y = final_y}; -} - -void DebrisManager::reset() { - for (auto& debris : debris_pool_) { - debris.active = false; - } -} - -auto DebrisManager::getActiveCount() const -> int { - int count = 0; - for (const auto& debris : debris_pool_) { - if (debris.active) { - count++; + auto DebrisManager::getActiveCount() const -> int { + int count = 0; + for (const auto& debris : debris_pool_) { + if (debris.active) { + count++; + } } + return count; } - return count; -} } // namespace Effects diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp index 48c0a43..6681deb 100644 --- a/source/game/effects/debris_manager.hpp +++ b/source/game/effects/debris_manager.hpp @@ -3,7 +3,6 @@ #pragma once -#include "core/rendering/render_context.hpp" #include #include @@ -13,15 +12,16 @@ #include "core/defaults.hpp" #include "core/graphics/shape.hpp" +#include "core/rendering/render_context.hpp" #include "core/types.hpp" #include "debris.hpp" namespace Effects { -// Gestor de fragments de explosions -// Manté un pool de objectes Debris i gestiona el seu cicle de vida -class DebrisManager { - public: + // Gestor de fragments de explosions + // Manté un pool de objectes Debris i gestiona el seu cicle de vida + class DebrisManager { + public: explicit DebrisManager(Rendering::Renderer* renderer); // Crear explosión a partir de una shape @@ -34,6 +34,9 @@ class DebrisManager { // - velocitat_objecte: velocity de l'objecte que explota (px/s, per defecte 0) // - velocitat_angular: velocity angular heretada (rad/s, per defecte 0) // - factor_herencia_visual: factor de herència rotación visual (0.0-1.0, per defecte 0.0) + // - lifetime: temps de vida del debris (s, per defecte TEMPS_VIDA = 2s) + // - friction: desacceleració del debris (px/s², per defecte ACCELERACIO = -60) + // - segment_multiplier: nombre de còpies per segment (per defecte 1 = sense duplicar) void explode(const std::shared_ptr& shape, const Vec2& centro, float angle, @@ -44,7 +47,10 @@ class DebrisManager { float velocitat_angular = 0.0F, float factor_herencia_visual = 0.0F, const std::string& sound = Defaults::Sound::EXPLOSION, - SDL_Color color = {0, 0, 0, 0}); // alpha==0 → fragmentos usan oscilador global + SDL_Color color = {0, 0, 0, 0}, // alpha==0 → fragmentos usan oscilador global + float lifetime = Defaults::Physics::Debris::TEMPS_VIDA, + float friction = Defaults::Physics::Debris::ACCELERACIO, + int segment_multiplier = 1); // Actualitzar todos los fragments active void update(float delta_time); @@ -58,14 +64,13 @@ class DebrisManager { // Obtenir número de fragments active [[nodiscard]] auto getActiveCount() const -> int; - private: + private: Rendering::Renderer* renderer_; // Pool de fragments (màxim concurrent) - // Un pentágono té 5 línies, 15 enemigos = 75 línies - // + ship (3 línies) + balas (5 línies * 3) = 93 línies màxim - // Arrodonit a 100 per seguretat - static constexpr int MAX_DEBRIS = 150; + // Pentàgon 5 línies × 15 enemics × multiplier 3 = 225 trossos només d'enemics. + // + ship (3 línies) + balas (5 línies × 3) = ~243. Arrodonit a 300. + static constexpr int MAX_DEBRIS = 300; std::array debris_pool_; // Trobar primer slot inactiu @@ -81,14 +86,9 @@ class DebrisManager { -> std::vector>; // Inicialitza un debris en un slot lliure i el deixa actiu. Retorna // false si el pool está ple (la cridadora ha d'aturar el bucle). - auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, - const Vec2& centro, float velocitat_base, float brightness, - const Vec2& velocitat_objecte, float velocitat_angular, - float factor_herencia_visual, SDL_Color color) -> bool; - static void applyAngularVelocity(Debris& debris, const Vec2& direccio, - float velocitat_angular); - static void applyVisualRotation(Debris& debris, float velocitat_angular, - float factor_herencia_visual); -}; + auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool; + static void applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular); + static void applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual); + }; } // namespace Effects diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index a26b7fe..85c362a 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -73,7 +73,10 @@ namespace Systems::Collision { DROTACIO, 0.0F, // sin herencia visual Defaults::Sound::EXPLOSION, - COLOR); + COLOR, + Defaults::Physics::Debris::ENEMY_LIFETIME, + Defaults::Physics::Debris::ENEMY_FRICTION, + Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER); } } // anonymous namespace From 048263a1d02d1a1c9fd6adebc5feff59c1b353a4 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 12:53:01 +0200 Subject: [PATCH 20/27] =?UTF-8?q?feat(debris):=20modelo=20INTACTO=E2=86=92?= =?UTF-8?q?MENGUANDO=E2=86=920=20(sin=20pop,=20fade-out=20por=20tama=C3=B1?= =?UTF-8?q?o)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/defaults/physics.hpp | 20 +-- source/game/effects/debris.hpp | 21 +-- source/game/effects/debris_manager.cpp | 161 ++++++++++++----------- source/game/effects/debris_manager.hpp | 2 +- source/game/systems/collision_system.cpp | 2 +- 5 files changed, 105 insertions(+), 101 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 617a73b..f3ad7db 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -25,15 +25,17 @@ namespace Defaults::Physics { constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²) constexpr float ROTACIO_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s) constexpr float ROTACIO_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s) - constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa - constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION) - constexpr float SHRINK_RATE = 0.5F; // Reducció de mida (factor sobre min_lifetime) - // Política de mort: passat el min_lifetime, el fragment mor quan la - // seva velocity cau per sota d'aquest llindar. Així els fragments - // ràpids no "popen" en moviment. - constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update - constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE; + // Política de mort en dos fases: + // 1. INTACTE: size_factor = 1.0 durant INTACT_TIME segons. Si la velocity + // cau per sota de SHRINK_SPEED_THRESHOLD abans, dispara la transició + // (el que arribi primer). + // 2. MENGUANT: size_factor decreix linealment a SHRINK_RATE per segon + // fins arribar a 0 → el fragment mor (mai "popa" en moviment). + constexpr float INTACT_TIME = 2.0F; // Default temps intacte (s) + constexpr float SHRINK_SPEED_THRESHOLD = 30.0F; // Velocity sota la qual dispara la mengua (px/s) + constexpr float SHRINK_SPEED_THRESHOLD_SQ = SHRINK_SPEED_THRESHOLD * SHRINK_SPEED_THRESHOLD; + constexpr float SHRINK_RATE = 1.0F; // 1/s = 1 segon per encongir de 100% a 0% // Herència de velocity angular (trayectorias curvas) constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat @@ -49,7 +51,7 @@ namespace Defaults::Physics { // Tuneig específic de l'explosió d'enemic (overrides als defaults // que es passen com a paràmetres opcionals a explode()). - constexpr float ENEMY_LIFETIME = 3.0F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més + constexpr float ENEMY_INTACT_TIME = 1.5F; // Temps intacte abans de menguar (s) constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més constexpr int ENEMY_SEGMENT_MULTIPLIER = 3; // Còpies de cada segment (5 cares × 3 = 15 trossos) diff --git a/source/game/effects/debris.hpp b/source/game/effects/debris.hpp index bc8f365..62f3ce3 100644 --- a/source/game/effects/debris.hpp +++ b/source/game/effects/debris.hpp @@ -24,16 +24,17 @@ namespace Effects { float velocitat_rot; // Velocidad de rotación de TRAYECTORIA (rad/s) float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s) - // Estat de vida - // Política: viu sempre durant min_lifetime, després mor quan - // |velocity| < MIN_SPEED_TO_DIE (definit en Defaults). Així els - // fragments ràpids no "popen" en moviment. - float temps_vida; // Temps transcorregut (segons) - float min_lifetime; // Temps mínim garantit (segons) - bool active; // Està actiu? - - // Shrinking (reducció de distancia entre points) - float factor_shrink; // Factor de reducció per segon (0.0-1.0) + // Estat de vida en dos fases: + // 1. INTACTE (shrinking=false): size_factor=1.0 durant intact_time segons, + // o fins que la velocity cau per sota de SHRINK_SPEED_THRESHOLD. + // 2. MENGUANT (shrinking=true): size_factor decreix a shrink_rate/s fins + // arribar a 0 → mor. Mai pop visual: sempre fade-out. + float temps_vida; // Temps transcorregut (segons) + float intact_time; // Temps en fase INTACTE (segons) + bool shrinking; // false=intacte, true=menguant + float size_factor; // Mida actual (1.0=ple, 0.0=mort) + float shrink_rate; // Velocity de menguada (per segon) + bool active; // Està actiu? // Rendering float brightness; // Factor de brightness (0.0-1.0, heretat de l'objecte original) diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index 0c263e0..195e499 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -136,11 +136,14 @@ namespace Effects { debris->angle_rotacio = 0.0F; - // Vida i shrinking — min_lifetime és el temps mínim garantit; després - // el fragment mor quan |velocity| < MIN_SPEED_TO_DIE. + // Estat inicial: INTACTE durant `lifetime` segons (o fins que la + // velocity baixi de SHRINK_SPEED_THRESHOLD). Després MENGUANT a + // SHRINK_RATE per segon fins arribar a size_factor=0 → mor. debris->temps_vida = 0.0F; - debris->min_lifetime = lifetime; - debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE; + debris->intact_time = lifetime; + debris->shrinking = false; + debris->size_factor = 1.0F; + debris->shrink_rate = Defaults::Physics::Debris::SHRINK_RATE; // Visuals heretades debris->brightness = brightness; @@ -212,102 +215,100 @@ namespace Effects { } } + // INTACTE → MENGUANT → mort. Retorna true si el debris segueix viu. + static auto updateLifecycle(Debris& debris, float delta_time) -> bool { + debris.temps_vida += delta_time; + + const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) + + (debris.velocity.y * debris.velocity.y); + + if (!debris.shrinking) { + const bool TIME_TRIGGER = debris.temps_vida >= debris.intact_time; + const bool SPEED_TRIGGER = SPEED_SQ < Defaults::Physics::Debris::SHRINK_SPEED_THRESHOLD_SQ; + if (TIME_TRIGGER || SPEED_TRIGGER) { + debris.shrinking = true; + } + } + + if (debris.shrinking) { + debris.size_factor -= debris.shrink_rate * delta_time; + if (debris.size_factor <= 0.0F) { + debris.active = false; + return false; + } + } + return true; + } + + // Fricció lineal: redueix |velocity| en acceleration per segon. + static void applyLinearFriction(Debris& debris, float delta_time) { + const float SPEED = std::sqrt((debris.velocity.x * debris.velocity.x) + + (debris.velocity.y * debris.velocity.y)); + if (SPEED > 1.0F) { + const float DIR_X = debris.velocity.x / SPEED; + const float DIR_Y = debris.velocity.y / SPEED; + const float NEW_SPEED = std::max(SPEED + (debris.acceleration * delta_time), 0.0F); + debris.velocity.x = DIR_X * NEW_SPEED; + debris.velocity.y = DIR_Y * NEW_SPEED; + } else { + debris.velocity.x = 0.0F; + debris.velocity.y = 0.0F; + } + } + + // Rota el vector velocity (trajectòria corba) i aplica fricció angular. + static void applyAngularDynamics(Debris& debris, float delta_time) { + if (std::abs(debris.velocitat_rot) <= 0.01F) { + return; + } + + // Rotar vector de velocity amb matriu 2D + const float DANGLE = debris.velocitat_rot * delta_time; + const float VX = debris.velocity.x; + const float VY = debris.velocity.y; + const float COS_A = std::cos(DANGLE); + const float SIN_A = std::sin(DANGLE); + debris.velocity.x = (VX * COS_A) - (VY * SIN_A); + debris.velocity.y = (VX * SIN_A) + (VY * COS_A); + + // Fricció angular (desacceleració gradual) + const float SIGN = (debris.velocitat_rot > 0) ? 1.0F : -1.0F; + const float REDUCCION = Defaults::Physics::Debris::FRICCIO_ANGULAR * delta_time; + debris.velocitat_rot -= SIGN * REDUCCION; + if ((debris.velocitat_rot > 0) != (SIGN > 0)) { + debris.velocitat_rot = 0.0F; + } + } + void DebrisManager::update(float delta_time) { for (auto& debris : debris_pool_) { if (!debris.active) { continue; } - // 1. Actualitzar time de vida - debris.temps_vida += delta_time; - - // Política de mort: viu sí o sí durant min_lifetime; després mor - // quan la velocity cau per sota d'un llindar. Així els fragments - // ràpids no desapareixen en moviment. - if (debris.temps_vida >= debris.min_lifetime) { - const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) + - (debris.velocity.y * debris.velocity.y); - if (SPEED_SQ < Defaults::Physics::Debris::MIN_SPEED_TO_DIE_SQ) { - debris.active = false; - continue; - } + if (!updateLifecycle(debris, delta_time)) { + continue; } - // 2. Actualitzar velocity (desacceleració) - // Aplicar fricció en la direcció del movement - float speed = std::sqrt((debris.velocity.x * debris.velocity.x) + - (debris.velocity.y * debris.velocity.y)); + applyLinearFriction(debris, delta_time); + applyAngularDynamics(debris, delta_time); - if (speed > 1.0F) { - // Calcular direcció normalitzada - float dir_x = debris.velocity.x / speed; - float dir_y = debris.velocity.y / speed; - - // Aplicar aceleración negativa (fricció) - float nova_speed = speed + (debris.acceleration * delta_time); - nova_speed = std::max(nova_speed, 0.0F); - - debris.velocity.x = dir_x * nova_speed; - debris.velocity.y = dir_y * nova_speed; - } else { - // Velocidad mucho baixa, aturar - debris.velocity.x = 0.0F; - debris.velocity.y = 0.0F; - } - - // 2b. Rotar vector de velocity (trayectoria curva) - if (std::abs(debris.velocitat_rot) > 0.01F) { - // Calcular angle de rotación este frame - float dangle = debris.velocitat_rot * delta_time; - - // Rotar vector de velocity usant matriu de rotación 2D - float vel_x_old = debris.velocity.x; - float vel_y_old = debris.velocity.y; - - float cos_a = std::cos(dangle); - float sin_a = std::sin(dangle); - - debris.velocity.x = (vel_x_old * cos_a) - (vel_y_old * sin_a); - debris.velocity.y = (vel_x_old * sin_a) + (vel_y_old * cos_a); - } - - // 2c. Aplicar fricció angular (desacceleració gradual) - if (std::abs(debris.velocitat_rot) > 0.01F) { - float sign = (debris.velocitat_rot > 0) ? 1.0F : -1.0F; - float reduccion = - Defaults::Physics::Debris::FRICCIO_ANGULAR * delta_time; - debris.velocitat_rot -= sign * reduccion; - - // Evitar canvi de signe (no pot passar de CW a CCW) - if ((debris.velocitat_rot > 0) != (sign > 0)) { - debris.velocitat_rot = 0.0F; - } - } - - // 3. Calcular centro del segment + // Posició: nou centre = centre antic + velocity · dt Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F, .y = (debris.p1.y + debris.p2.y) / 2.0F}; - - // 4. Actualitzar posición del centro centro.x += debris.velocity.x * delta_time; centro.y += debris.velocity.y * delta_time; - // 5. Actualitzar rotación VISUAL + // Rotació visual del segment debris.angle_rotacio += debris.velocitat_rot_visual * delta_time; - // 6. Aplicar shrinking (reducció de distancia entre points). - // El shrink es normalitza al min_lifetime (capat a 1.0) perquè els - // fragments que viuen més no es continuïn fent més petits per sempre. - const float SHRINK_T = std::min(debris.temps_vida / debris.min_lifetime, 1.0F); - float shrink_factor = 1.0F - (debris.factor_shrink * SHRINK_T); - shrink_factor = std::max(0.0F, shrink_factor); // No negatiu - - // Calcular distancia original entre points + // Mida actual = size_factor (1.0 intacte, decreix durant MENGUANT) + const float SHRINK_FACTOR = std::max(0.0F, debris.size_factor); float dx = debris.p2.x - debris.p1.x; float dy = debris.p2.y - debris.p1.y; // 7. Reconstruir segment con nueva mida i rotación - float half_length = std::sqrt((dx * dx) + (dy * dy)) * shrink_factor / 2.0F; + float half_length = std::sqrt((dx * dx) + (dy * dy)) * SHRINK_FACTOR / 2.0F; float original_angle = std::atan2(dy, dx); float new_angle = original_angle + debris.angle_rotacio; diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp index 6681deb..95b45e2 100644 --- a/source/game/effects/debris_manager.hpp +++ b/source/game/effects/debris_manager.hpp @@ -48,7 +48,7 @@ namespace Effects { float factor_herencia_visual = 0.0F, const std::string& sound = Defaults::Sound::EXPLOSION, SDL_Color color = {0, 0, 0, 0}, // alpha==0 → fragmentos usan oscilador global - float lifetime = Defaults::Physics::Debris::TEMPS_VIDA, + float lifetime = Defaults::Physics::Debris::INTACT_TIME, float friction = Defaults::Physics::Debris::ACCELERACIO, int segment_multiplier = 1); diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index 85c362a..f000d1e 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -74,7 +74,7 @@ namespace Systems::Collision { 0.0F, // sin herencia visual Defaults::Sound::EXPLOSION, COLOR, - Defaults::Physics::Debris::ENEMY_LIFETIME, + Defaults::Physics::Debris::ENEMY_INTACT_TIME, Defaults::Physics::Debris::ENEMY_FRICTION, Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER); } From e84f555a668a80be6694069930f5f2028ef0d1ff Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 13:23:16 +0200 Subject: [PATCH 21/27] =?UTF-8?q?fix(debris):=20rotaci=C3=B3n=20visual=20d?= =?UTF-8?q?ecae=20con=20fricci=C3=B3n=20+=20modulada=20por=20size=5Ffactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/defaults/physics.hpp | 3 +- source/game/effects/debris_manager.cpp | 57 ++++++++++++++++---------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index f3ad7db..10d942a 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -40,7 +40,8 @@ namespace Defaults::Physics { // Herència de velocity angular (trayectorias curvas) constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat constexpr float FACTOR_HERENCIA_MAX = 1.0F; // Màxim 100% del drotacio heredat - constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²) + constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració de la rotació de TRAJECTÒRIA (rad/s²) + constexpr float FRICCIO_VISUAL = 0.1F; // Desacceleració de la rotació VISUAL (més suau) // Velocity heredada de la nau a l'explosió (80% del feel original). constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F; diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index 195e499..24a90c4 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -256,30 +256,42 @@ namespace Effects { } } - // Rota el vector velocity (trajectòria corba) i aplica fricció angular. - static void applyAngularDynamics(Debris& debris, float delta_time) { - if (std::abs(debris.velocitat_rot) <= 0.01F) { + // Aplica fricció a una velocity angular (genèric per a trajectòria i visual). + static void decayAngular(float& vel, float friction, float delta_time) { + if (std::abs(vel) <= 0.01F) { + vel = 0.0F; return; } - - // Rotar vector de velocity amb matriu 2D - const float DANGLE = debris.velocitat_rot * delta_time; - const float VX = debris.velocity.x; - const float VY = debris.velocity.y; - const float COS_A = std::cos(DANGLE); - const float SIN_A = std::sin(DANGLE); - debris.velocity.x = (VX * COS_A) - (VY * SIN_A); - debris.velocity.y = (VX * SIN_A) + (VY * COS_A); - - // Fricció angular (desacceleració gradual) - const float SIGN = (debris.velocitat_rot > 0) ? 1.0F : -1.0F; - const float REDUCCION = Defaults::Physics::Debris::FRICCIO_ANGULAR * delta_time; - debris.velocitat_rot -= SIGN * REDUCCION; - if ((debris.velocitat_rot > 0) != (SIGN > 0)) { - debris.velocitat_rot = 0.0F; + const float SIGN = (vel > 0) ? 1.0F : -1.0F; + vel -= SIGN * friction * delta_time; + if ((vel > 0) != (SIGN > 0)) { + vel = 0.0F; } } + // Rota el vector velocity (trajectòria corba) i aplica fricció a ambdues + // velocitats angulars (trajectòria i visual). + static void applyAngularDynamics(Debris& debris, float delta_time) { + // Rotar vector de velocity amb matriu 2D (només si encara gira la trajectòria) + if (std::abs(debris.velocitat_rot) > 0.01F) { + const float DANGLE = debris.velocitat_rot * delta_time; + const float VX = debris.velocity.x; + const float VY = debris.velocity.y; + const float COS_A = std::cos(DANGLE); + const float SIN_A = std::sin(DANGLE); + debris.velocity.x = (VX * COS_A) - (VY * SIN_A); + debris.velocity.y = (VX * SIN_A) + (VY * COS_A); + } + + // Decay independent de les dues velocitats angulars. + decayAngular(debris.velocitat_rot, + Defaults::Physics::Debris::FRICCIO_ANGULAR, + delta_time); + decayAngular(debris.velocitat_rot_visual, + Defaults::Physics::Debris::FRICCIO_VISUAL, + delta_time); + } + void DebrisManager::update(float delta_time) { for (auto& debris : debris_pool_) { if (!debris.active) { @@ -299,8 +311,11 @@ namespace Effects { centro.x += debris.velocity.x * delta_time; centro.y += debris.velocity.y * delta_time; - // Rotació visual del segment - debris.angle_rotacio += debris.velocitat_rot_visual * delta_time; + // Rotació visual del segment. Modulada per size_factor: durant + // INTACTE (size=1) no afecta, però quan el segment mengua la + // rotació també s'apaga, evitant l'efecte "hèlix d'avió" quan + // el segment es fa molt petit. + debris.angle_rotacio += debris.velocitat_rot_visual * debris.size_factor * delta_time; // Mida actual = size_factor (1.0 intacte, decreix durant MENGUANT) const float SHRINK_FACTOR = std::max(0.0F, debris.size_factor); From 9b25e875f31da56eeab539ced3c60e37595932a9 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 13:37:12 +0200 Subject: [PATCH 22/27] fix(debris): bug rotacion cuadratica + shrink exponencial; geometria autoritativa --- source/core/defaults/physics.hpp | 3 +- source/game/effects/debris.hpp | 21 +++++++-- source/game/effects/debris_manager.cpp | 58 ++++++++++++------------ source/game/effects/debris_manager.hpp | 8 ++-- source/game/systems/collision_system.cpp | 3 +- 5 files changed, 55 insertions(+), 38 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 10d942a..9f4062e 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -52,7 +52,8 @@ namespace Defaults::Physics { // Tuneig específic de l'explosió d'enemic (overrides als defaults // que es passen com a paràmetres opcionals a explode()). - constexpr float ENEMY_INTACT_TIME = 1.5F; // Temps intacte abans de menguar (s) + constexpr float ENEMY_INTACT_TIME = 0.5F; // Temps intacte abans de menguar (s) — comencen aviat + constexpr float ENEMY_SHRINK_RATE = 0.33F; // 1/3 per segon → 3s de fade-out lent constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més constexpr int ENEMY_SEGMENT_MULTIPLIER = 3; // Còpies de cada segment (5 cares × 3 = 15 trossos) diff --git a/source/game/effects/debris.hpp b/source/game/effects/debris.hpp index 62f3ce3..1211be2 100644 --- a/source/game/effects/debris.hpp +++ b/source/game/effects/debris.hpp @@ -9,18 +9,29 @@ namespace Effects { // Debris: un segment de línia que vola perpendicular a sí mismo - // Representa un fragment de una shape destruïda (ship, enemy, bullet) + // Representa un fragment de una shape destruïda (ship, enemy, bullet). + // + // Representació autoritaritzada (font de veritat): + // centro + original_angle (orientació spawn) + angle_rotacio (acumulat) + // + original_half_length × size_factor (mida actual) + // p1/p2 es reconstrueixen cada frame des d'aquestes dades — no es modifiquen + // iterativament (evita bugs de rotació quadràtica i shrink exponencial). struct Debris { - // Geometria del segment (2 points en coordenades mundials) - Vec2 p1; // Vec2 inicial del segment - Vec2 p2; // Vec2 final del segment + // Geometria del segment (2 points en coordenades mundials, derivats) + Vec2 p1; + Vec2 p2; + + // Geometria original (font de veritat, no canvia després del spawn) + Vec2 centro; // Centre actual del segment (es mou amb velocity) + float original_angle; // Orientació del segment al spawn (rad) + float original_half_length; // Mitja-longitud al spawn (px) // Física Vec2 velocity; // Velocidad en px/s (components x, y) float acceleration; // Aceleración negativa (fricció) en px/s² // Rotación - float angle_rotacio; // Angle de rotación acumulat (radians) + float angle_rotacio; // Acumulat de rotació visual des del spawn (radians) float velocitat_rot; // Velocidad de rotación de TRAYECTORIA (rad/s) float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s) diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index 24a90c4..08cc966 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -57,7 +57,8 @@ namespace Effects { SDL_Color color, float lifetime, float friction, - int segment_multiplier) { + int segment_multiplier, + float shrink_rate) { if (!shape || !shape->isValid()) { return; } @@ -79,7 +80,7 @@ namespace Effects { Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale); // Si el pool es ple, no té sentit continuar amb la resta de segments - if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction)) { + if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction, shrink_rate)) { return; } } @@ -105,14 +106,21 @@ namespace Effects { return segments; } - auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool { + auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, float shrink_rate) -> bool { Debris* debris = findFreeSlot(); if (debris == nullptr) { std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n"; return false; } - // Geometria + // Geometria autoritaritzada: centro + original_angle + original_half_length. + // p1/p2 es reconstrueixen cada frame en update() des d'aquestes dades. + const float DX = world_p2.x - world_p1.x; + const float DY = world_p2.y - world_p1.y; + debris->centro = {.x = (world_p1.x + world_p2.x) / 2.0F, + .y = (world_p1.y + world_p2.y) / 2.0F}; + debris->original_angle = std::atan2(DY, DX); + debris->original_half_length = std::sqrt((DX * DX) + (DY * DY)) / 2.0F; debris->p1 = world_p1; debris->p2 = world_p2; @@ -138,12 +146,12 @@ namespace Effects { // Estat inicial: INTACTE durant `lifetime` segons (o fins que la // velocity baixi de SHRINK_SPEED_THRESHOLD). Després MENGUANT a - // SHRINK_RATE per segon fins arribar a size_factor=0 → mor. + // shrink_rate per segon fins arribar a size_factor=0 → mor. debris->temps_vida = 0.0F; debris->intact_time = lifetime; debris->shrinking = false; debris->size_factor = 1.0F; - debris->shrink_rate = Defaults::Physics::Debris::SHRINK_RATE; + debris->shrink_rate = shrink_rate; // Visuals heretades debris->brightness = brightness; @@ -305,32 +313,26 @@ namespace Effects { applyLinearFriction(debris, delta_time); applyAngularDynamics(debris, delta_time); - // Posició: nou centre = centre antic + velocity · dt - Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F, - .y = (debris.p1.y + debris.p2.y) / 2.0F}; - centro.x += debris.velocity.x * delta_time; - centro.y += debris.velocity.y * delta_time; + // Posició del centre: integra velocity (separat de p1/p2). + debris.centro.x += debris.velocity.x * delta_time; + debris.centro.y += debris.velocity.y * delta_time; - // Rotació visual del segment. Modulada per size_factor: durant + // Acumular rotació visual. Modulada per size_factor: durant // INTACTE (size=1) no afecta, però quan el segment mengua la - // rotació també s'apaga, evitant l'efecte "hèlix d'avió" quan - // el segment es fa molt petit. + // rotació també s'apaga. debris.angle_rotacio += debris.velocitat_rot_visual * debris.size_factor * delta_time; - // Mida actual = size_factor (1.0 intacte, decreix durant MENGUANT) - const float SHRINK_FACTOR = std::max(0.0F, debris.size_factor); - float dx = debris.p2.x - debris.p1.x; - float dy = debris.p2.y - debris.p1.y; - - // 7. Reconstruir segment con nueva mida i rotación - float half_length = std::sqrt((dx * dx) + (dy * dy)) * SHRINK_FACTOR / 2.0F; - float original_angle = std::atan2(dy, dx); - float new_angle = original_angle + debris.angle_rotacio; - - debris.p1.x = centro.x - (half_length * std::cos(new_angle)); - debris.p1.y = centro.y - (half_length * std::sin(new_angle)); - debris.p2.x = centro.x + (half_length * std::cos(new_angle)); - debris.p2.y = centro.y + (half_length * std::sin(new_angle)); + // Reconstruir p1/p2 des de la geometria autoritaritzada: + // centro + (cos/sin(original_angle + angle_rotacio)) × original_half_length × size_factor + // No iteratiu — evita la rotació quadràtica i el shrink exponencial. + const float CURRENT_ANGLE = debris.original_angle + debris.angle_rotacio; + const float HALF_LEN = debris.original_half_length * std::max(0.0F, debris.size_factor); + const float COS_A = std::cos(CURRENT_ANGLE); + const float SIN_A = std::sin(CURRENT_ANGLE); + debris.p1.x = debris.centro.x - (HALF_LEN * COS_A); + debris.p1.y = debris.centro.y - (HALF_LEN * SIN_A); + debris.p2.x = debris.centro.x + (HALF_LEN * COS_A); + debris.p2.y = debris.centro.y + (HALF_LEN * SIN_A); } } diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp index 95b45e2..8647b1a 100644 --- a/source/game/effects/debris_manager.hpp +++ b/source/game/effects/debris_manager.hpp @@ -34,9 +34,10 @@ namespace Effects { // - velocitat_objecte: velocity de l'objecte que explota (px/s, per defecte 0) // - velocitat_angular: velocity angular heretada (rad/s, per defecte 0) // - factor_herencia_visual: factor de herència rotación visual (0.0-1.0, per defecte 0.0) - // - lifetime: temps de vida del debris (s, per defecte TEMPS_VIDA = 2s) + // - lifetime: temps INTACTE del debris (s, per defecte INTACT_TIME) // - friction: desacceleració del debris (px/s², per defecte ACCELERACIO = -60) // - segment_multiplier: nombre de còpies per segment (per defecte 1 = sense duplicar) + // - shrink_rate: velocity de mengua durant MENGUANT (1/s, per defecte SHRINK_RATE) void explode(const std::shared_ptr& shape, const Vec2& centro, float angle, @@ -50,7 +51,8 @@ namespace Effects { SDL_Color color = {0, 0, 0, 0}, // alpha==0 → fragmentos usan oscilador global float lifetime = Defaults::Physics::Debris::INTACT_TIME, float friction = Defaults::Physics::Debris::ACCELERACIO, - int segment_multiplier = 1); + int segment_multiplier = 1, + float shrink_rate = Defaults::Physics::Debris::SHRINK_RATE); // Actualitzar todos los fragments active void update(float delta_time); @@ -86,7 +88,7 @@ namespace Effects { -> std::vector>; // Inicialitza un debris en un slot lliure i el deixa actiu. Retorna // false si el pool está ple (la cridadora ha d'aturar el bucle). - auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool; + auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, float shrink_rate) -> bool; static void applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular); static void applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual); }; diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index f000d1e..8a63fc5 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -76,7 +76,8 @@ namespace Systems::Collision { COLOR, Defaults::Physics::Debris::ENEMY_INTACT_TIME, Defaults::Physics::Debris::ENEMY_FRICTION, - Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER); + Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER, + Defaults::Physics::Debris::ENEMY_SHRINK_RATE); } } // anonymous namespace From 0c8a9b744ec65c09eede86afc1d93de1c74ec073 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 13:41:20 +0200 Subject: [PATCH 23/27] tune(debris): un poco mas de rotacion + shrink mas rapido (1.4s) --- source/core/defaults/physics.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 9f4062e..da559b7 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -23,8 +23,8 @@ namespace Defaults::Physics { constexpr float VELOCITAT_BASE = 80.0F; // Velocidad inicial (px/s) constexpr float VARIACIO_VELOCITAT = 40.0F; // ±variació aleatòria (px/s) constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²) - constexpr float ROTACIO_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s) - constexpr float ROTACIO_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s) + constexpr float ROTACIO_MIN = 0.2F; // Rotación mínima (rad/s ~11.5°/s) + constexpr float ROTACIO_MAX = 0.5F; // Rotación màxima (rad/s ~28.6°/s) // Política de mort en dos fases: // 1. INTACTE: size_factor = 1.0 durant INTACT_TIME segons. Si la velocity @@ -41,7 +41,7 @@ namespace Defaults::Physics { constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat constexpr float FACTOR_HERENCIA_MAX = 1.0F; // Màxim 100% del drotacio heredat constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració de la rotació de TRAJECTÒRIA (rad/s²) - constexpr float FRICCIO_VISUAL = 0.1F; // Desacceleració de la rotació VISUAL (més suau) + constexpr float FRICCIO_VISUAL = 0.05F; // Desacceleració de la rotació VISUAL (suau, persisteix més) // Velocity heredada de la nau a l'explosió (80% del feel original). constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F; @@ -53,7 +53,7 @@ namespace Defaults::Physics { // Tuneig específic de l'explosió d'enemic (overrides als defaults // que es passen com a paràmetres opcionals a explode()). constexpr float ENEMY_INTACT_TIME = 0.5F; // Temps intacte abans de menguar (s) — comencen aviat - constexpr float ENEMY_SHRINK_RATE = 0.33F; // 1/3 per segon → 3s de fade-out lent + constexpr float ENEMY_SHRINK_RATE = 0.7F; // 0.7 per segon → ~1.4s de fade-out constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més constexpr int ENEMY_SEGMENT_MULTIPLIER = 3; // Còpies de cada segment (5 cares × 3 = 15 trossos) From ae1d1397b17446f000d6b0021204e38b83e3d36f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 13:46:25 +0200 Subject: [PATCH 24/27] revert: vuelve al modelo de efd18ff + ENEMY_LIFETIME 3.0 -> 4.5 --- source/core/defaults/physics.hpp | 28 ++- source/game/effects/debris.hpp | 42 ++--- source/game/effects/debris_manager.cpp | 218 +++++++++++------------ source/game/effects/debris_manager.hpp | 10 +- source/game/systems/collision_system.cpp | 5 +- 5 files changed, 133 insertions(+), 170 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index da559b7..96cc9a9 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -23,25 +23,22 @@ namespace Defaults::Physics { constexpr float VELOCITAT_BASE = 80.0F; // Velocidad inicial (px/s) constexpr float VARIACIO_VELOCITAT = 40.0F; // ±variació aleatòria (px/s) constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²) - constexpr float ROTACIO_MIN = 0.2F; // Rotación mínima (rad/s ~11.5°/s) - constexpr float ROTACIO_MAX = 0.5F; // Rotación màxima (rad/s ~28.6°/s) + constexpr float ROTACIO_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s) + constexpr float ROTACIO_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s) + constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa + constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION) + constexpr float SHRINK_RATE = 0.5F; // Reducció de mida (factor sobre min_lifetime) - // Política de mort en dos fases: - // 1. INTACTE: size_factor = 1.0 durant INTACT_TIME segons. Si la velocity - // cau per sota de SHRINK_SPEED_THRESHOLD abans, dispara la transició - // (el que arribi primer). - // 2. MENGUANT: size_factor decreix linealment a SHRINK_RATE per segon - // fins arribar a 0 → el fragment mor (mai "popa" en moviment). - constexpr float INTACT_TIME = 2.0F; // Default temps intacte (s) - constexpr float SHRINK_SPEED_THRESHOLD = 30.0F; // Velocity sota la qual dispara la mengua (px/s) - constexpr float SHRINK_SPEED_THRESHOLD_SQ = SHRINK_SPEED_THRESHOLD * SHRINK_SPEED_THRESHOLD; - constexpr float SHRINK_RATE = 1.0F; // 1/s = 1 segon per encongir de 100% a 0% + // Política de mort: passat el min_lifetime, el fragment mor quan la + // seva velocity cau per sota d'aquest llindar. Així els fragments + // ràpids no "popen" en moviment. + constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update + constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE; // Herència de velocity angular (trayectorias curvas) constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat constexpr float FACTOR_HERENCIA_MAX = 1.0F; // Màxim 100% del drotacio heredat - constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració de la rotació de TRAJECTÒRIA (rad/s²) - constexpr float FRICCIO_VISUAL = 0.05F; // Desacceleració de la rotació VISUAL (suau, persisteix més) + constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²) // Velocity heredada de la nau a l'explosió (80% del feel original). constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F; @@ -52,8 +49,7 @@ namespace Defaults::Physics { // Tuneig específic de l'explosió d'enemic (overrides als defaults // que es passen com a paràmetres opcionals a explode()). - constexpr float ENEMY_INTACT_TIME = 0.5F; // Temps intacte abans de menguar (s) — comencen aviat - constexpr float ENEMY_SHRINK_RATE = 0.7F; // 0.7 per segon → ~1.4s de fade-out + constexpr float ENEMY_LIFETIME = 4.5F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més constexpr int ENEMY_SEGMENT_MULTIPLIER = 3; // Còpies de cada segment (5 cares × 3 = 15 trossos) diff --git a/source/game/effects/debris.hpp b/source/game/effects/debris.hpp index 1211be2..bc8f365 100644 --- a/source/game/effects/debris.hpp +++ b/source/game/effects/debris.hpp @@ -9,43 +9,31 @@ namespace Effects { // Debris: un segment de línia que vola perpendicular a sí mismo - // Representa un fragment de una shape destruïda (ship, enemy, bullet). - // - // Representació autoritaritzada (font de veritat): - // centro + original_angle (orientació spawn) + angle_rotacio (acumulat) - // + original_half_length × size_factor (mida actual) - // p1/p2 es reconstrueixen cada frame des d'aquestes dades — no es modifiquen - // iterativament (evita bugs de rotació quadràtica i shrink exponencial). + // Representa un fragment de una shape destruïda (ship, enemy, bullet) struct Debris { - // Geometria del segment (2 points en coordenades mundials, derivats) - Vec2 p1; - Vec2 p2; - - // Geometria original (font de veritat, no canvia després del spawn) - Vec2 centro; // Centre actual del segment (es mou amb velocity) - float original_angle; // Orientació del segment al spawn (rad) - float original_half_length; // Mitja-longitud al spawn (px) + // Geometria del segment (2 points en coordenades mundials) + Vec2 p1; // Vec2 inicial del segment + Vec2 p2; // Vec2 final del segment // Física Vec2 velocity; // Velocidad en px/s (components x, y) float acceleration; // Aceleración negativa (fricció) en px/s² // Rotación - float angle_rotacio; // Acumulat de rotació visual des del spawn (radians) + float angle_rotacio; // Angle de rotación acumulat (radians) float velocitat_rot; // Velocidad de rotación de TRAYECTORIA (rad/s) float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s) - // Estat de vida en dos fases: - // 1. INTACTE (shrinking=false): size_factor=1.0 durant intact_time segons, - // o fins que la velocity cau per sota de SHRINK_SPEED_THRESHOLD. - // 2. MENGUANT (shrinking=true): size_factor decreix a shrink_rate/s fins - // arribar a 0 → mor. Mai pop visual: sempre fade-out. - float temps_vida; // Temps transcorregut (segons) - float intact_time; // Temps en fase INTACTE (segons) - bool shrinking; // false=intacte, true=menguant - float size_factor; // Mida actual (1.0=ple, 0.0=mort) - float shrink_rate; // Velocity de menguada (per segon) - bool active; // Està actiu? + // Estat de vida + // Política: viu sempre durant min_lifetime, després mor quan + // |velocity| < MIN_SPEED_TO_DIE (definit en Defaults). Així els + // fragments ràpids no "popen" en moviment. + float temps_vida; // Temps transcorregut (segons) + float min_lifetime; // Temps mínim garantit (segons) + bool active; // Està actiu? + + // Shrinking (reducció de distancia entre points) + float factor_shrink; // Factor de reducció per segon (0.0-1.0) // Rendering float brightness; // Factor de brightness (0.0-1.0, heretat de l'objecte original) diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index 08cc966..0c263e0 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -57,8 +57,7 @@ namespace Effects { SDL_Color color, float lifetime, float friction, - int segment_multiplier, - float shrink_rate) { + int segment_multiplier) { if (!shape || !shape->isValid()) { return; } @@ -80,7 +79,7 @@ namespace Effects { Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale); // Si el pool es ple, no té sentit continuar amb la resta de segments - if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction, shrink_rate)) { + if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction)) { return; } } @@ -106,21 +105,14 @@ namespace Effects { return segments; } - auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, float shrink_rate) -> bool { + auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool { Debris* debris = findFreeSlot(); if (debris == nullptr) { std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n"; return false; } - // Geometria autoritaritzada: centro + original_angle + original_half_length. - // p1/p2 es reconstrueixen cada frame en update() des d'aquestes dades. - const float DX = world_p2.x - world_p1.x; - const float DY = world_p2.y - world_p1.y; - debris->centro = {.x = (world_p1.x + world_p2.x) / 2.0F, - .y = (world_p1.y + world_p2.y) / 2.0F}; - debris->original_angle = std::atan2(DY, DX); - debris->original_half_length = std::sqrt((DX * DX) + (DY * DY)) / 2.0F; + // Geometria debris->p1 = world_p1; debris->p2 = world_p2; @@ -144,14 +136,11 @@ namespace Effects { debris->angle_rotacio = 0.0F; - // Estat inicial: INTACTE durant `lifetime` segons (o fins que la - // velocity baixi de SHRINK_SPEED_THRESHOLD). Després MENGUANT a - // shrink_rate per segon fins arribar a size_factor=0 → mor. + // Vida i shrinking — min_lifetime és el temps mínim garantit; després + // el fragment mor quan |velocity| < MIN_SPEED_TO_DIE. debris->temps_vida = 0.0F; - debris->intact_time = lifetime; - debris->shrinking = false; - debris->size_factor = 1.0F; - debris->shrink_rate = shrink_rate; + debris->min_lifetime = lifetime; + debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE; // Visuals heretades debris->brightness = brightness; @@ -223,116 +212,109 @@ namespace Effects { } } - // INTACTE → MENGUANT → mort. Retorna true si el debris segueix viu. - static auto updateLifecycle(Debris& debris, float delta_time) -> bool { - debris.temps_vida += delta_time; - - const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) + - (debris.velocity.y * debris.velocity.y); - - if (!debris.shrinking) { - const bool TIME_TRIGGER = debris.temps_vida >= debris.intact_time; - const bool SPEED_TRIGGER = SPEED_SQ < Defaults::Physics::Debris::SHRINK_SPEED_THRESHOLD_SQ; - if (TIME_TRIGGER || SPEED_TRIGGER) { - debris.shrinking = true; - } - } - - if (debris.shrinking) { - debris.size_factor -= debris.shrink_rate * delta_time; - if (debris.size_factor <= 0.0F) { - debris.active = false; - return false; - } - } - return true; - } - - // Fricció lineal: redueix |velocity| en acceleration per segon. - static void applyLinearFriction(Debris& debris, float delta_time) { - const float SPEED = std::sqrt((debris.velocity.x * debris.velocity.x) + - (debris.velocity.y * debris.velocity.y)); - if (SPEED > 1.0F) { - const float DIR_X = debris.velocity.x / SPEED; - const float DIR_Y = debris.velocity.y / SPEED; - const float NEW_SPEED = std::max(SPEED + (debris.acceleration * delta_time), 0.0F); - debris.velocity.x = DIR_X * NEW_SPEED; - debris.velocity.y = DIR_Y * NEW_SPEED; - } else { - debris.velocity.x = 0.0F; - debris.velocity.y = 0.0F; - } - } - - // Aplica fricció a una velocity angular (genèric per a trajectòria i visual). - static void decayAngular(float& vel, float friction, float delta_time) { - if (std::abs(vel) <= 0.01F) { - vel = 0.0F; - return; - } - const float SIGN = (vel > 0) ? 1.0F : -1.0F; - vel -= SIGN * friction * delta_time; - if ((vel > 0) != (SIGN > 0)) { - vel = 0.0F; - } - } - - // Rota el vector velocity (trajectòria corba) i aplica fricció a ambdues - // velocitats angulars (trajectòria i visual). - static void applyAngularDynamics(Debris& debris, float delta_time) { - // Rotar vector de velocity amb matriu 2D (només si encara gira la trajectòria) - if (std::abs(debris.velocitat_rot) > 0.01F) { - const float DANGLE = debris.velocitat_rot * delta_time; - const float VX = debris.velocity.x; - const float VY = debris.velocity.y; - const float COS_A = std::cos(DANGLE); - const float SIN_A = std::sin(DANGLE); - debris.velocity.x = (VX * COS_A) - (VY * SIN_A); - debris.velocity.y = (VX * SIN_A) + (VY * COS_A); - } - - // Decay independent de les dues velocitats angulars. - decayAngular(debris.velocitat_rot, - Defaults::Physics::Debris::FRICCIO_ANGULAR, - delta_time); - decayAngular(debris.velocitat_rot_visual, - Defaults::Physics::Debris::FRICCIO_VISUAL, - delta_time); - } - void DebrisManager::update(float delta_time) { for (auto& debris : debris_pool_) { if (!debris.active) { continue; } - if (!updateLifecycle(debris, delta_time)) { - continue; + // 1. Actualitzar time de vida + debris.temps_vida += delta_time; + + // Política de mort: viu sí o sí durant min_lifetime; després mor + // quan la velocity cau per sota d'un llindar. Així els fragments + // ràpids no desapareixen en moviment. + if (debris.temps_vida >= debris.min_lifetime) { + const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) + + (debris.velocity.y * debris.velocity.y); + if (SPEED_SQ < Defaults::Physics::Debris::MIN_SPEED_TO_DIE_SQ) { + debris.active = false; + continue; + } } - applyLinearFriction(debris, delta_time); - applyAngularDynamics(debris, delta_time); + // 2. Actualitzar velocity (desacceleració) + // Aplicar fricció en la direcció del movement + float speed = std::sqrt((debris.velocity.x * debris.velocity.x) + + (debris.velocity.y * debris.velocity.y)); - // Posició del centre: integra velocity (separat de p1/p2). - debris.centro.x += debris.velocity.x * delta_time; - debris.centro.y += debris.velocity.y * delta_time; + if (speed > 1.0F) { + // Calcular direcció normalitzada + float dir_x = debris.velocity.x / speed; + float dir_y = debris.velocity.y / speed; - // Acumular rotació visual. Modulada per size_factor: durant - // INTACTE (size=1) no afecta, però quan el segment mengua la - // rotació també s'apaga. - debris.angle_rotacio += debris.velocitat_rot_visual * debris.size_factor * delta_time; + // Aplicar aceleración negativa (fricció) + float nova_speed = speed + (debris.acceleration * delta_time); + nova_speed = std::max(nova_speed, 0.0F); - // Reconstruir p1/p2 des de la geometria autoritaritzada: - // centro + (cos/sin(original_angle + angle_rotacio)) × original_half_length × size_factor - // No iteratiu — evita la rotació quadràtica i el shrink exponencial. - const float CURRENT_ANGLE = debris.original_angle + debris.angle_rotacio; - const float HALF_LEN = debris.original_half_length * std::max(0.0F, debris.size_factor); - const float COS_A = std::cos(CURRENT_ANGLE); - const float SIN_A = std::sin(CURRENT_ANGLE); - debris.p1.x = debris.centro.x - (HALF_LEN * COS_A); - debris.p1.y = debris.centro.y - (HALF_LEN * SIN_A); - debris.p2.x = debris.centro.x + (HALF_LEN * COS_A); - debris.p2.y = debris.centro.y + (HALF_LEN * SIN_A); + debris.velocity.x = dir_x * nova_speed; + debris.velocity.y = dir_y * nova_speed; + } else { + // Velocidad mucho baixa, aturar + debris.velocity.x = 0.0F; + debris.velocity.y = 0.0F; + } + + // 2b. Rotar vector de velocity (trayectoria curva) + if (std::abs(debris.velocitat_rot) > 0.01F) { + // Calcular angle de rotación este frame + float dangle = debris.velocitat_rot * delta_time; + + // Rotar vector de velocity usant matriu de rotación 2D + float vel_x_old = debris.velocity.x; + float vel_y_old = debris.velocity.y; + + float cos_a = std::cos(dangle); + float sin_a = std::sin(dangle); + + debris.velocity.x = (vel_x_old * cos_a) - (vel_y_old * sin_a); + debris.velocity.y = (vel_x_old * sin_a) + (vel_y_old * cos_a); + } + + // 2c. Aplicar fricció angular (desacceleració gradual) + if (std::abs(debris.velocitat_rot) > 0.01F) { + float sign = (debris.velocitat_rot > 0) ? 1.0F : -1.0F; + float reduccion = + Defaults::Physics::Debris::FRICCIO_ANGULAR * delta_time; + debris.velocitat_rot -= sign * reduccion; + + // Evitar canvi de signe (no pot passar de CW a CCW) + if ((debris.velocitat_rot > 0) != (sign > 0)) { + debris.velocitat_rot = 0.0F; + } + } + + // 3. Calcular centro del segment + Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F, + .y = (debris.p1.y + debris.p2.y) / 2.0F}; + + // 4. Actualitzar posición del centro + centro.x += debris.velocity.x * delta_time; + centro.y += debris.velocity.y * delta_time; + + // 5. Actualitzar rotación VISUAL + debris.angle_rotacio += debris.velocitat_rot_visual * delta_time; + + // 6. Aplicar shrinking (reducció de distancia entre points). + // El shrink es normalitza al min_lifetime (capat a 1.0) perquè els + // fragments que viuen més no es continuïn fent més petits per sempre. + const float SHRINK_T = std::min(debris.temps_vida / debris.min_lifetime, 1.0F); + float shrink_factor = 1.0F - (debris.factor_shrink * SHRINK_T); + shrink_factor = std::max(0.0F, shrink_factor); // No negatiu + + // Calcular distancia original entre points + float dx = debris.p2.x - debris.p1.x; + float dy = debris.p2.y - debris.p1.y; + + // 7. Reconstruir segment con nueva mida i rotación + float half_length = std::sqrt((dx * dx) + (dy * dy)) * shrink_factor / 2.0F; + float original_angle = std::atan2(dy, dx); + float new_angle = original_angle + debris.angle_rotacio; + + debris.p1.x = centro.x - (half_length * std::cos(new_angle)); + debris.p1.y = centro.y - (half_length * std::sin(new_angle)); + debris.p2.x = centro.x + (half_length * std::cos(new_angle)); + debris.p2.y = centro.y + (half_length * std::sin(new_angle)); } } diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp index 8647b1a..6681deb 100644 --- a/source/game/effects/debris_manager.hpp +++ b/source/game/effects/debris_manager.hpp @@ -34,10 +34,9 @@ namespace Effects { // - velocitat_objecte: velocity de l'objecte que explota (px/s, per defecte 0) // - velocitat_angular: velocity angular heretada (rad/s, per defecte 0) // - factor_herencia_visual: factor de herència rotación visual (0.0-1.0, per defecte 0.0) - // - lifetime: temps INTACTE del debris (s, per defecte INTACT_TIME) + // - lifetime: temps de vida del debris (s, per defecte TEMPS_VIDA = 2s) // - friction: desacceleració del debris (px/s², per defecte ACCELERACIO = -60) // - segment_multiplier: nombre de còpies per segment (per defecte 1 = sense duplicar) - // - shrink_rate: velocity de mengua durant MENGUANT (1/s, per defecte SHRINK_RATE) void explode(const std::shared_ptr& shape, const Vec2& centro, float angle, @@ -49,10 +48,9 @@ namespace Effects { float factor_herencia_visual = 0.0F, const std::string& sound = Defaults::Sound::EXPLOSION, SDL_Color color = {0, 0, 0, 0}, // alpha==0 → fragmentos usan oscilador global - float lifetime = Defaults::Physics::Debris::INTACT_TIME, + float lifetime = Defaults::Physics::Debris::TEMPS_VIDA, float friction = Defaults::Physics::Debris::ACCELERACIO, - int segment_multiplier = 1, - float shrink_rate = Defaults::Physics::Debris::SHRINK_RATE); + int segment_multiplier = 1); // Actualitzar todos los fragments active void update(float delta_time); @@ -88,7 +86,7 @@ namespace Effects { -> std::vector>; // Inicialitza un debris en un slot lliure i el deixa actiu. Retorna // false si el pool está ple (la cridadora ha d'aturar el bucle). - auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, float shrink_rate) -> bool; + auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool; static void applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular); static void applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual); }; diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index 8a63fc5..85c362a 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -74,10 +74,9 @@ namespace Systems::Collision { 0.0F, // sin herencia visual Defaults::Sound::EXPLOSION, COLOR, - Defaults::Physics::Debris::ENEMY_INTACT_TIME, + Defaults::Physics::Debris::ENEMY_LIFETIME, Defaults::Physics::Debris::ENEMY_FRICTION, - Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER, - Defaults::Physics::Debris::ENEMY_SHRINK_RATE); + Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER); } } // anonymous namespace From 7505de074c9a5a5575e5ed9a2836d0a62c791e16 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 13:55:32 +0200 Subject: [PATCH 25/27] feat(debris): rebote contra los limites del playarea (restitution 0.7) --- source/core/defaults/physics.hpp | 4 +++ source/game/effects/debris_manager.cpp | 38 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index 96cc9a9..ec64ede 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -35,6 +35,10 @@ namespace Defaults::Physics { constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE; + // Rebot contra els límits del PLAYAREA (mateix patró que enemics/ship). + // 0.7 = 70% de l'energia conservada al rebot. + constexpr float RESTITUTION_BOUNDS = 0.7F; + // Herència de velocity angular (trayectorias curvas) constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat constexpr float FACTOR_HERENCIA_MAX = 1.0F; // Màxim 100% del drotacio heredat diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index 0c263e0..9d6bdfc 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -212,6 +212,41 @@ namespace Effects { } } + // Rebot del fragment contra els límits del PLAYAREA (mateix patró que + // PhysicsWorld::resolveBoundsCollisions per a enemics i ships). + static void bounceOffPlayArea(Vec2& centro, Vec2& velocity) { + const auto& bounds = Defaults::Zones::PLAYAREA; + const float MIN_X = bounds.x; + const float MAX_X = bounds.x + bounds.w; + const float MIN_Y = bounds.y; + const float MAX_Y = bounds.y + bounds.h; + constexpr float REST = Defaults::Physics::Debris::RESTITUTION_BOUNDS; + if (centro.x < MIN_X) { + centro.x = MIN_X; + if (velocity.x < 0.0F) { + velocity.x = -velocity.x * REST; + } + } + if (centro.x > MAX_X) { + centro.x = MAX_X; + if (velocity.x > 0.0F) { + velocity.x = -velocity.x * REST; + } + } + if (centro.y < MIN_Y) { + centro.y = MIN_Y; + if (velocity.y < 0.0F) { + velocity.y = -velocity.y * REST; + } + } + if (centro.y > MAX_Y) { + centro.y = MAX_Y; + if (velocity.y > 0.0F) { + velocity.y = -velocity.y * REST; + } + } + } + void DebrisManager::update(float delta_time) { for (auto& debris : debris_pool_) { if (!debris.active) { @@ -292,6 +327,9 @@ namespace Effects { centro.x += debris.velocity.x * delta_time; centro.y += debris.velocity.y * delta_time; + // 4b. Rebot contra els límits del PLAYAREA. + bounceOffPlayArea(centro, debris.velocity); + // 5. Actualitzar rotación VISUAL debris.angle_rotacio += debris.velocitat_rot_visual * delta_time; From 87b96b8226c08e32db58ab9016613534bee17b51 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 14:05:10 +0200 Subject: [PATCH 26/27] fix(debris): bugs rotacion cuadratica y shrink exponencial (geometria autoritativa) --- source/game/effects/debris.hpp | 21 +++++++--- source/game/effects/debris_manager.cpp | 58 +++++++++++++------------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/source/game/effects/debris.hpp b/source/game/effects/debris.hpp index bc8f365..53eeb80 100644 --- a/source/game/effects/debris.hpp +++ b/source/game/effects/debris.hpp @@ -9,18 +9,29 @@ namespace Effects { // Debris: un segment de línia que vola perpendicular a sí mismo - // Representa un fragment de una shape destruïda (ship, enemy, bullet) + // Representa un fragment de una shape destruïda (ship, enemy, bullet). + // + // Representació autoritaritzada (font de veritat per a la geometria): + // centro + original_angle (orientació spawn) + angle_rotacio (acumulat) + // + original_half_length × shrink_factor (mida actual) + // p1/p2 es reconstrueixen cada frame en update() des d'aquestes dades, mai + // de forma iterativa — evita bugs de rotació quadràtica i shrink exponencial. struct Debris { - // Geometria del segment (2 points en coordenades mundials) - Vec2 p1; // Vec2 inicial del segment - Vec2 p2; // Vec2 final del segment + // Geometria del segment (2 points en coordenades mundials, derivats) + Vec2 p1; + Vec2 p2; + + // Geometria original (font de veritat, no canvia després del spawn) + Vec2 centro; // Centre actual del segment (es mou amb velocity) + float original_angle; // Orientació del segment al spawn (rad) + float original_half_length; // Mitja-longitud al spawn (px) // Física Vec2 velocity; // Velocidad en px/s (components x, y) float acceleration; // Aceleración negativa (fricció) en px/s² // Rotación - float angle_rotacio; // Angle de rotación acumulat (radians) + float angle_rotacio; // Acumulat de rotació visual des del spawn (radians) float velocitat_rot; // Velocidad de rotación de TRAYECTORIA (rad/s) float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s) diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index 9d6bdfc..9b8cc3f 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -112,7 +112,14 @@ namespace Effects { return false; } - // Geometria + // Geometria autoritaritzada: centro + original_angle + original_half_length. + // p1/p2 es reconstrueixen cada frame en update() des d'aquestes dades. + const float DX = world_p2.x - world_p1.x; + const float DY = world_p2.y - world_p1.y; + debris->centro = {.x = (world_p1.x + world_p2.x) / 2.0F, + .y = (world_p1.y + world_p2.y) / 2.0F}; + debris->original_angle = std::atan2(DY, DX); + debris->original_half_length = std::sqrt((DX * DX) + (DY * DY)) / 2.0F; debris->p1 = world_p1; debris->p2 = world_p2; @@ -319,40 +326,33 @@ namespace Effects { } } - // 3. Calcular centro del segment - Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F, - .y = (debris.p1.y + debris.p2.y) / 2.0F}; + // 3. Actualitzar posició del centre (integra velocity). + debris.centro.x += debris.velocity.x * delta_time; + debris.centro.y += debris.velocity.y * delta_time; - // 4. Actualitzar posición del centro - centro.x += debris.velocity.x * delta_time; - centro.y += debris.velocity.y * delta_time; + // 4. Rebot contra els límits del PLAYAREA. + bounceOffPlayArea(debris.centro, debris.velocity); - // 4b. Rebot contra els límits del PLAYAREA. - bounceOffPlayArea(centro, debris.velocity); - - // 5. Actualitzar rotación VISUAL + // 5. Actualitzar rotació visual acumulada. debris.angle_rotacio += debris.velocitat_rot_visual * delta_time; - // 6. Aplicar shrinking (reducció de distancia entre points). - // El shrink es normalitza al min_lifetime (capat a 1.0) perquè els - // fragments que viuen més no es continuïn fent més petits per sempre. + // 6. Shrink lineal sobre la longitud ORIGINAL (no iteratiu). + // SHRINK_T va de 0 a 1 al llarg de min_lifetime; després queda + // a 1 i el shrink_factor manté el valor mínim (1 - factor_shrink). const float SHRINK_T = std::min(debris.temps_vida / debris.min_lifetime, 1.0F); - float shrink_factor = 1.0F - (debris.factor_shrink * SHRINK_T); - shrink_factor = std::max(0.0F, shrink_factor); // No negatiu + const float SHRINK_FACTOR = std::max(0.0F, 1.0F - (debris.factor_shrink * SHRINK_T)); - // Calcular distancia original entre points - float dx = debris.p2.x - debris.p1.x; - float dy = debris.p2.y - debris.p1.y; - - // 7. Reconstruir segment con nueva mida i rotación - float half_length = std::sqrt((dx * dx) + (dy * dy)) * shrink_factor / 2.0F; - float original_angle = std::atan2(dy, dx); - float new_angle = original_angle + debris.angle_rotacio; - - debris.p1.x = centro.x - (half_length * std::cos(new_angle)); - debris.p1.y = centro.y - (half_length * std::sin(new_angle)); - debris.p2.x = centro.x + (half_length * std::cos(new_angle)); - debris.p2.y = centro.y + (half_length * std::sin(new_angle)); + // 7. Reconstruir p1/p2 des de la geometria autoritaritzada: + // centro + (cos/sin(original_angle + angle_rotacio)) × original_half_length × shrink_factor + // No iteratiu — evita la rotació quadràtica i el shrink exponencial. + const float CURRENT_ANGLE = debris.original_angle + debris.angle_rotacio; + const float HALF_LEN = debris.original_half_length * SHRINK_FACTOR; + const float COS_A = std::cos(CURRENT_ANGLE); + const float SIN_A = std::sin(CURRENT_ANGLE); + debris.p1.x = debris.centro.x - (HALF_LEN * COS_A); + debris.p1.y = debris.centro.y - (HALF_LEN * SIN_A); + debris.p2.x = debris.centro.x + (HALF_LEN * COS_A); + debris.p2.y = debris.centro.y + (HALF_LEN * SIN_A); } } From 2869c635175c833ee363a0dc6a0e94c0dc871b77 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 17:11:08 +0200 Subject: [PATCH 27/27] tune(debris): N=1, shrink completo y sin herencia angular en enemigos --- source/core/defaults/physics.hpp | 6 +++--- source/game/systems/collision_system.cpp | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/source/core/defaults/physics.hpp b/source/core/defaults/physics.hpp index ec64ede..1de337f 100644 --- a/source/core/defaults/physics.hpp +++ b/source/core/defaults/physics.hpp @@ -27,7 +27,7 @@ namespace Defaults::Physics { constexpr float ROTACIO_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s) constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION) - constexpr float SHRINK_RATE = 0.5F; // Reducció de mida (factor sobre min_lifetime) + constexpr float SHRINK_RATE = 1.0F; // Reducció de mida (1.0 = encoge a 0 al final del min_lifetime) // Política de mort: passat el min_lifetime, el fragment mor quan la // seva velocity cau per sota d'aquest llindar. Així els fragments @@ -53,9 +53,9 @@ namespace Defaults::Physics { // Tuneig específic de l'explosió d'enemic (overrides als defaults // que es passen com a paràmetres opcionals a explode()). - constexpr float ENEMY_LIFETIME = 4.5F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més + constexpr float ENEMY_LIFETIME = 2.5F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més - constexpr int ENEMY_SEGMENT_MULTIPLIER = 3; // Còpies de cada segment (5 cares × 3 = 15 trossos) + constexpr int ENEMY_SEGMENT_MULTIPLIER = 1; // Sense còpies (5 cares = 5 trossos); >1 produeix grups sincronitzats // Angular velocity sin for trajectory inheritance // Excess above this threshold is converted to tangential linear velocity diff --git a/source/game/systems/collision_system.cpp b/source/game/systems/collision_system.cpp index 85c362a..f985b8c 100644 --- a/source/game/systems/collision_system.cpp +++ b/source/game/systems/collision_system.cpp @@ -45,7 +45,6 @@ namespace Systems::Collision { void explodeNow(Context& ctx, Enemy& enemy, uint8_t shooter_id) { const Vec2 ENEMY_POS = enemy.getCenter(); const Vec2 ENEMY_VEL = enemy.getVelocityVector(); - const float DROTACIO = enemy.getRotationDelta(); const float BRIGHTNESS = enemy.getBrightness(); const auto SHAPE = enemy.getShape(); const EnemyType TYPE = enemy.getType(); @@ -70,7 +69,7 @@ namespace Systems::Collision { VELOCITAT_EXPLOSIO, BRIGHTNESS, INHERITED_VEL, - DROTACIO, + 0.0F, // sense herència angular: evita que els 5 trossos curvin en bloc 0.0F, // sin herencia visual Defaults::Sound::EXPLOSION, COLOR,