Merge branch 'rewrite/physics-gpu'
Reescritura completa de la capa de rendering y la arquitectura del juego. Aportaciones principales por fases: - Fase 1: rename masivo a CamelCase/camelBack (clang-tidy) - Fase 2: resolución lógica 1280×720 (16:9) - Fase 3: subsistema de audio importado de AEEA - Fases 5/6: sistema de física vectorial; Ship/Enemy/Bullet sobre RigidBody - Fase 7: migración atómica de SDL_Renderer a SDL3 GPU (Vulkan/Metal) - Fase 8: paleta semántica por entidad y postpro completo (bloom + flicker + background pulse) configurable vía data/config/postfx.yaml - Fase 9: extracción de sistemas de GameScene (collision, continue, init_hud_animator) y descomposición de update en sub-pasos - Plan A: frame loop al Director con interfaz Scene común - Debug overlay (FPS + VSync) toggleable con F11; título de ventana estático
This commit is contained in:
@@ -135,6 +135,46 @@ add_custom_command(
|
|||||||
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
|
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
|
||||||
add_dependencies(${PROJECT_NAME} 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
|
||||||
|
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_dependencies(${PROJECT_NAME} shaders)
|
||||||
|
message(STATUS "Shaders trobats: ${SHADER_SOURCES}")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "glslc no trobat: instal·la 'shaderc' o 'vulkan-sdk' per compilar shaders SPIR-V")
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- STATIC ANALYSIS / FORMAT TARGETS ---
|
# --- STATIC ANALYSIS / FORMAT TARGETS ---
|
||||||
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
||||||
find_program(CLANG_FORMAT_EXE NAMES clang-format)
|
find_program(CLANG_FORMAT_EXE NAMES clang-format)
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Plan de migración Orni Attack → SDL3 GPU + física vectorial
|
||||||
|
|
||||||
|
Documento maestro de la rama `rewrite/physics-gpu`. Resumen del progreso y
|
||||||
|
qué queda. Si pierdes el contexto de la sesión, **léeme primero**.
|
||||||
|
|
||||||
|
## Visión
|
||||||
|
|
||||||
|
Modernizar la base técnica del juego antes de tunear jugabilidad:
|
||||||
|
1. Limpiar legacy (polares, mezcla catalán/inglés, header guards).
|
||||||
|
2. Adoptar formato 16:9 (1280×720).
|
||||||
|
3. Importar subsistemas robustos de AEEA (audio, *input deferido*).
|
||||||
|
4. Implementar física vectorial 2D con rigid bodies + colisiones elásticas.
|
||||||
|
5. **Migrar renderizado a SDL3 GPU sin fallback** ([memoria: project-no-sdl-fallback](./.claude/projects/-home-sergio-gitea-orni-attack/memory/project_no_sdl_fallback.md)).
|
||||||
|
6. Postprocesado y color.
|
||||||
|
|
||||||
|
El tuning de feeling se hace al final, post-GPU.
|
||||||
|
|
||||||
|
## Estado actual
|
||||||
|
|
||||||
|
Rama de trabajo: **`rewrite/physics-gpu`** (pusheada a Gitea).
|
||||||
|
Tag de seguridad: **`beta-3.0`** (snapshot de `main` antes de empezar).
|
||||||
|
|
||||||
|
| Fase | Estado | Commits |
|
||||||
|
|---|---|---|
|
||||||
|
| 0 — Limpieza de código muerto (primitives, polígonos, Bresenham, polares) | ✅ | `6cf990b` |
|
||||||
|
| 1a — `Punt → Vec2` con operadores modernos | ✅ | `cd38101` |
|
||||||
|
| 1b — Renames de entidades + métodos virtuales a camelBack | ✅ | `ae5cc1c` |
|
||||||
|
| 1c — Renames de escenas | ✅ | `5871d29` |
|
||||||
|
| 1d — Renames effects/stage_system/locales | ✅ | `7ee359b` |
|
||||||
|
| 1e — Pragma once + locales restantes + comentarios castellano | ✅ | `bf83f16` |
|
||||||
|
| (fix) — clave YAML `quadrat → cuadrado` post-sweep | ✅ | `56533ca` |
|
||||||
|
| 2 — Cambio a 1280×720 (16:9) | ✅ | `a4f6a55` |
|
||||||
|
| 3 — Import del subsistema de Audio desde AEEA | ✅ | `ed98ef6` |
|
||||||
|
| **4** — Input parcial AEEA | ⏭️ **saltado** (decisión usuario) | — |
|
||||||
|
| 5 — Infraestructura física (`RigidBody`, `PhysicsWorld`) | ✅ | `0fd9360` |
|
||||||
|
| 6a+b — `body_` en Entity + `world_` en GameScene | ✅ | `0574077` |
|
||||||
|
| 6c — Migrar Ship | ✅ | `2fe22ff` |
|
||||||
|
| 6d — Migrar Enemy | ✅ | `27242f5` |
|
||||||
|
| 6e — Migrar Bullet | ✅ | `9993b2d` |
|
||||||
|
| 7a — Infra GPU (shaders + wrappers, runtime dormido) | ✅ | `ba6fd00` |
|
||||||
|
| 7b+c — Swap atómico a SDL3 GPU + line_renderer al pipeline | ✅ | `fa7da4c` |
|
||||||
|
| 7d — Validación visual del usuario en hardware Vulkan | ✅ | — |
|
||||||
|
| 9a — Extraer `Systems::Collision` | ✅ | `896a899` |
|
||||||
|
| 9b — Extraer `Systems::ContinueScreen` | ✅ | `816bc02` |
|
||||||
|
| 9c — Extraer `Systems::InitHud` | ✅ | `a4942fc` |
|
||||||
|
| 9d — Descomponer `GameScene::update` (339→18 LOC) | ✅ | `808abb2` |
|
||||||
|
| **8 — Postprocesado + paleta de colores por entidad** | 🔲 **siguiente** | — |
|
||||||
|
| 10 — Tuning final de masa/restitución/damping | 🔲 | — |
|
||||||
|
| Mac/Metal — shaders MSL en build | 🔲 | — |
|
||||||
|
| 8 — Postprocesado, color, paleta por tipo | 🔲 | — |
|
||||||
|
| 9 — Refactor de GameScene (2.877 LOC → módulos) | 🔲 | — |
|
||||||
|
| 10 — Tuning final de masa/restitución/damping | 🔲 | — |
|
||||||
|
|
||||||
|
## Lo que queda inmediato (Fase 8 — postprocesado + paleta de colores)
|
||||||
|
|
||||||
|
Fase 7 y Fase 9 cerradas. Validación visual SDL3 GPU OK (`Backend GPU: vulkan`).
|
||||||
|
`GameScene.cpp` 1429 → 1015 LOC; `update()` 339 → 18 LOC, complexity baja a
|
||||||
|
< 10 por sub-paso. Tres sistemas extraídos a `source/game/systems/`:
|
||||||
|
- `Systems::Collision` (~210 LOC propios)
|
||||||
|
- `Systems::ContinueScreen` (~160 LOC propios)
|
||||||
|
- `Systems::InitHud` (~155 LOC propios)
|
||||||
|
|
||||||
|
**Fase 8 — siguientes pasos**:
|
||||||
|
1. Color por entidad (paleta semántica): naves blancas, balas verdes,
|
||||||
|
pentagons azules, quadrats rojos, molinillos magenta, debris hereda
|
||||||
|
color del padre. Hoy todo se pinta con el color global del oscilador
|
||||||
|
(`g_current_line_color`). Hay que pasar el color al `pushLine` desde
|
||||||
|
las entidades (cada entity expone un `getColor()`).
|
||||||
|
2. Postprocesado básico:
|
||||||
|
- Bloom/glow: render-to-texture al swapchain real con fragment shader
|
||||||
|
que aplique kernel separable de blur sobre los píxeles iluminados.
|
||||||
|
Requiere un pipeline extra y un par de texturas off-screen.
|
||||||
|
- Opcional: scanlines o leve aberración cromática para feel CRT.
|
||||||
|
|
||||||
|
**Fase 10** (tras 8): tuning de mass/restitution/damping con feedback
|
||||||
|
visual de los colores.
|
||||||
|
|
||||||
|
**Mac/Metal**: cuando vayamos a release de macOS, añadir compilación
|
||||||
|
de los shaders también a MSL (con `spirv-cross` o `glslangValidator`).
|
||||||
|
|
||||||
|
## Memoria del proyecto
|
||||||
|
|
||||||
|
Las preferencias y decisiones persistentes están en:
|
||||||
|
`~/.claude/projects/-home-sergio-gitea-orni-attack/memory/MEMORY.md` y los
|
||||||
|
archivos enlazados desde ahí. Léelos al empezar sesión nueva.
|
||||||
|
|
||||||
|
Resumen:
|
||||||
|
- **No fallback a SDL_Renderer** en Fase 7 — solo GPU o falla.
|
||||||
|
- **Naming**: `.clang-tidy` exige `CamelCase` tipos, `camelBack` métodos,
|
||||||
|
`lower_case_` miembros privados, `UPPER_CASE` constantes.
|
||||||
|
- **Costuras del título tras 16:9** son tuning artístico, no bug, post-Fase 10.
|
||||||
|
- **Push automático** tras cada commit en `rewrite/physics-gpu`.
|
||||||
|
- **Smoke test xvfb** (`timeout 5 xvfb-run -a ./build/orni 2>&1 | head -40`)
|
||||||
|
tras builds importantes para validar arranque.
|
||||||
|
|
||||||
|
## Convenciones técnicas
|
||||||
|
|
||||||
|
- **Lenguaje código**: inglés. **Lenguaje comentarios**: castellano. **Strings
|
||||||
|
de UI**: valenciano (preservados).
|
||||||
|
- **Coordenadas**: cartesianas (`Vec2`). Polares prohibidas (legacy).
|
||||||
|
- **Resolución lógica**: 1280×720. Constantes en `core/defaults.hpp`.
|
||||||
|
- **Audio**: import de AEEA. API: `Audio::get()->playSound/playMusic` con
|
||||||
|
crossfade y efectos.
|
||||||
|
- **Física**: `Physics::RigidBody` en `core/physics/rigid_body.hpp`,
|
||||||
|
`Physics::PhysicsWorld` en `core/physics/physics_world.hpp/.cpp`.
|
||||||
|
Entity tiene `body_` como member. Flujo tri-fase por frame en
|
||||||
|
`GameScene::update`:
|
||||||
|
1. `physics_world_.update(dt)` — integrar y resolver
|
||||||
|
2. `entity.postUpdate(dt)` — sincronizar mirror (center_, angle_)
|
||||||
|
3. Lógica del juego (input, AI, etc.) — aplica fuerzas/cambia velocity
|
||||||
|
|
||||||
|
## Cómo reanudar tras compactación
|
||||||
|
|
||||||
|
1. Lee este archivo.
|
||||||
|
2. Lee `MEMORY.md` (memorias persistentes).
|
||||||
|
3. `git log --oneline -20` para ver últimos commits.
|
||||||
|
4. `git status` para ver si hay trabajo en curso sin commitear.
|
||||||
|
5. Si la fase actual estaba a medias: continúa donde se quedó (este archivo
|
||||||
|
indica en qué fase estamos).
|
||||||
|
6. Tras cada commit, push automático: `git push origin rewrite/physics-gpu`.
|
||||||
|
|
||||||
|
## Configuración física actual (Fase 6e)
|
||||||
|
|
||||||
|
| Entidad | mass | radius (world) | restitution | linear_damping | angular_damping |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Ship | 10.0 | 12 | 0.6 | 1.5 | 0.0 |
|
||||||
|
| Enemy Pentagon | 5.0 | 20 | 1.0 | 0.0 | 0.0 |
|
||||||
|
| Enemy Quadrat | 8.0 | 20 | 1.0 | 0.0 | 0.0 |
|
||||||
|
| Enemy Molinillo | 4.0 | 20 | 1.0 | 0.0 | 0.0 |
|
||||||
|
| Bullet | 0.5 | **0** (cinemática) | 0 | 0 | 0 |
|
||||||
|
| Wall (PLAYAREA bounds) | ∞ (estático) | — | — | — | — |
|
||||||
|
|
||||||
|
Nota: Bullet usa `radius=0` en el body físico para que no participe en las
|
||||||
|
colisiones del world (ni body-body ni bounds). El radio de gameplay
|
||||||
|
(`Defaults::Entities::BULLET_RADIUS = 3`) lo expone vía `getCollisionRadius()`
|
||||||
|
y lo consumen `check_collision` y la detección de salida del PLAYAREA en
|
||||||
|
`Bullet::update`.
|
||||||
|
|
||||||
|
Las paredes son implícitas: `physics_world_.setBounds(PLAYAREA)` en
|
||||||
|
`GameScene::init`. No son `RigidBody` separados, sino un AABB que rebota.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# postfx.yaml - Parámetros del shader de postprocesado
|
||||||
|
#
|
||||||
|
# Este archivo configura el pase final del renderer (bloom + flicker +
|
||||||
|
# background pulse). Se carga al iniciar el juego desde resources.pack.
|
||||||
|
# Si falta o tiene errores, se usan los valores por defecto de
|
||||||
|
# Defaults::PostFx (defaults.hpp).
|
||||||
|
#
|
||||||
|
# Tip de tuning:
|
||||||
|
# - Para más "neón vector", sube bloom.intensity y bloom.radius_px.
|
||||||
|
# - Para más "CRT viejo", sube flicker.amplitude (riesgo de mareo si >0.3).
|
||||||
|
# - Background es muy sutil; pasa los componentes G a 0.15-0.20 para
|
||||||
|
# un fondo verde-tenue más marcado.
|
||||||
|
|
||||||
|
# Bloom / glow: desenfoque gaussiano de las regiones brillantes.
|
||||||
|
bloom:
|
||||||
|
enabled: true
|
||||||
|
intensity: 0.6 # 0..2 — cuanto del bloom se suma a la imagen
|
||||||
|
threshold: 0.30 # 0..1 — luminancia mínima que aporta al bloom
|
||||||
|
radius_px: 2.0 # radio del kernel en píxeles lógicos (1..8 razonable)
|
||||||
|
|
||||||
|
# Flicker: modulación global de brillo (efecto fósforo CRT).
|
||||||
|
# Sustituye a la antigua oscilación CPU del ColorOscillator.
|
||||||
|
flicker:
|
||||||
|
enabled: true
|
||||||
|
amplitude: 0.10 # 0..1 — profundidad del flicker
|
||||||
|
frequency_hz: 6.0 # Hz — velocidad de la pulsación
|
||||||
|
|
||||||
|
# Background pulse: color de fondo oscilante (suma aditiva).
|
||||||
|
# RGB en [0..255]; el shader normaliza a [0..1].
|
||||||
|
background:
|
||||||
|
enabled: true
|
||||||
|
color_min: [0, 5, 0] # negro casi puro
|
||||||
|
color_max: [0, 15, 0] # verde muy tenue
|
||||||
|
pulse_frequency_hz: 6.0 # Hz — sincronizado con flicker por defecto
|
||||||
+1
-17
@@ -1,23 +1,7 @@
|
|||||||
# bullet.shp - Projectil (petit pentàgon)
|
# bullet.shp - Projectil (octàgon, radi=3)
|
||||||
# © 1999 Visente i Sergi (versió Pascal)
|
|
||||||
# © 2025 Port a C++20 amb SDL3
|
|
||||||
|
|
||||||
name: bullet
|
name: bullet
|
||||||
scale: 1.0
|
scale: 1.0
|
||||||
center: 0, 0
|
center: 0, 0
|
||||||
|
|
||||||
# Cercle (octàgon regular radi=3)
|
|
||||||
# 8 punts equidistants (45° entre ells) per aproximar un cercle
|
|
||||||
# Començant a angle=-90° (amunt), rotant sentit horari
|
|
||||||
#
|
|
||||||
# Conversió polar→cartesià (radi=3, SDL: Y creix cap avall):
|
|
||||||
# angle=-90°: (0.00, -3.00)
|
|
||||||
# angle=-45°: (2.12, -2.12)
|
|
||||||
# angle=0°: (3.00, 0.00)
|
|
||||||
# angle=45°: (2.12, 2.12)
|
|
||||||
# angle=90°: (0.00, 3.00)
|
|
||||||
# angle=135°: (-2.12, 2.12)
|
|
||||||
# angle=180°: (-3.00, 0.00)
|
|
||||||
# angle=225°: (-2.12, -2.12)
|
|
||||||
|
|
||||||
polyline: 0,-3 2.12,-2.12 3,0 2.12,2.12 0,3 -2.12,2.12 -3,0 -2.12,-2.12 0,-3
|
polyline: 0,-3 2.12,-2.12 3,0 2.12,2.12 0,3 -2.12,2.12 -3,0 -2.12,-2.12 0,-3
|
||||||
|
|||||||
@@ -1,21 +1,7 @@
|
|||||||
# enemy_pentagon.shp - ORNI enemic (pentàgon regular)
|
# enemy_pentagon.shp - ORNI enemic (pentàgon regular, radi=20)
|
||||||
# © 1999 Visente i Sergi (versió Pascal)
|
|
||||||
# © 2025 Port a C++20 amb SDL3
|
|
||||||
|
|
||||||
name: enemy_pentagon
|
name: enemy_pentagon
|
||||||
scale: 1.0
|
scale: 1.0
|
||||||
center: 0, 0
|
center: 0, 0
|
||||||
|
|
||||||
# Pentàgon regular radi=20
|
|
||||||
# 5 punts equidistants al voltant d'un cercle (72° entre ells)
|
|
||||||
# Començant a angle=-90° (amunt), rotant sentit antihorari
|
|
||||||
#
|
|
||||||
# Angles: -90°, -18°, 54°, 126°, 198°
|
|
||||||
# Conversió polar→cartesià (SDL: Y creix cap avall):
|
|
||||||
# angle=-90°: (0.00, -20.00)
|
|
||||||
# angle=-18°: (19.02, -6.18)
|
|
||||||
# angle=54°: (11.76, 16.18)
|
|
||||||
# angle=126°: (-11.76, 16.18)
|
|
||||||
# angle=198°: (-19.02, -6.18)
|
|
||||||
|
|
||||||
polyline: 0,-20 19.02,-6.18 11.76,16.18 -11.76,16.18 -19.02,-6.18 0,-20
|
polyline: 0,-20 19.02,-6.18 11.76,16.18 -11.76,16.18 -19.02,-6.18 0,-20
|
||||||
|
|||||||
@@ -1,19 +1,7 @@
|
|||||||
# enemy_square.shp - ORNI enemic (quadrat regular)
|
# enemy_square.shp - ORNI enemic (quadrat regular, radi=20)
|
||||||
# © 2025 Port a C++20 amb SDL3
|
|
||||||
|
|
||||||
name: enemy_square
|
name: enemy_square
|
||||||
scale: 1.0
|
scale: 1.0
|
||||||
center: 0, 0
|
center: 0, 0
|
||||||
|
|
||||||
# Quadrat regular radi=20 (circumscrit)
|
|
||||||
# 4 punts equidistants al voltant d'un cercle (90° entre ells)
|
|
||||||
# Començant a angle=-90° (amunt), rotant sentit horari
|
|
||||||
#
|
|
||||||
# Angles: -90°, 0°, 90°, 180°
|
|
||||||
# Conversió polar→cartesià (SDL: Y creix cap avall):
|
|
||||||
# angle=-90°: (0.00, -20.00)
|
|
||||||
# angle=0°: (20.00, 0.00)
|
|
||||||
# angle=90°: (0.00, 20.00)
|
|
||||||
# angle=180°: (-20.00, 0.00)
|
|
||||||
|
|
||||||
polyline: 0,-20 20,0 0,20 -20,0 0,-20
|
polyline: 0,-20 20,0 0,20 -20,0 0,-20
|
||||||
|
|||||||
+2
-18
@@ -1,24 +1,8 @@
|
|||||||
# ship.shp - Nau del jugador 1 (triangle amb base còncava - punta de fletxa)
|
# ship.shp - Nau del jugador 1
|
||||||
# © 1999 Visente i Sergi (versió Pascal)
|
# Triangle amb base còncava (punta de fletxa)
|
||||||
# © 2025 Port a C++20 amb SDL3
|
|
||||||
|
|
||||||
name: ship
|
name: ship
|
||||||
scale: 1.0
|
scale: 1.0
|
||||||
center: 0, 0
|
center: 0, 0
|
||||||
|
|
||||||
# Triangle amb base còncava tipus "punta de fletxa"
|
|
||||||
# Punts originals (polar):
|
|
||||||
# p1: r=12, angle=270° (3π/2) → punta amunt
|
|
||||||
# p2: r=12, angle=45° (π/4) → base dreta-darrere
|
|
||||||
# p3: r=12, angle=135° (3π/4) → base esquerra-darrere
|
|
||||||
#
|
|
||||||
# MODIFICACIÓ: afegit p4 al mig de la base, desplaçat cap al centre
|
|
||||||
# p4: (0, 4) → punt central de la base, cap endins
|
|
||||||
#
|
|
||||||
# Conversió polar→cartesià (angle-90° perquè origen visual és amunt):
|
|
||||||
# p1: (0, -12) → punta
|
|
||||||
# p2: (8.49, 8.49) → base dreta
|
|
||||||
# p4: (0, 4) → base centre (cap endins)
|
|
||||||
# p3: (-8.49, 8.49) → base esquerra
|
|
||||||
|
|
||||||
polyline: 0,-12 8.49,8.49 0,4 -8.49,8.49 0,-12
|
polyline: 0,-12 8.49,8.49 0,4 -8.49,8.49 0,-12
|
||||||
|
|||||||
+3
-22
@@ -1,30 +1,11 @@
|
|||||||
# ship2.shp - Nau del jugador 2 (triangle amb circulito central)
|
# ship2.shp - Nau del jugador 2
|
||||||
# © 1999 Visente i Sergi (versió Pascal)
|
# Triangle amb cercle central (distintiu visual)
|
||||||
# © 2025 Port a C++20 amb SDL3
|
|
||||||
|
|
||||||
name: ship2
|
name: ship2
|
||||||
scale: 1.0
|
scale: 1.0
|
||||||
center: 0, 0
|
center: 0, 0
|
||||||
|
|
||||||
# Triangle amb base còncava tipus "punta de fletxa"
|
|
||||||
# (Mateix que ship.shp)
|
|
||||||
# Punts originals (polar):
|
|
||||||
# p1: r=12, angle=270° (3π/2) → punta amunt
|
|
||||||
# p2: r=12, angle=45° (π/4) → base dreta-darrere
|
|
||||||
# p3: r=12, angle=135° (3π/4) → base esquerra-darrere
|
|
||||||
#
|
|
||||||
# MODIFICACIÓ: afegit p4 al mig de la base, desplaçat cap al centre
|
|
||||||
# p4: (0, 4) → punt central de la base, cap endins
|
|
||||||
#
|
|
||||||
# Conversió polar→cartesià (angle-90° perquè origen visual és amunt):
|
|
||||||
# p1: (0, -12) → punta
|
|
||||||
# p2: (8.49, 8.49) → base dreta
|
|
||||||
# p4: (0, 4) → base centre (cap endins)
|
|
||||||
# p3: (-8.49, 8.49) → base esquerra
|
|
||||||
|
|
||||||
#polyline: 0,-12 8.49,8.49 0,4 -8.49,8.49 0,-12
|
|
||||||
polyline: 0,-12 8.49,8.49 -8.49,8.49 0,-12
|
polyline: 0,-12 8.49,8.49 -8.49,8.49 0,-12
|
||||||
|
|
||||||
# Circulito central (octàgon r=2.5)
|
# Octàgon central (radi=2.5)
|
||||||
# Distintiu visual del jugador 2
|
|
||||||
polyline: 0,-2.5 1.77,-1.77 2.5,0 1.77,1.77 0,2.5 -1.77,1.77 -2.5,0 -1.77,-1.77 0,-2.5
|
polyline: 0,-2.5 1.77,-1.77 2.5,0 1.77,1.77 0,2.5 -1.77,1.77 -2.5,0 -1.77,-1.77 0,-2.5
|
||||||
|
|||||||
+10
-10
@@ -16,7 +16,7 @@ stages:
|
|||||||
spawn_interval: 3.0
|
spawn_interval: 3.0
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 100
|
pentagon: 100
|
||||||
quadrat: 0
|
cuadrado: 0
|
||||||
molinillo: 0
|
molinillo: 0
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 0.7
|
speed_multiplier: 0.7
|
||||||
@@ -32,7 +32,7 @@ stages:
|
|||||||
spawn_interval: 2.5
|
spawn_interval: 2.5
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 70
|
pentagon: 70
|
||||||
quadrat: 30
|
cuadrado: 30
|
||||||
molinillo: 0
|
molinillo: 0
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 0.85
|
speed_multiplier: 0.85
|
||||||
@@ -48,7 +48,7 @@ stages:
|
|||||||
spawn_interval: 2.0
|
spawn_interval: 2.0
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 50
|
pentagon: 50
|
||||||
quadrat: 30
|
cuadrado: 30
|
||||||
molinillo: 20
|
molinillo: 20
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 1.0
|
speed_multiplier: 1.0
|
||||||
@@ -64,7 +64,7 @@ stages:
|
|||||||
spawn_interval: 1.8
|
spawn_interval: 1.8
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 40
|
pentagon: 40
|
||||||
quadrat: 35
|
cuadrado: 35
|
||||||
molinillo: 25
|
molinillo: 25
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 1.1
|
speed_multiplier: 1.1
|
||||||
@@ -80,7 +80,7 @@ stages:
|
|||||||
spawn_interval: 1.5
|
spawn_interval: 1.5
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 35
|
pentagon: 35
|
||||||
quadrat: 35
|
cuadrado: 35
|
||||||
molinillo: 30
|
molinillo: 30
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 1.2
|
speed_multiplier: 1.2
|
||||||
@@ -96,7 +96,7 @@ stages:
|
|||||||
spawn_interval: 1.3
|
spawn_interval: 1.3
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 30
|
pentagon: 30
|
||||||
quadrat: 30
|
cuadrado: 30
|
||||||
molinillo: 40
|
molinillo: 40
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 1.3
|
speed_multiplier: 1.3
|
||||||
@@ -112,7 +112,7 @@ stages:
|
|||||||
spawn_interval: 1.0
|
spawn_interval: 1.0
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 25
|
pentagon: 25
|
||||||
quadrat: 30
|
cuadrado: 30
|
||||||
molinillo: 45
|
molinillo: 45
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 1.4
|
speed_multiplier: 1.4
|
||||||
@@ -128,7 +128,7 @@ stages:
|
|||||||
spawn_interval: 0.8
|
spawn_interval: 0.8
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 20
|
pentagon: 20
|
||||||
quadrat: 30
|
cuadrado: 30
|
||||||
molinillo: 50
|
molinillo: 50
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 1.5
|
speed_multiplier: 1.5
|
||||||
@@ -144,7 +144,7 @@ stages:
|
|||||||
spawn_interval: 0.6
|
spawn_interval: 0.6
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 15
|
pentagon: 15
|
||||||
quadrat: 25
|
cuadrado: 25
|
||||||
molinillo: 60
|
molinillo: 60
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 1.6
|
speed_multiplier: 1.6
|
||||||
@@ -160,7 +160,7 @@ stages:
|
|||||||
spawn_interval: 0.5
|
spawn_interval: 0.5
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 10
|
pentagon: 10
|
||||||
quadrat: 20
|
cuadrado: 20
|
||||||
molinillo: 70
|
molinillo: 70
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 1.8
|
speed_multiplier: 1.8
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Fragment shader para líneas vectoriales.
|
||||||
|
// Pinta el color interpolado per-vertex que viene del vertex shader.
|
||||||
|
// (Postprocesado / bloom / glow son responsabilidad de la Fase 8.)
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 frag_color;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_color = frag_color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vertex shader para líneas vectoriales.
|
||||||
|
// Las líneas se proveen ya extrudidas en CPU como quads (2 triángulos por línea)
|
||||||
|
// con grosor configurable. El vertex shader solo:
|
||||||
|
// 1. Transforma de píxeles lógicos (0..viewport_size) a clip-space (-1..+1).
|
||||||
|
// 2. Pasa el color RGBA al fragment shader.
|
||||||
|
//
|
||||||
|
// Slot de uniform buffer 0 (vertex): viewport size para la transformación.
|
||||||
|
// Convención SDL_gpu: SDL_PushGPUVertexUniformData(cmd, 0, &ubo, sizeof(ubo)).
|
||||||
|
|
||||||
|
layout(set = 1, binding = 0) uniform UBO {
|
||||||
|
vec2 viewport_size; // ancho y alto en píxeles lógicos (ej. 1280, 720)
|
||||||
|
vec2 _padding; // alineamiento a 16 bytes
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 in_position; // píxeles lógicos
|
||||||
|
layout(location = 1) in vec4 in_color; // RGBA 0..1
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Píxeles lógicos -> NDC (-1..+1)
|
||||||
|
vec2 ndc = (in_position / ubo.viewport_size) * 2.0 - 1.0;
|
||||||
|
// Y flip: SDL screen-Y va hacia abajo, clip-Y hacia arriba.
|
||||||
|
ndc.y = -ndc.y;
|
||||||
|
gl_Position = vec4(ndc, 0.0, 1.0);
|
||||||
|
frag_color = in_color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Fragment shader del pase de postprocesado.
|
||||||
|
// Lee la textura offscreen (escena vectorial sobre fondo negro) y produce
|
||||||
|
// el fragmento final aplicando:
|
||||||
|
// 1. Bloom kernel 5×5 con high-pass (solo los brillos por encima de
|
||||||
|
// threshold contribuyen).
|
||||||
|
// 2. Flicker: multiplicador global de brillo modulado por tiempo
|
||||||
|
// (sustituye al oscilador CPU del legacy).
|
||||||
|
// 3. Background pulse: color de fondo que oscila entre min y max y se
|
||||||
|
// suma a la imagen (las líneas brillan por encima).
|
||||||
|
//
|
||||||
|
// Resource sets (SDL_gpu):
|
||||||
|
// set=2, binding=0 → sampler2D (escena offscreen)
|
||||||
|
// set=3, binding=0 → uniform buffer (parámetros del postpro)
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform 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;
|
||||||
|
|
||||||
|
vec4 background_min; // RGB en [0..1], A=1
|
||||||
|
vec4 background_max; // RGB en [0..1], A=1
|
||||||
|
|
||||||
|
vec2 texel_size; // 1.0 / texture_size
|
||||||
|
vec2 _pad_b;
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 frag;
|
||||||
|
|
||||||
|
const float TAU = 6.28318530718;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// === BLOOM ===
|
||||||
|
// Kernel 5×5 con muestreo radial y high-pass por luminancia (max RGB).
|
||||||
|
// Pesos gaussianos: w = exp(-(dx²+dy²) / 4).
|
||||||
|
vec3 src = texture(scene, v_uv).rgb;
|
||||||
|
vec3 bloom = vec3(0.0);
|
||||||
|
float total_weight = 0.0;
|
||||||
|
for (int dy = -2; dy <= 2; ++dy) {
|
||||||
|
for (int dx = -2; dx <= 2; ++dx) {
|
||||||
|
vec2 offset = vec2(float(dx), float(dy)) * ubo.texel_size * ubo.bloom_radius_px;
|
||||||
|
vec3 c = texture(scene, v_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 ===
|
||||||
|
// Multiplicador global de brillo. Oscila entre (1.0 - amplitude) y 1.0.
|
||||||
|
// amplitude=0 → sin flicker; amplitude=1 → pulsa entre apagado y máximo.
|
||||||
|
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 ===
|
||||||
|
// Suma de color de fondo oscilante. min..max se interpolan con sin(t).
|
||||||
|
float bg_pulse = (sin(ubo.time * ubo.background_pulse_freq_hz * TAU) * 0.5) + 0.5;
|
||||||
|
vec3 background = mix(ubo.background_min.rgb, ubo.background_max.rgb, bg_pulse);
|
||||||
|
|
||||||
|
// === COMPOSICIÓN ===
|
||||||
|
// El offscreen viene con clear=black, por lo que solo las líneas y el
|
||||||
|
// bloom aportan luz. Sumamos el fondo y luego multiplicamos por flicker
|
||||||
|
// para que el pulso afecte a todo (líneas + bloom + bg).
|
||||||
|
vec3 lines_and_glow = (src + bloom) * flicker;
|
||||||
|
frag = vec4(background + lines_and_glow, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vertex shader del pase de postprocesado.
|
||||||
|
// Emite un solo triángulo que cubre toda la pantalla (técnica del "fullscreen
|
||||||
|
// triangle"): tres vértices en (-1,-1), (3,-1), (-1,3) → la región visible
|
||||||
|
// [-1..1]² queda completamente cubierta y el clip recorta el resto. No hace
|
||||||
|
// falta vertex buffer; el draw es DrawPrimitives(vertex_count=3).
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 v_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 positions[3] = vec2[3](
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2( 3.0, -1.0),
|
||||||
|
vec2(-1.0, 3.0)
|
||||||
|
);
|
||||||
|
// UV.y invertida para compensar la diferencia entre la convención de
|
||||||
|
// clip-space del line shader (ndc.y flipeado, GL-style) y la convención
|
||||||
|
// de muestreo de SDL_gpu/Vulkan (origen de textura en top-left). Sin esta
|
||||||
|
// inversión, el offscreen se ve cabeza-abajo en el composite.
|
||||||
|
vec2 uvs[3] = vec2[3](
|
||||||
|
vec2(0.0, 1.0),
|
||||||
|
vec2(2.0, 1.0),
|
||||||
|
vec2(0.0, -1.0)
|
||||||
|
);
|
||||||
|
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||||
|
v_uv = uvs[gl_VertexIndex];
|
||||||
|
}
|
||||||
+218
-110
@@ -1,183 +1,291 @@
|
|||||||
#include "audio.hpp"
|
#include "core/audio/audio.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_G...
|
#include <SDL3/SDL.h> // Para SDL_GetError, SDL_Init
|
||||||
|
|
||||||
#include <algorithm> // Para clamp
|
#include <algorithm> // Para clamp
|
||||||
#include <iostream> // Para std::cout
|
#include <cstdio> // Para std::fprintf
|
||||||
|
|
||||||
// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp)
|
#include "core/audio/audio_adapter.hpp" // Para AudioResource::getMusic/getSound
|
||||||
// clang-format off
|
#include "core/audio/jail_audio.hpp" // Para Ja::* (motor jailgames)
|
||||||
#undef STB_VORBIS_HEADER_ONLY
|
#include "core/audio/sound_effects_config.hpp" // Para SoundEffectsConfig
|
||||||
#include "external/stb_vorbis.h"
|
#include "core/defaults.hpp" // Para Defaults::Audio::FREQUENCY
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
#include "core/audio/audio_cache.hpp" // Para AudioCache
|
// Invariant compile-time: tots los valors d'Audio::Group han de cabre als slots
|
||||||
#include "core/audio/jail_audio.hpp" // Para JA_FadeOutMusic, JA_Init, JA_PauseM...
|
// de volum per grup que manté l'engine. Si s'afegeix una nueva entrada a Group
|
||||||
#include "game/options.hpp" // Para AudioOptions, audio, MusicOptions
|
// y no s'incrementa Ja::MAX_GROUPS, este assert falla antes de compilar.
|
||||||
|
static_assert(static_cast<int>(Audio::Group::INTERFACE) < Ja::MAX_GROUPS,
|
||||||
|
"Audio::Group té més entrades que slots té Ja::MAX_GROUPS");
|
||||||
|
|
||||||
// Singleton
|
// Singleton
|
||||||
Audio* Audio::instance = nullptr;
|
std::unique_ptr<Audio> Audio::instance;
|
||||||
|
|
||||||
// Inicializa la instancia única del singleton
|
// Inicialitza la instància única del singleton con la configuración rebuda
|
||||||
void Audio::init() { Audio::instance = new Audio(); }
|
void Audio::init(const Config& config) { Audio::instance = std::unique_ptr<Audio>(new Audio(config)); }
|
||||||
|
|
||||||
// Libera la instancia
|
// Allibera la instància
|
||||||
void Audio::destroy() { delete Audio::instance; }
|
void Audio::destroy() { Audio::instance.reset(); }
|
||||||
|
|
||||||
// Obtiene la instancia
|
// Obté la instància
|
||||||
auto Audio::get() -> Audio* { return Audio::instance; }
|
auto Audio::get() -> Audio* { return Audio::instance.get(); }
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Audio::Audio() { initSDLAudio(); }
|
Audio::Audio(const Config& config)
|
||||||
|
: config_(config) { initSDLAudio(); }
|
||||||
|
|
||||||
// Destructor
|
// Destructor: engine_ es std::unique_ptr, el seu dtor tanca el device SDL i
|
||||||
Audio::~Audio() {
|
// desregistra Ja::Engine::active_. Cap crida explícita necessària.
|
||||||
JA_Quit();
|
Audio::~Audio() = default;
|
||||||
}
|
|
||||||
|
|
||||||
// Método principal
|
// Método principal: l'estat de la música el manté el motor (única font de
|
||||||
|
// veritat), per tant no cal sin sincronització aquí.
|
||||||
void Audio::update() {
|
void Audio::update() {
|
||||||
JA_Update();
|
if (instance && instance->engine_) { instance->engine_->update(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reproduce la música
|
// Reprodueix la música per nom (amb crossfade opcional)
|
||||||
void Audio::playMusic(const std::string& name, const int loop) {
|
void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) {
|
||||||
bool new_loop = (loop != 0);
|
const bool NEW_LOOP = (loop != 0);
|
||||||
|
|
||||||
// Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada
|
// Si ya sona exactament la misma pista i mismo mode loop, no fem res
|
||||||
if (music_.state == MusicState::PLAYING && music_.name == name && music_.loop == new_loop) {
|
if (getMusicState() == MusicState::PLAYING && music_.name == name && music_.loop == NEW_LOOP) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intentar obtener recurso; si falla, no tocar estado
|
if (!music_enabled_) { return; }
|
||||||
auto* resource = AudioCache::getMusic(name);
|
|
||||||
if (resource == nullptr) {
|
|
||||||
// manejo de error opcional
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si hay algo reproduciéndose, detenerlo primero (si el backend lo requiere)
|
auto* resource = AudioResource::getMusic(name);
|
||||||
if (music_.state == MusicState::PLAYING) {
|
if (resource == nullptr) { return; }
|
||||||
JA_StopMusic(); // sustituir por la función de stop real del API si tiene otro nombre
|
|
||||||
}
|
|
||||||
|
|
||||||
// Llamada al motor para reproducir la nueva pista
|
playMusicInternal(resource, loop, crossfade_ms);
|
||||||
JA_PlayMusic(resource, loop);
|
|
||||||
|
|
||||||
// Actualizar estado y metadatos después de iniciar con éxito
|
|
||||||
music_.name = name;
|
music_.name = name;
|
||||||
music_.loop = new_loop;
|
|
||||||
music_.state = MusicState::PLAYING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pausa la música
|
// Reprodueix la música per punter (amb crossfade opcional)
|
||||||
|
void Audio::playMusic(Ja::Music* music, const int loop, const int crossfade_ms) {
|
||||||
|
if (!music_enabled_ || music == nullptr) { return; }
|
||||||
|
|
||||||
|
playMusicInternal(music, loop, crossfade_ms);
|
||||||
|
// Si el Ja::Music es va crear con filename (loadMusic con 3 arguments), el
|
||||||
|
// recuperem porque getCurrentMusicName() no menteixi. Si no, music_.name
|
||||||
|
// queda buit — el contracte d'este overload no garanteix el nom.
|
||||||
|
music_.name = music->filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camí comú dels dos overloads: fa el dispatch crossfade vs stop+play i
|
||||||
|
// actualitza el loop cachejat. Els callers s'encarreguen del gating
|
||||||
|
// (music_enabled_, nullptr, same-track early return) y del nom. L'estat el
|
||||||
|
// manté Ja (Ja::playMusic posa PLAYING al Ja::Music* corresponent).
|
||||||
|
void Audio::playMusicInternal(Ja::Music* music, const int loop, const int crossfade_ms) {
|
||||||
|
const bool CURRENTLY_PLAYING = (getMusicState() == MusicState::PLAYING);
|
||||||
|
if (crossfade_ms > 0 && CURRENTLY_PLAYING) {
|
||||||
|
engine_->crossfadeMusic(music, crossfade_ms, loop);
|
||||||
|
} else {
|
||||||
|
if (CURRENTLY_PLAYING) {
|
||||||
|
engine_->stopMusic();
|
||||||
|
}
|
||||||
|
engine_->playMusic(music, loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
music_.loop = (loop != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pausa la música (l'estat el transiciona Engine::pauseMusic)
|
||||||
void Audio::pauseMusic() {
|
void Audio::pauseMusic() {
|
||||||
if (music_enabled_ && music_.state == MusicState::PLAYING) {
|
if (music_enabled_ && getMusicState() == MusicState::PLAYING) {
|
||||||
JA_PauseMusic();
|
engine_->pauseMusic();
|
||||||
music_.state = MusicState::PAUSED;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continua la música pausada
|
// Continua la música pausada (l'estat el transiciona Engine::resumeMusic)
|
||||||
void Audio::resumeMusic() {
|
void Audio::resumeMusic() {
|
||||||
if (music_enabled_ && music_.state == MusicState::PAUSED) {
|
if (music_enabled_ && getMusicState() == MusicState::PAUSED) {
|
||||||
JA_ResumeMusic();
|
engine_->resumeMusic();
|
||||||
music_.state = MusicState::PLAYING;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detiene la música
|
// Atura la música (l'estat el transiciona Engine::stopMusic)
|
||||||
void Audio::stopMusic() {
|
void Audio::stopMusic() {
|
||||||
if (music_enabled_) {
|
if (music_enabled_) {
|
||||||
JA_StopMusic();
|
engine_->stopMusic();
|
||||||
music_.state = MusicState::STOPPED;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reproduce un sonido por nombre
|
void Audio::setMusicSpeed(float ratio) {
|
||||||
void Audio::playSound(const std::string& name, Group group) const {
|
if (music_enabled_) {
|
||||||
|
engine_->setMusicSpeed(ratio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reprodueix un so per nom
|
||||||
|
void Audio::playSound(const std::string& name, Group group) {
|
||||||
if (sound_enabled_) {
|
if (sound_enabled_) {
|
||||||
JA_PlaySound(AudioCache::getSound(name), 0, static_cast<int>(group));
|
engine_->playSound(AudioResource::getSound(name), 0, static_cast<int>(group));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reproduce un sonido por puntero directo
|
// Reprodueix un so per punter directe
|
||||||
void Audio::playSound(JA_Sound_t* sound, Group group) const {
|
void Audio::playSound(Ja::Sound* sound, Group group) {
|
||||||
|
if (sound_enabled_ && sound != nullptr) {
|
||||||
|
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant con velocitat (i to) escalats. Apliquem el ratio al canal
|
||||||
|
// just retornat per `playSound`: así el `SDL_AudioStream` recent creat
|
||||||
|
// processa tot el sample con el ratio des del primer pull del callback.
|
||||||
|
// Si l'engine torna -1 (sense canal lliure) o el so no existeix, no fem
|
||||||
|
// la crida al ratio — sin efectes col·laterals.
|
||||||
|
void Audio::playSound(const std::string& name, Group group, float speed) {
|
||||||
|
if (!sound_enabled_) { return; }
|
||||||
|
auto* sound = AudioResource::getSound(name);
|
||||||
|
if (sound == nullptr) { return; }
|
||||||
|
const int CH = engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
if (CH >= 0 && speed != 1.0F) {
|
||||||
|
engine_->setChannelSpeed(CH, speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reprodueix un so processat per un eco definit a sounds.yaml. Si el preset no
|
||||||
|
// existeix o l'engine retorna -1 (sin de canals d'efecte plé), cau a playSound
|
||||||
|
// sec — l'usuari sent el so aún que la cua no s'apliqui.
|
||||||
|
void Audio::playSoundWithEcho(const std::string& name, const std::string& preset_name, Group group) {
|
||||||
|
if (!sound_enabled_) { return; }
|
||||||
|
auto* sound = AudioResource::getSound(name);
|
||||||
|
if (sound == nullptr) { return; }
|
||||||
|
|
||||||
|
const auto* params = SoundEffectsConfig::get().findEcho(preset_name);
|
||||||
|
if (params == nullptr) {
|
||||||
|
std::fprintf(stderr, "Audio: preset d'eco '%s' desconegut — so '%s' es reprodueix sec\n", preset_name.c_str(), name.c_str());
|
||||||
|
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engine_->playSoundWithEcho(sound, *params, static_cast<int>(group)) < 0) {
|
||||||
|
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reprodueix un so processat per un reverb definit a sounds.yaml. Mateix
|
||||||
|
// fallback que playSoundWithEcho.
|
||||||
|
void Audio::playSoundWithReverb(const std::string& name, const std::string& preset_name, Group group) {
|
||||||
|
if (!sound_enabled_) { return; }
|
||||||
|
auto* sound = AudioResource::getSound(name);
|
||||||
|
if (sound == nullptr) { return; }
|
||||||
|
|
||||||
|
const auto* params = SoundEffectsConfig::get().findReverb(preset_name);
|
||||||
|
if (params == nullptr) {
|
||||||
|
std::fprintf(stderr, "Audio: preset de reverb '%s' desconegut — so '%s' es reprodueix sec\n", preset_name.c_str(), name.c_str());
|
||||||
|
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (engine_->playSoundWithReverb(sound, *params, static_cast<int>(group)) < 0) {
|
||||||
|
engine_->playSound(sound, 0, static_cast<int>(group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atura tots los sons
|
||||||
|
void Audio::stopAllSounds() {
|
||||||
if (sound_enabled_) {
|
if (sound_enabled_) {
|
||||||
JA_PlaySound(sound, 0, static_cast<int>(group));
|
engine_->stopChannel(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detiene todos los sonidos
|
// Fa una fosa de sortida de la música
|
||||||
void Audio::stopAllSounds() const {
|
void Audio::fadeOutMusic(int milliseconds) {
|
||||||
if (sound_enabled_) {
|
if (music_enabled_ && getMusicState() == MusicState::PLAYING) {
|
||||||
JA_StopChannel(-1);
|
engine_->fadeOutMusic(milliseconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Realiza un fundido de salida de la música
|
// Registra un callback que el motor dispararà cuando la pista actual acabi de
|
||||||
void Audio::fadeOutMusic(int milliseconds) const {
|
// drenar (times == 0 + stream buit). S'executa al mismo thread que
|
||||||
if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) {
|
// Audio::update (render loop); los consumidors no poden fer I/O blocant.
|
||||||
JA_FadeOutMusic(milliseconds);
|
void Audio::setOnMusicEnded(std::function<void()> callback) {
|
||||||
}
|
if (engine_) { engine_->setOnMusicEnded(std::move(callback)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consulta directamente el estado real de la música en jailaudio
|
// Resol el nom contra el cache de recursos i retorna la duración pre-calculada
|
||||||
auto Audio::getRealMusicState() -> MusicState {
|
// al `loadMusic`. 0 si la pista no existeix — así el caller pot decidir
|
||||||
JA_Music_state ja_state = JA_GetMusicState();
|
// fallback (p. ex. usar un timeout fix) sin haver de propagar errors.
|
||||||
switch (ja_state) {
|
auto Audio::getMusicDurationMs(const std::string& name) -> int {
|
||||||
case JA_MUSIC_PLAYING:
|
auto* music = AudioResource::getMusic(name);
|
||||||
|
return (music != nullptr) ? music->duration_ms : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consulta directament l'estat a Ja y el projecta al subconjunt d'estats que
|
||||||
|
// exposa Audio (INVALID/DISABLED de Ja col·lapsen a STOPPED — la capa d'usuari
|
||||||
|
// solo vol saber si está sonant, pausat o parat).
|
||||||
|
auto Audio::getMusicState() -> MusicState {
|
||||||
|
if (!instance || !instance->engine_) { return MusicState::STOPPED; }
|
||||||
|
switch (instance->engine_->getMusicState()) {
|
||||||
|
case Ja::MusicState::PLAYING:
|
||||||
return MusicState::PLAYING;
|
return MusicState::PLAYING;
|
||||||
case JA_MUSIC_PAUSED:
|
case Ja::MusicState::PAUSED:
|
||||||
return MusicState::PAUSED;
|
return MusicState::PAUSED;
|
||||||
case JA_MUSIC_STOPPED:
|
case Ja::MusicState::STOPPED:
|
||||||
case JA_MUSIC_INVALID:
|
case Ja::MusicState::INVALID:
|
||||||
case JA_MUSIC_DISABLED:
|
|
||||||
default:
|
default:
|
||||||
return MusicState::STOPPED;
|
return MusicState::STOPPED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el volumen de los sonidos
|
// Aplica el gate master (enabled_) + el gate del canal (sound/music_enabled_)
|
||||||
void Audio::setSoundVolume(float sound_volume, Group group) const {
|
// i retorna el volum escalat pel master config_.volume. 0 si algun gate está
|
||||||
if (sound_enabled_) {
|
// tancat. Así los dos setters comparteixen la misma política.
|
||||||
sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
|
auto Audio::effectiveVolume(float volume, bool channel_enabled) const -> float {
|
||||||
const float CONVERTED_VOLUME = sound_volume * Options::audio.volume;
|
volume = std::clamp(volume, MIN_VOLUME, MAX_VOLUME);
|
||||||
JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
|
return (enabled_ && channel_enabled) ? volume * config_.volume : 0.0F;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el volumen de la música
|
// Estableix el volum dels sons (float 0.0..1.0)
|
||||||
void Audio::setMusicVolume(float music_volume) const {
|
void Audio::setSoundVolume(float sound_volume, Group group) {
|
||||||
if (music_enabled_) {
|
engine_->setSoundVolume(effectiveVolume(sound_volume, sound_enabled_), static_cast<int>(group));
|
||||||
music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
|
|
||||||
const float CONVERTED_VOLUME = music_volume * Options::audio.volume;
|
|
||||||
JA_SetMusicVolume(CONVERTED_VOLUME);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aplica la configuración
|
// Estableix el volum de la música (float 0.0..1.0)
|
||||||
void Audio::applySettings() {
|
void Audio::setMusicVolume(float music_volume) {
|
||||||
enable(Options::audio.enabled);
|
engine_->setMusicVolume(effectiveVolume(music_volume, music_enabled_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establecer estado general
|
// Aplica una nueva configuración (substitueix la config cachejada i reaplica enables/volums)
|
||||||
|
void Audio::applySettings(const Config& config) {
|
||||||
|
config_ = config;
|
||||||
|
sound_enabled_ = config_.sound_enabled;
|
||||||
|
music_enabled_ = config_.music_enabled;
|
||||||
|
enable(config_.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estableix l'estat general
|
||||||
void Audio::enable(bool value) {
|
void Audio::enable(bool value) {
|
||||||
enabled_ = value;
|
enabled_ = value;
|
||||||
|
|
||||||
setSoundVolume(enabled_ ? Options::audio.sound.volume : MIN_VOLUME);
|
setSoundVolume(enabled_ ? config_.sound_volume : MIN_VOLUME);
|
||||||
setMusicVolume(enabled_ ? Options::audio.music.volume : MIN_VOLUME);
|
setMusicVolume(enabled_ ? config_.music_volume : MIN_VOLUME);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicializa SDL Audio
|
// Estableix l'estat dels sons i reaplica el volum porque los canals actius
|
||||||
|
// responguin a l'instant (evita que el toggle solo surti efecte al pròxim
|
||||||
|
// setSoundVolume explícit).
|
||||||
|
void Audio::enableSound(bool value) {
|
||||||
|
sound_enabled_ = value;
|
||||||
|
setSoundVolume(config_.sound_volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estableix l'estat de la música i reaplica el volum per la misma raó.
|
||||||
|
void Audio::enableMusic(bool value) {
|
||||||
|
music_enabled_ = value;
|
||||||
|
setMusicVolume(config_.music_volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicialitza SDL Audio y el motor Ja::Engine owned.
|
||||||
void Audio::initSDLAudio() {
|
void Audio::initSDLAudio() {
|
||||||
if (!SDL_Init(SDL_INIT_AUDIO)) {
|
if (!SDL_Init(SDL_INIT_AUDIO)) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError());
|
std::fprintf(stderr, "Audio: SDL_AUDIO could not initialize! SDL Error: %s\n", SDL_GetError());
|
||||||
} else {
|
return;
|
||||||
JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2);
|
|
||||||
enable(Options::audio.enabled);
|
|
||||||
|
|
||||||
std::cout << "\n** AUDIO SYSTEM **\n";
|
|
||||||
std::cout << "Audio system initialized successfully\n";
|
|
||||||
}
|
}
|
||||||
}
|
engine_ = std::make_unique<Ja::Engine>(Defaults::Audio::FREQUENCY, Defaults::Audio::FORMAT, Defaults::Audio::CHANNELS);
|
||||||
|
sound_enabled_ = config_.sound_enabled;
|
||||||
|
music_enabled_ = config_.music_enabled;
|
||||||
|
enable(config_.enabled);
|
||||||
|
}
|
||||||
|
|||||||
+144
-78
@@ -1,97 +1,163 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string> // Para string
|
#include <cmath> // Para std::lround
|
||||||
#include <utility> // Para move
|
#include <cstdint> // Para int8_t, uint8_t
|
||||||
|
#include <functional> // Para std::function
|
||||||
|
#include <memory> // Para std::unique_ptr
|
||||||
|
#include <string> // Para string
|
||||||
|
#include <utility> // Para move
|
||||||
|
|
||||||
// --- Clase Audio: gestor de audio (singleton) ---
|
// Forward-declares per no incloure core/audio/jail_audio.hpp al header. Els
|
||||||
|
// tres símbols (Music/Sound para el punter que exposa la API i Engine per al
|
||||||
|
// std::unique_ptr<Engine> membre) s'usen solo per punter al header, así que
|
||||||
|
// el forward-decl basta. El ~Audio() en .cpp veu la definició completa i
|
||||||
|
// instancia correctament el dtor de l'unique_ptr.
|
||||||
|
namespace Ja {
|
||||||
|
class Engine;
|
||||||
|
struct Music;
|
||||||
|
struct Sound;
|
||||||
|
} // namespace Ja
|
||||||
|
|
||||||
|
// --- Clase Audio: gestor d'àudio (singleton) ---
|
||||||
|
// Port del subsistema d'àudio del projecte ../aee, desacoblat d'Options:
|
||||||
|
// la configuración entra per la struct Audio::Config a init()/applySettings(),
|
||||||
|
// en lloc de llegir directament Options::audio. Això deixa audio.cpp independent
|
||||||
|
// del layout d'Options i permet substituir la font de configuración.
|
||||||
|
//
|
||||||
|
// Els volums es manegen internament como a float 0.0–1.0; la capa de
|
||||||
|
// presentació (menús, notificacions) usa las helpers toPercent/fromPercent
|
||||||
|
// per mostrar 0–100 a l'usuari.
|
||||||
class Audio {
|
class Audio {
|
||||||
public:
|
public:
|
||||||
// --- Enums ---
|
// --- Configuración injectada (Options la construeix via buildAudioConfig) ---
|
||||||
enum class Group : int {
|
struct Config {
|
||||||
ALL = -1, // Todos los grupos
|
bool enabled{true};
|
||||||
GAME = 0, // Sonidos del juego
|
float volume{1.0F}; // Master 0..1
|
||||||
INTERFACE = 1 // Sonidos de la interfaz
|
bool music_enabled{true};
|
||||||
};
|
float music_volume{0.8F};
|
||||||
|
bool sound_enabled{true};
|
||||||
|
float sound_volume{1.0F};
|
||||||
|
};
|
||||||
|
|
||||||
enum class MusicState {
|
// --- Enums ---
|
||||||
PLAYING, // Reproduciendo música
|
enum class Group : std::int8_t {
|
||||||
PAUSED, // Música pausada
|
ALL = -1, // Tots los grups
|
||||||
STOPPED, // Música detenida
|
GAME = 0, // Sons del joc
|
||||||
};
|
INTERFACE = 1 // Sons de la interfície
|
||||||
|
};
|
||||||
|
|
||||||
// --- Constantes ---
|
enum class MusicState : std::uint8_t {
|
||||||
static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo
|
PLAYING, // Reproduint música
|
||||||
static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo
|
PAUSED, // Música pausada
|
||||||
static constexpr int FREQUENCY = 48000; // Frecuencia de audio
|
STOPPED, // Música aturada
|
||||||
|
};
|
||||||
|
|
||||||
// --- Singleton ---
|
// --- Constants ---
|
||||||
static void init(); // Inicializa el objeto Audio
|
static constexpr float MAX_VOLUME = 1.0F; // Volum màxim (float 0..1)
|
||||||
static void destroy(); // Libera el objeto Audio
|
static constexpr float MIN_VOLUME = 0.0F; // Volum mínim (float 0..1)
|
||||||
static auto get() -> Audio*; // Obtiene el puntero al objeto Audio
|
|
||||||
Audio(const Audio&) = delete; // Evitar copia
|
|
||||||
auto operator=(const Audio&) -> Audio& = delete; // Evitar asignación
|
|
||||||
|
|
||||||
static void update(); // Actualización del sistema de audio
|
// --- Singleton ---
|
||||||
|
static void init(const Config& config); // Inicialitza con la configuración rebuda
|
||||||
|
static void destroy(); // Allibera l'objecte Audio
|
||||||
|
static auto get() -> Audio*; // Obté el punter a l'objecte Audio
|
||||||
|
~Audio(); // Destructor (públic para std::unique_ptr)
|
||||||
|
Audio(const Audio&) = delete; // Evitar còpia
|
||||||
|
Audio(Audio&&) = delete;
|
||||||
|
auto operator=(const Audio&) -> Audio& = delete; // Evitar assignació
|
||||||
|
auto operator=(Audio&&) -> Audio& = delete;
|
||||||
|
|
||||||
// --- Control de música ---
|
static void update(); // Actualització del sistema d'àudio
|
||||||
void playMusic(const std::string& name, int loop = -1); // Reproducir música en bucle
|
|
||||||
void pauseMusic(); // Pausar reproducción de música
|
|
||||||
void resumeMusic(); // Continua la música pausada
|
|
||||||
void stopMusic(); // Detener completamente la música
|
|
||||||
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
|
|
||||||
|
|
||||||
// --- Control de sonidos ---
|
// --- Control de música ---
|
||||||
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre
|
void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproduir música per nom (amb crossfade opcional)
|
||||||
void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
|
void playMusic(Ja::Music* music, int loop = -1, int crossfade_ms = 0); // Reproduir música per punter (amb crossfade opcional)
|
||||||
void stopAllSounds() const; // Detener todos los sonidos
|
void pauseMusic(); // Pausar la reproducció de música
|
||||||
|
void resumeMusic(); // Continua la música pausada
|
||||||
|
void stopMusic(); // Aturar completament la música
|
||||||
|
void fadeOutMusic(int milliseconds); // Fosa de sortida de la música (muta globals de Ja)
|
||||||
|
void setOnMusicEnded(std::function<void()> callback); // Callback disparat cuando la pista actual acaba de drenar (CONV-03)
|
||||||
|
// Multiplicador de velocitat de la música actual. 1.0 = normal,
|
||||||
|
// 1.5 = un 50% més ràpid (efecte "chipmunk" — también puja el to).
|
||||||
|
// Es reseteja a 1.0 implícitament a cada `playMusic`. No-op si no
|
||||||
|
// hay música activa.
|
||||||
|
void setMusicSpeed(float ratio);
|
||||||
|
|
||||||
// --- Control de volumen ---
|
// --- Control de sons ---
|
||||||
void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
|
void playSound(const std::string& name, Group group = Group::GAME); // Reproduir so puntual per nom (muta globals de Ja)
|
||||||
void setMusicVolume(float volume) const; // Ajustar volumen de música
|
void playSound(Ja::Sound* sound, Group group = Group::GAME); // Reproduir so puntual per punter (muta globals de Ja)
|
||||||
|
// Reprodueix un so con la velocitat (i to) escalats per `speed`:
|
||||||
|
// 1.0 = normal, 0.95 ≈ -5% (més greu i lent), 1.05 ≈ +5% (més
|
||||||
|
// agut i ràpid). Mateixa semàntica que `setMusicSpeed`. Útil per a
|
||||||
|
// variacions subtils que eviten la fatiga d'escoltar el mismo
|
||||||
|
// sample idèntic (p.ex. obertures de sarcòfag, picks d'ítems).
|
||||||
|
void playSound(const std::string& name, Group group, float speed);
|
||||||
|
// Reprodueix un so processat per un efecte definit a data/config/sounds.yaml
|
||||||
|
// (preset_name busca a SoundEffectsConfig). Si el preset no existeix
|
||||||
|
// o el motor está al sin de canals con efecte, fa fallback a playSound
|
||||||
|
// sec — l'usuari sent el so igualment, sin la cua.
|
||||||
|
void playSoundWithEcho(const std::string& name, const std::string& preset_name, Group group = Group::GAME);
|
||||||
|
void playSoundWithReverb(const std::string& name, const std::string& preset_name, Group group = Group::GAME);
|
||||||
|
void stopAllSounds(); // Aturar tots los sons (muta globals de Ja)
|
||||||
|
|
||||||
// --- Configuración general ---
|
// --- Control de volum (API interna: float 0.0..1.0) ---
|
||||||
void enable(bool value); // Establecer estado general
|
void setSoundVolume(float volume, Group group = Group::ALL); // Ajusta el volum dels efectes
|
||||||
void toggleEnabled() { enabled_ = !enabled_; } // Alternar estado general
|
void setMusicVolume(float volume); // Ajusta el volum de la música
|
||||||
void applySettings(); // Aplica la configuración
|
|
||||||
|
|
||||||
// --- Configuración de sonidos ---
|
// --- Helpers de conversió para la capa de presentació ---
|
||||||
void enableSound() { sound_enabled_ = true; } // Habilitar sonidos
|
// UI (menús, notificacions) manega enters 0..100; internament viu float 0..1.
|
||||||
void disableSound() { sound_enabled_ = false; } // Deshabilitar sonidos
|
// No són constexpr porque std::lround no ho es en C++20; s'usen en runtime.
|
||||||
void enableSound(bool value) { sound_enabled_ = value; } // Establecer estado de sonidos
|
static auto toPercent(float volume) -> int {
|
||||||
void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alternar estado de sonidos
|
return static_cast<int>(std::lround(volume * 100.0F));
|
||||||
|
}
|
||||||
|
static auto fromPercent(int percent) -> float {
|
||||||
|
return static_cast<float>(percent) / 100.0F;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Configuración de música ---
|
// --- Configuración general ---
|
||||||
void enableMusic() { music_enabled_ = true; } // Habilitar música
|
void enable(bool value); // Estableix l'estat general (reaplica volums)
|
||||||
void disableMusic() { music_enabled_ = false; } // Deshabilitar música
|
void toggleEnabled() { enable(!enabled_); } // Alterna l'estat general (reaplica volums)
|
||||||
void enableMusic(bool value) { music_enabled_ = value; } // Establecer estado de música
|
void applySettings(const Config& config); // Aplica una nueva configuración
|
||||||
void toggleMusic() { music_enabled_ = !music_enabled_; } // Alternar estado de música
|
|
||||||
|
|
||||||
// --- Consultas de estado ---
|
// --- Configuración de sons ---
|
||||||
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
|
void enableSound(bool value); // Estableix l'estat dels sons (reaplica volum)
|
||||||
[[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
|
void toggleSound() { enableSound(!sound_enabled_); } // Alterna l'estat dels sons (reaplica volum)
|
||||||
[[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
|
|
||||||
[[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; }
|
|
||||||
[[nodiscard]] static auto getRealMusicState() -> MusicState;
|
|
||||||
[[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
|
|
||||||
|
|
||||||
private:
|
// --- Configuración de música ---
|
||||||
// --- Tipos anidados ---
|
void enableMusic(bool value); // Estableix l'estat de la música (reaplica volum)
|
||||||
struct Music {
|
void toggleMusic() { enableMusic(!music_enabled_); } // Alterna l'estat de la música (reaplica volum)
|
||||||
MusicState state{MusicState::STOPPED}; // Estado actual de la música
|
|
||||||
std::string name; // Última pista de música reproducida
|
|
||||||
bool loop{false}; // Indica si se reproduce en bucle
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Métodos ---
|
// --- Consultes d'estat ---
|
||||||
Audio(); // Constructor privado
|
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
|
||||||
~Audio(); // Destructor privado
|
[[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
|
||||||
void initSDLAudio(); // Inicializa SDL Audio
|
[[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
|
||||||
|
[[nodiscard]] static auto getMusicState() -> MusicState; // Estat real consultat a Ja::
|
||||||
|
[[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
|
||||||
|
// Duración de la pista resolta per nom (mil·lisegons). 0 si la pista no
|
||||||
|
// existeix al cache de recursos o si el seu header OGG no permet
|
||||||
|
// calcular-la. Pensat para clients que necessiten un timeline
|
||||||
|
// determinista (p. ex. RoomFsm) sin dependre de callbacks de fi.
|
||||||
|
[[nodiscard]] static auto getMusicDurationMs(const std::string& name) -> int;
|
||||||
|
|
||||||
// --- Variables miembro ---
|
private:
|
||||||
static Audio* instance; // Instancia única de Audio
|
// --- Tipus anidats ---
|
||||||
|
struct Music {
|
||||||
|
std::string name; // Última pista de música reproduïda (buida si es va passar per punter sin filename)
|
||||||
|
bool loop{false}; // Si el play actual es en bucle
|
||||||
|
};
|
||||||
|
|
||||||
Music music_; // Estado de la música
|
// --- Mètodes ---
|
||||||
bool enabled_{true}; // Estado general del audio
|
explicit Audio(const Config& config); // Constructor privat: rep la config
|
||||||
bool sound_enabled_{true}; // Estado de los efectos de sonido
|
void initSDLAudio(); // Inicialitza SDL Audio
|
||||||
bool music_enabled_{true}; // Estado de la música
|
void playMusicInternal(Ja::Music* music, int loop, int crossfade_ms); // Camí comú dels dos overloads de playMusic
|
||||||
};
|
[[nodiscard]] auto effectiveVolume(float volume, bool channel_enabled) const -> float; // Gate master+channel: 0 si algun está off, clamp 0..1 altrament
|
||||||
|
|
||||||
|
// --- Variables membre ---
|
||||||
|
static std::unique_ptr<Audio> instance; // Instància única d'Audio
|
||||||
|
|
||||||
|
std::unique_ptr<Ja::Engine> engine_; // Motor de baix nivell (owned); viu mentre Audio viu.
|
||||||
|
Config config_{}; // Configuración injectada (volums, enables)
|
||||||
|
Music music_; // Estat de la música (nom + loop cachejats)
|
||||||
|
bool enabled_{true}; // Estat general de l'àudio
|
||||||
|
bool sound_enabled_{true}; // Estat dels efectes de so
|
||||||
|
bool music_enabled_{true}; // Estat de la música
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// audio_adapter.cpp - Implementación de AudioResource para orni_attack
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Implementa AudioResource::getMusic / getSound delegando a
|
||||||
|
// Resource::Helper::loadFile (que abstrae el resources.pack y el fallback
|
||||||
|
// a filesystem). Cache local de Ja::Music* / Ja::Sound* con lazy load:
|
||||||
|
// cada recurso se carga la primera vez que se pide y se mantiene vivo
|
||||||
|
// hasta el shutdown.
|
||||||
|
|
||||||
|
#include "core/audio/audio_adapter.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "core/audio/jail_audio.hpp"
|
||||||
|
#include "core/resources/resource_helper.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Cachés locales: indexados por nombre lógico ("title.ogg", "effects/laser_shoot.wav", etc.)
|
||||||
|
// Mantienen ownership con unique_ptr; se liberan al salir del programa.
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<Ja::Music>>& musicCache() {
|
||||||
|
static std::unordered_map<std::string, std::unique_ptr<Ja::Music>> cache;
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<Ja::Sound>>& soundCache() {
|
||||||
|
static std::unordered_map<std::string, std::unique_ptr<Ja::Sound>> cache;
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normaliza el nombre añadiendo la subcarpeta correspondiente si no la trae:
|
||||||
|
// "title.ogg" -> "music/title.ogg"
|
||||||
|
// "music/title.ogg" -> "music/title.ogg"
|
||||||
|
// "effects/laser.wav" -> "sounds/effects/laser.wav"
|
||||||
|
std::string normalizeMusicPath(const std::string& name) {
|
||||||
|
return (name.rfind("music/", 0) == 0) ? name : "music/" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string normalizeSoundPath(const std::string& name) {
|
||||||
|
return (name.rfind("sounds/", 0) == 0) ? name : "sounds/" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace AudioResource {
|
||||||
|
|
||||||
|
auto getMusic(const std::string& name) -> Ja::Music* {
|
||||||
|
auto& cache = musicCache();
|
||||||
|
if (auto it = cache.find(name); it != cache.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string path = normalizeMusicPath(name);
|
||||||
|
auto bytes = Resource::Helper::loadFile(path);
|
||||||
|
if (bytes.empty()) {
|
||||||
|
std::cerr << "[AudioResource] no se ha podido cargar música: " << path << "\n";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ja::Music* raw = Ja::loadMusic(bytes.data(), static_cast<std::uint32_t>(bytes.size()), name.c_str());
|
||||||
|
if (raw == nullptr) {
|
||||||
|
std::cerr << "[AudioResource] decodificación de música falló: " << path << "\n";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.emplace(name, std::unique_ptr<Ja::Music>(raw));
|
||||||
|
std::cout << "[AudioResource] música cargada: " << path << "\n";
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getSound(const std::string& name) -> Ja::Sound* {
|
||||||
|
auto& cache = soundCache();
|
||||||
|
if (auto it = cache.find(name); it != cache.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string path = normalizeSoundPath(name);
|
||||||
|
auto bytes = Resource::Helper::loadFile(path);
|
||||||
|
if (bytes.empty()) {
|
||||||
|
std::cerr << "[AudioResource] no se ha podido cargar sonido: " << path << "\n";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ja::Sound* raw = Ja::loadSound(bytes.data(), static_cast<std::uint32_t>(bytes.size()));
|
||||||
|
if (raw == nullptr) {
|
||||||
|
std::cerr << "[AudioResource] decodificación de sonido falló: " << path << "\n";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.emplace(name, std::unique_ptr<Ja::Sound>(raw));
|
||||||
|
std::cout << "[AudioResource] sonido cargado: " << path << "\n";
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace AudioResource
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// --- Audio Resource Adapter ---
|
||||||
|
// Este archivo exposa una interfície comuna a Audio per obtenir Ja::Music* /
|
||||||
|
// Ja::Sound* per nom. Cada projecte la implementa en audio_adapter.cpp delegant
|
||||||
|
// al seu singleton de recursos (Resource::Cache::get(), ...). Así audio.hpp
|
||||||
|
// i audio.cpp es poden compartir entre projectes.
|
||||||
|
|
||||||
|
#include <string> // Para string
|
||||||
|
|
||||||
|
namespace Ja {
|
||||||
|
struct Music;
|
||||||
|
struct Sound;
|
||||||
|
} // namespace Ja
|
||||||
|
|
||||||
|
namespace AudioResource {
|
||||||
|
auto getMusic(const std::string& name) -> Ja::Music*;
|
||||||
|
auto getSound(const std::string& name) -> Ja::Sound*;
|
||||||
|
} // namespace AudioResource
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
// audio_cache.cpp - Implementació del caché de sons i música
|
|
||||||
// © 2025 Port a C++20 amb SDL3
|
|
||||||
|
|
||||||
#include "core/audio/audio_cache.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "core/resources/resource_helper.hpp"
|
|
||||||
|
|
||||||
// Inicialització de variables estàtiques
|
|
||||||
std::unordered_map<std::string, JA_Sound_t*> AudioCache::sounds_;
|
|
||||||
std::unordered_map<std::string, JA_Music_t*> AudioCache::musics_;
|
|
||||||
std::string AudioCache::sounds_base_path_ = "data/sounds/";
|
|
||||||
std::string AudioCache::music_base_path_ = "data/music/";
|
|
||||||
|
|
||||||
JA_Sound_t* AudioCache::getSound(const std::string& name) {
|
|
||||||
// Cache hit
|
|
||||||
auto it = sounds_.find(name);
|
|
||||||
if (it != sounds_.end()) {
|
|
||||||
std::cout << "[AudioCache] Sound cache hit: " << name << std::endl;
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize path: "laser_shoot.wav" → "sounds/laser_shoot.wav"
|
|
||||||
std::string normalized = name;
|
|
||||||
if (normalized.find("sounds/") != 0) {
|
|
||||||
normalized = "sounds/" + normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load from resource system
|
|
||||||
std::vector<uint8_t> data = Resource::Helper::loadFile(normalized);
|
|
||||||
if (data.empty()) {
|
|
||||||
std::cerr << "[AudioCache] Error: no s'ha pogut carregar " << normalized << std::endl;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load sound from memory
|
|
||||||
JA_Sound_t* sound = JA_LoadSound(data.data(), static_cast<uint32_t>(data.size()));
|
|
||||||
if (sound == nullptr) {
|
|
||||||
std::cerr << "[AudioCache] Error: no s'ha pogut decodificar " << normalized
|
|
||||||
<< std::endl;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "[AudioCache] Sound loaded: " << normalized << std::endl;
|
|
||||||
sounds_[name] = sound;
|
|
||||||
return sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
JA_Music_t* AudioCache::getMusic(const std::string& name) {
|
|
||||||
// Cache hit
|
|
||||||
auto it = musics_.find(name);
|
|
||||||
if (it != musics_.end()) {
|
|
||||||
std::cout << "[AudioCache] Music cache hit: " << name << std::endl;
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize path: "title.ogg" → "music/title.ogg"
|
|
||||||
std::string normalized = name;
|
|
||||||
if (normalized.find("music/") != 0) {
|
|
||||||
normalized = "music/" + normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load from resource system
|
|
||||||
std::vector<uint8_t> data = Resource::Helper::loadFile(normalized);
|
|
||||||
if (data.empty()) {
|
|
||||||
std::cerr << "[AudioCache] Error: no s'ha pogut carregar " << normalized << std::endl;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load music from memory
|
|
||||||
JA_Music_t* music = JA_LoadMusic(data.data(), static_cast<uint32_t>(data.size()));
|
|
||||||
if (music == nullptr) {
|
|
||||||
std::cerr << "[AudioCache] Error: no s'ha pogut decodificar " << normalized
|
|
||||||
<< std::endl;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "[AudioCache] Music loaded: " << normalized << std::endl;
|
|
||||||
musics_[name] = music;
|
|
||||||
return music;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioCache::clear() {
|
|
||||||
std::cout << "[AudioCache] Clearing cache (" << sounds_.size() << " sounds, "
|
|
||||||
<< musics_.size() << " music)" << std::endl;
|
|
||||||
|
|
||||||
// Liberar memoria de sonidos
|
|
||||||
for (auto& [name, sound] : sounds_) {
|
|
||||||
if (sound && sound->buffer) {
|
|
||||||
SDL_free(sound->buffer);
|
|
||||||
}
|
|
||||||
delete sound;
|
|
||||||
}
|
|
||||||
sounds_.clear();
|
|
||||||
|
|
||||||
// Liberar memoria de música
|
|
||||||
for (auto& [name, music] : musics_) {
|
|
||||||
if (music && music->buffer) {
|
|
||||||
SDL_free(music->buffer);
|
|
||||||
}
|
|
||||||
if (music && music->filename) {
|
|
||||||
free(music->filename);
|
|
||||||
}
|
|
||||||
delete music;
|
|
||||||
}
|
|
||||||
musics_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AudioCache::getSoundCacheSize() { return sounds_.size(); }
|
|
||||||
|
|
||||||
size_t AudioCache::getMusicCacheSize() { return musics_.size(); }
|
|
||||||
|
|
||||||
std::string AudioCache::resolveSoundPath(const std::string& name) {
|
|
||||||
// Si es un path absoluto (comienza con '/'), usarlo directamente
|
|
||||||
if (!name.empty() && name[0] == '/') {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si ya contiene el prefix base_path, usarlo directamente
|
|
||||||
if (name.find(sounds_base_path_) == 0) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Caso contrario, añadir base_path
|
|
||||||
return sounds_base_path_ + name;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string AudioCache::resolveMusicPath(const std::string& name) {
|
|
||||||
// Si es un path absoluto (comienza con '/'), usarlo directamente
|
|
||||||
if (!name.empty() && name[0] == '/') {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si ya contiene el prefix base_path, usarlo directamente
|
|
||||||
if (name.find(music_base_path_) == 0) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Caso contrario, añadir base_path
|
|
||||||
return music_base_path_ + name;
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// audio_cache.hpp - Caché simplificado de sonidos y música
|
|
||||||
// © 2025 Port a C++20 amb SDL3
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include "core/audio/jail_audio.hpp"
|
|
||||||
|
|
||||||
// Caché estático de sonidos y música
|
|
||||||
// Patrón inspirado en Graphics::ShapeLoader
|
|
||||||
class AudioCache {
|
|
||||||
public:
|
|
||||||
// No instanciable (todo estático)
|
|
||||||
AudioCache() = delete;
|
|
||||||
|
|
||||||
// Obtener sonido (carga bajo demanda)
|
|
||||||
// Retorna puntero (nullptr si error)
|
|
||||||
static JA_Sound_t* getSound(const std::string& name);
|
|
||||||
|
|
||||||
// Obtener música (carga bajo demanda)
|
|
||||||
// Retorna puntero (nullptr si error)
|
|
||||||
static JA_Music_t* getMusic(const std::string& name);
|
|
||||||
|
|
||||||
// Limpiar caché (útil para debug/recarga)
|
|
||||||
static void clear();
|
|
||||||
|
|
||||||
// Estadísticas (debug)
|
|
||||||
static size_t getSoundCacheSize();
|
|
||||||
static size_t getMusicCacheSize();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::unordered_map<std::string, JA_Sound_t*> sounds_;
|
|
||||||
static std::unordered_map<std::string, JA_Music_t*> musics_;
|
|
||||||
static std::string sounds_base_path_; // "data/sounds/"
|
|
||||||
static std::string music_base_path_; // "data/music/"
|
|
||||||
|
|
||||||
// Helpers privados
|
|
||||||
static std::string resolveSoundPath(const std::string& name);
|
|
||||||
static std::string resolveMusicPath(const std::string& name);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
#include "core/audio/audio_effects.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/audio/jail_audio.hpp"
|
||||||
|
|
||||||
|
namespace AudioEffects {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// --- Caps de cua ---
|
||||||
|
constexpr float ECHO_TAIL_MS = 800.0F;
|
||||||
|
constexpr float REVERB_TAIL_MS = 1500.0F;
|
||||||
|
|
||||||
|
// --- Constants Freeverb ---
|
||||||
|
// Delays de comb i allpass tunats para 44.1 kHz; los reescalem per
|
||||||
|
// freqüència real de la font.
|
||||||
|
constexpr int COMB_REFERENCE_RATE = 44100;
|
||||||
|
constexpr std::array<int, 8> COMB_DELAYS_L = {1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617};
|
||||||
|
constexpr std::array<int, 4> ALLPASS_DELAYS_L = {556, 441, 341, 225};
|
||||||
|
constexpr int STEREO_SPREAD = 23;
|
||||||
|
|
||||||
|
// Mapeig de Schroeder/Dattorro/Freeverb estàndard.
|
||||||
|
constexpr float FIXED_GAIN = 0.015F;
|
||||||
|
constexpr float SCALE_ROOM = 0.28F;
|
||||||
|
constexpr float OFFSET_ROOM = 0.7F;
|
||||||
|
constexpr float SCALE_DAMP = 0.4F;
|
||||||
|
|
||||||
|
// --- Decodificació a float -1..1 ---
|
||||||
|
// Suporta U8/S16, mono/estèreo. Mono es duplica a L i R (la cadena
|
||||||
|
// d'efectes treballa siempre con dos canals per simplicitat).
|
||||||
|
auto decodeToStereoFloat(const Ja::Sound& src, std::vector<float>& left, std::vector<float>& right) -> bool {
|
||||||
|
const auto& spec = src.spec;
|
||||||
|
const Uint8* buf = src.buffer.get();
|
||||||
|
if (buf == nullptr || src.length == 0) { return false; }
|
||||||
|
|
||||||
|
int bytes_per_sample = 0;
|
||||||
|
if (spec.format == SDL_AUDIO_S16) {
|
||||||
|
bytes_per_sample = 2;
|
||||||
|
} else if (spec.format == SDL_AUDIO_U8) {
|
||||||
|
bytes_per_sample = 1;
|
||||||
|
} else {
|
||||||
|
std::cerr << "[AudioEffects] formato de sonido no soportado (solo U8 o S16)\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (spec.channels < 1 || spec.channels > 2) {
|
||||||
|
std::cerr << "[AudioEffects] el sonido debe ser mono o estéreo\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t TOTAL_FRAMES = src.length / static_cast<std::size_t>(bytes_per_sample * spec.channels);
|
||||||
|
left.resize(TOTAL_FRAMES);
|
||||||
|
right.resize(TOTAL_FRAMES);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < TOTAL_FRAMES; ++i) {
|
||||||
|
float sample_l = 0.0F;
|
||||||
|
float sample_r = 0.0F;
|
||||||
|
if (spec.format == SDL_AUDIO_S16) {
|
||||||
|
const auto* p = reinterpret_cast<const std::int16_t*>(buf + (i * spec.channels * 2));
|
||||||
|
sample_l = static_cast<float>(p[0]) / 32768.0F;
|
||||||
|
sample_r = (spec.channels == 2) ? static_cast<float>(p[1]) / 32768.0F : sample_l;
|
||||||
|
} else { // U8
|
||||||
|
const Uint8* p = buf + (i * spec.channels);
|
||||||
|
sample_l = (static_cast<float>(p[0]) - 128.0F) / 128.0F;
|
||||||
|
sample_r = (spec.channels == 2) ? (static_cast<float>(p[1]) - 128.0F) / 128.0F : sample_l;
|
||||||
|
}
|
||||||
|
left[i] = sample_l;
|
||||||
|
right[i] = sample_r;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empaqueta dos canals float (-1..1) a S16 entrellaçat.
|
||||||
|
void encodeStereoS16(const std::vector<float>& left, const std::vector<float>& right, std::vector<std::uint8_t>& out) {
|
||||||
|
const std::size_t LEN = left.size();
|
||||||
|
out.resize(LEN * 2 * sizeof(std::int16_t));
|
||||||
|
auto* dst = reinterpret_cast<std::int16_t*>(out.data());
|
||||||
|
for (std::size_t i = 0; i < LEN; ++i) {
|
||||||
|
const float L = std::clamp(left[i], -1.0F, 1.0F);
|
||||||
|
const float R = std::clamp(right[i], -1.0F, 1.0F);
|
||||||
|
dst[(i * 2) + 0] = static_cast<std::int16_t>(std::lround(L * 32767.0F));
|
||||||
|
dst[(i * 2) + 1] = static_cast<std::int16_t>(std::lround(R * 32767.0F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reescala un delay de la taula de Freeverb para la freqüència real.
|
||||||
|
auto scaledDelay(int reference_delay, int rate) -> int {
|
||||||
|
const long SCALED = std::lround(static_cast<double>(reference_delay) * static_cast<double>(rate) / static_cast<double>(COMB_REFERENCE_RATE));
|
||||||
|
return std::max(1, static_cast<int>(SCALED));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Filtres bàsics ---
|
||||||
|
struct Comb {
|
||||||
|
std::vector<float> buf;
|
||||||
|
std::size_t idx{0};
|
||||||
|
float feedback{0.0F};
|
||||||
|
float damp1{0.0F};
|
||||||
|
float damp2{1.0F};
|
||||||
|
float store{0.0F};
|
||||||
|
|
||||||
|
void init(int delay, float fb, float damping) {
|
||||||
|
buf.assign(static_cast<std::size_t>(delay), 0.0F);
|
||||||
|
idx = 0;
|
||||||
|
feedback = fb;
|
||||||
|
damp1 = damping;
|
||||||
|
damp2 = 1.0F - damping;
|
||||||
|
store = 0.0F;
|
||||||
|
}
|
||||||
|
auto tick(float in) -> float {
|
||||||
|
const float OUT = buf[idx];
|
||||||
|
store = (OUT * damp2) + (store * damp1);
|
||||||
|
buf[idx] = in + (store * feedback);
|
||||||
|
idx = (idx + 1) % buf.size();
|
||||||
|
return OUT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Allpass {
|
||||||
|
std::vector<float> buf;
|
||||||
|
std::size_t idx{0};
|
||||||
|
|
||||||
|
void init(int delay) {
|
||||||
|
buf.assign(static_cast<std::size_t>(delay), 0.0F);
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
auto tick(float in) -> float {
|
||||||
|
const float BUFOUT = buf[idx];
|
||||||
|
const float OUT = -in + BUFOUT;
|
||||||
|
buf[idx] = in + (BUFOUT * 0.5F);
|
||||||
|
idx = (idx + 1) % buf.size();
|
||||||
|
return OUT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto applyEcho(const Ja::Sound& src, const Ja::EchoParams& params) -> std::optional<ProcessedSound> {
|
||||||
|
std::vector<float> left;
|
||||||
|
std::vector<float> right;
|
||||||
|
if (!decodeToStereoFloat(src, left, right)) { return std::nullopt; }
|
||||||
|
|
||||||
|
const int RATE = src.spec.freq;
|
||||||
|
const int DELAY_SAMPLES = std::max(1, static_cast<int>(std::lround(params.delay_ms * 0.001F * static_cast<float>(RATE))));
|
||||||
|
const auto TAIL_SAMPLES = static_cast<std::size_t>(std::lround(ECHO_TAIL_MS * 0.001F * static_cast<float>(RATE)));
|
||||||
|
|
||||||
|
const float FEEDBACK = std::clamp(params.feedback, 0.0F, 0.95F);
|
||||||
|
const float WET = std::clamp(params.wet, 0.0F, 1.0F);
|
||||||
|
const float DRY = 1.0F - WET;
|
||||||
|
|
||||||
|
const std::size_t INPUT_LEN = left.size();
|
||||||
|
const std::size_t TOTAL_LEN = INPUT_LEN + TAIL_SAMPLES;
|
||||||
|
|
||||||
|
std::vector<float> ring_l(static_cast<std::size_t>(DELAY_SAMPLES), 0.0F);
|
||||||
|
std::vector<float> ring_r(static_cast<std::size_t>(DELAY_SAMPLES), 0.0F);
|
||||||
|
std::size_t cursor = 0;
|
||||||
|
|
||||||
|
std::vector<float> out_l(TOTAL_LEN);
|
||||||
|
std::vector<float> out_r(TOTAL_LEN);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < TOTAL_LEN; ++i) {
|
||||||
|
const float IN_L = (i < INPUT_LEN) ? left[i] : 0.0F;
|
||||||
|
const float IN_R = (i < INPUT_LEN) ? right[i] : 0.0F;
|
||||||
|
|
||||||
|
const float DELAYED_L = ring_l[cursor];
|
||||||
|
const float DELAYED_R = ring_r[cursor];
|
||||||
|
|
||||||
|
out_l[i] = (DRY * IN_L) + (WET * DELAYED_L);
|
||||||
|
out_r[i] = (DRY * IN_R) + (WET * DELAYED_R);
|
||||||
|
|
||||||
|
ring_l[cursor] = IN_L + (DELAYED_L * FEEDBACK);
|
||||||
|
ring_r[cursor] = IN_R + (DELAYED_R * FEEDBACK);
|
||||||
|
cursor = (cursor + 1) % static_cast<std::size_t>(DELAY_SAMPLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessedSound result;
|
||||||
|
result.spec = SDL_AudioSpec{.format = SDL_AUDIO_S16, .channels = 2, .freq = RATE};
|
||||||
|
encodeStereoS16(out_l, out_r, result.bytes);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto applyReverb(const Ja::Sound& src, const Ja::ReverbParams& params) -> std::optional<ProcessedSound> {
|
||||||
|
std::vector<float> left;
|
||||||
|
std::vector<float> right;
|
||||||
|
if (!decodeToStereoFloat(src, left, right)) { return std::nullopt; }
|
||||||
|
|
||||||
|
const int RATE = src.spec.freq;
|
||||||
|
const auto TAIL_SAMPLES = static_cast<std::size_t>(std::lround(REVERB_TAIL_MS * 0.001F * static_cast<float>(RATE)));
|
||||||
|
|
||||||
|
const float ROOM_SIZE = std::clamp(params.room_size, 0.0F, 1.0F);
|
||||||
|
const float DAMPING = std::clamp(params.damping, 0.0F, 1.0F);
|
||||||
|
const float WET = std::clamp(params.wet, 0.0F, 1.0F);
|
||||||
|
const float DRY = 1.0F - WET;
|
||||||
|
|
||||||
|
const float FEEDBACK = (ROOM_SIZE * SCALE_ROOM) + OFFSET_ROOM; // 0.7..0.98
|
||||||
|
const float DAMP1 = DAMPING * SCALE_DAMP; // 0..0.4
|
||||||
|
|
||||||
|
// Inicialitza los 8 comb filters per cada canal i los 4 allpass.
|
||||||
|
std::array<Comb, 8> comb_l;
|
||||||
|
std::array<Comb, 8> comb_r;
|
||||||
|
for (std::size_t i = 0; i < COMB_DELAYS_L.size(); ++i) {
|
||||||
|
comb_l[i].init(scaledDelay(COMB_DELAYS_L[i], RATE), FEEDBACK, DAMP1);
|
||||||
|
comb_r[i].init(scaledDelay(COMB_DELAYS_L[i] + STEREO_SPREAD, RATE), FEEDBACK, DAMP1);
|
||||||
|
}
|
||||||
|
std::array<Allpass, 4> allpass_l;
|
||||||
|
std::array<Allpass, 4> allpass_r;
|
||||||
|
for (std::size_t i = 0; i < ALLPASS_DELAYS_L.size(); ++i) {
|
||||||
|
allpass_l[i].init(scaledDelay(ALLPASS_DELAYS_L[i], RATE));
|
||||||
|
allpass_r[i].init(scaledDelay(ALLPASS_DELAYS_L[i] + STEREO_SPREAD, RATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t INPUT_LEN = left.size();
|
||||||
|
const std::size_t TOTAL_LEN = INPUT_LEN + TAIL_SAMPLES;
|
||||||
|
std::vector<float> out_l(TOTAL_LEN);
|
||||||
|
std::vector<float> out_r(TOTAL_LEN);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < TOTAL_LEN; ++i) {
|
||||||
|
const float IN_L = (i < INPUT_LEN) ? left[i] : 0.0F;
|
||||||
|
const float IN_R = (i < INPUT_LEN) ? right[i] : 0.0F;
|
||||||
|
const float MONO_INPUT = (IN_L + IN_R) * FIXED_GAIN;
|
||||||
|
|
||||||
|
// 8 comb filters en paral·lel, sumats.
|
||||||
|
float wet_l = 0.0F;
|
||||||
|
float wet_r = 0.0F;
|
||||||
|
for (std::size_t k = 0; k < comb_l.size(); ++k) {
|
||||||
|
wet_l += comb_l[k].tick(MONO_INPUT);
|
||||||
|
wet_r += comb_r[k].tick(MONO_INPUT);
|
||||||
|
}
|
||||||
|
// 4 allpass en sèrie.
|
||||||
|
for (std::size_t k = 0; k < allpass_l.size(); ++k) {
|
||||||
|
wet_l = allpass_l[k].tick(wet_l);
|
||||||
|
wet_r = allpass_r[k].tick(wet_r);
|
||||||
|
}
|
||||||
|
|
||||||
|
out_l[i] = (DRY * IN_L) + (WET * wet_l);
|
||||||
|
out_r[i] = (DRY * IN_R) + (WET * wet_r);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessedSound result;
|
||||||
|
result.spec = SDL_AudioSpec{.format = SDL_AUDIO_S16, .channels = 2, .freq = RATE};
|
||||||
|
encodeStereoS16(out_l, out_r, result.bytes);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace AudioEffects
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Forward-declaració per no incloure jail_audio.hpp (cicle d'inclusió: este
|
||||||
|
// header viu sota los params declarats a jail_audio.hpp, i alhora jail_audio
|
||||||
|
// usa applyEcho/applyReverb).
|
||||||
|
namespace Ja {
|
||||||
|
struct Sound;
|
||||||
|
struct EchoParams;
|
||||||
|
struct ReverbParams;
|
||||||
|
} // namespace Ja
|
||||||
|
|
||||||
|
// Processadors d'efectes para sons puntuals. Reben un Ja::Sound (qualsevol
|
||||||
|
// format suportat pel decodificador WAV: U8/S16, mono o estèreo) i tornen un
|
||||||
|
// buffer PCM en S16 + el seu spec, llest per empenyer a un SDL_AudioStream.
|
||||||
|
//
|
||||||
|
// El buffer de sortida inclou la cua (decay) generada per l'efecte: per al
|
||||||
|
// reverb, hasta a 1500 ms; para l'eco, hasta a 800 ms. Aquests caps eviten
|
||||||
|
// allargar indefinidament la reproducció cuando los parámetros reinjecten mucho.
|
||||||
|
//
|
||||||
|
// Si el format del so d'origen no es pot processar, retornen std::nullopt
|
||||||
|
// (el caller ha de fer fallback a reproducció seca).
|
||||||
|
namespace AudioEffects {
|
||||||
|
|
||||||
|
struct ProcessedSound {
|
||||||
|
std::vector<std::uint8_t> bytes; // PCM S16 entrellaçat (LRLRLR... si stereo)
|
||||||
|
SDL_AudioSpec spec; // Format/canals/freqüència del buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] auto applyEcho(const Ja::Sound& src, const Ja::EchoParams& params) -> std::optional<ProcessedSound>;
|
||||||
|
[[nodiscard]] auto applyReverb(const Ja::Sound& src, const Ja::ReverbParams& params) -> std::optional<ProcessedSound>;
|
||||||
|
|
||||||
|
} // namespace AudioEffects
|
||||||
@@ -0,0 +1,645 @@
|
|||||||
|
#include "core/audio/jail_audio.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/audio/audio_effects.hpp"
|
||||||
|
|
||||||
|
// Solo declaracions de stb_vorbis: STB_VORBIS_HEADER_ONLY omet el bloc
|
||||||
|
// d'implementació. Les definicions las aporta source/external/stb_vorbis_impl.cpp
|
||||||
|
// (TU aïllat porque clang-analyzer no dispari fals positius al nostre codi).
|
||||||
|
#define STB_VORBIS_HEADER_ONLY
|
||||||
|
// clang-format off
|
||||||
|
// NOLINTNEXTLINE(bugprone-suspicious-include) -- stb_vorbis es single-file: la macro de dalt limita este TU a solo-declaracions; la implementació viu a external/stb_vorbis_impl.cpp.
|
||||||
|
#include "external/stb_vorbis.c"
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
namespace Ja {
|
||||||
|
|
||||||
|
// --- Streaming internals (file-scope constants) ---
|
||||||
|
namespace {
|
||||||
|
// Bytes-per-sample per canal (siempre s16)
|
||||||
|
constexpr int MUSIC_BYTES_PER_SAMPLE = 2;
|
||||||
|
// Quants shorts decodifiquem per crida a get_samples_short_interleaved.
|
||||||
|
// 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz.
|
||||||
|
constexpr int MUSIC_CHUNK_SHORTS = 8192;
|
||||||
|
// Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a
|
||||||
|
// l'SDL_AudioStream per absorbir jitter de frame i evitar underruns.
|
||||||
|
constexpr float MUSIC_LOW_WATER_SECONDS = 0.5F;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// --- Engine::active_ storage ---
|
||||||
|
Engine* Engine::active_ = nullptr;
|
||||||
|
|
||||||
|
auto Engine::active() noexcept -> Engine* { return active_; }
|
||||||
|
|
||||||
|
// --- Ctor/Dtor ---
|
||||||
|
|
||||||
|
Engine::Engine(const int freq, const SDL_AudioFormat format, const int num_channels) {
|
||||||
|
assert(active_ == nullptr && "Ja::Engine: més d'una instància activa no está suportat");
|
||||||
|
active_ = this;
|
||||||
|
|
||||||
|
audio_spec_ = {.format = format, .channels = num_channels, .freq = freq};
|
||||||
|
sdl_audio_device_ = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec_);
|
||||||
|
if (sdl_audio_device_ == 0) { std::fprintf(stderr, "Ja::Engine: Failed to initialize SDL audio!\n"); }
|
||||||
|
for (auto& channel : channels_) { channel.state = ChannelState::FREE; }
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine::~Engine() {
|
||||||
|
if (outgoing_music_.stream != nullptr) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||||
|
outgoing_music_.stream = nullptr;
|
||||||
|
}
|
||||||
|
if (sdl_audio_device_ != 0) { SDL_CloseAudioDevice(sdl_audio_device_); }
|
||||||
|
sdl_audio_device_ = 0;
|
||||||
|
|
||||||
|
if (active_ == this) { active_ = nullptr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Helpers stateless (no toquen membres d'Engine) ---
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto feedMusicChunk(Music* music) -> int {
|
||||||
|
if (music == nullptr || music->vorbis == nullptr || music->stream == nullptr) { return 0; }
|
||||||
|
|
||||||
|
short chunk[MUSIC_CHUNK_SHORTS];
|
||||||
|
const int NUM_CHANNELS = music->spec.channels;
|
||||||
|
const int SAMPLES_PER_CHANNEL = stb_vorbis_get_samples_short_interleaved(
|
||||||
|
music->vorbis,
|
||||||
|
NUM_CHANNELS,
|
||||||
|
static_cast<short*>(chunk),
|
||||||
|
MUSIC_CHUNK_SHORTS);
|
||||||
|
if (SAMPLES_PER_CHANNEL <= 0) { return 0; }
|
||||||
|
|
||||||
|
const int BYTES = SAMPLES_PER_CHANNEL * NUM_CHANNELS * MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
SDL_PutAudioStreamData(music->stream, static_cast<const void*>(chunk), BYTES);
|
||||||
|
return SAMPLES_PER_CHANNEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pumpMusic(Music* music) {
|
||||||
|
if (music == nullptr || music->vorbis == nullptr || music->stream == nullptr) { return; }
|
||||||
|
|
||||||
|
const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
const int LOW_WATER_BYTES = static_cast<int>(MUSIC_LOW_WATER_SECONDS * static_cast<float>(BYTES_PER_SECOND));
|
||||||
|
|
||||||
|
while (SDL_GetAudioStreamAvailable(music->stream) < LOW_WATER_BYTES) {
|
||||||
|
const int DECODED = feedMusicChunk(music);
|
||||||
|
if (DECODED > 0) { continue; }
|
||||||
|
|
||||||
|
// EOF: si queden loops, rebobinar; si no, tallar y deixar drenar.
|
||||||
|
if (music->times != 0) {
|
||||||
|
stb_vorbis_seek_start(music->vorbis);
|
||||||
|
if (music->times > 0) { music->times--; }
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void preFillOutgoing(Music* music, const int duration_ms) {
|
||||||
|
if (music == nullptr || music->vorbis == nullptr || music->stream == nullptr) { return; }
|
||||||
|
|
||||||
|
const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
const int NEEDED_BYTES = static_cast<int>((static_cast<std::int64_t>(duration_ms) * BYTES_PER_SECOND) / 1000);
|
||||||
|
|
||||||
|
while (SDL_GetAudioStreamAvailable(music->stream) < NEEDED_BYTES) {
|
||||||
|
const int DECODED = feedMusicChunk(music);
|
||||||
|
if (DECODED <= 0) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retorna el progrés lineal [0..1] d'un fade. 1.0 vol dir completat. Única
|
||||||
|
// font de la corba del fade: si es vol canviar a logarítmica/quadràtica,
|
||||||
|
// s'edita aquí i afecta fade-in i fade-out alhora.
|
||||||
|
auto fadeProgress(const FadeState& fade) -> float {
|
||||||
|
if (fade.duration_ms <= 0) { return 1.0F; }
|
||||||
|
const Uint64 ELAPSED = SDL_GetTicks() - fade.start_time;
|
||||||
|
if (ELAPSED >= static_cast<Uint64>(fade.duration_ms)) { return 1.0F; }
|
||||||
|
return static_cast<float>(ELAPSED) / static_cast<float>(fade.duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Engine::updateOutgoingFade() {
|
||||||
|
if (outgoing_music_.stream == nullptr || !outgoing_music_.fade.active) { return; }
|
||||||
|
|
||||||
|
// Mentre la fosa está activa, mantenim el stream con una reserva
|
||||||
|
// de samples per davant del cursor (mismo patró que pumpMusic
|
||||||
|
// para el current_music_). Así el stream no es buida ni cuando SDL
|
||||||
|
// drena més ràpid del previst en haver sounds bound a la misma
|
||||||
|
// device. Si l'OGG arriba a EOF, rebobina (la fosa pot ser més
|
||||||
|
// llarga que la pista).
|
||||||
|
if (outgoing_music_.music != nullptr && outgoing_music_.music->vorbis != nullptr) {
|
||||||
|
const Music& music = *outgoing_music_.music;
|
||||||
|
const int BYTES_PER_SECOND = music.spec.freq * music.spec.channels * MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
const int LOW_WATER = static_cast<int>(MUSIC_LOW_WATER_SECONDS * static_cast<float>(BYTES_PER_SECOND));
|
||||||
|
while (SDL_GetAudioStreamAvailable(outgoing_music_.stream) < LOW_WATER) {
|
||||||
|
short chunk[MUSIC_CHUNK_SHORTS];
|
||||||
|
const int SAMPLES = stb_vorbis_get_samples_short_interleaved(
|
||||||
|
music.vorbis,
|
||||||
|
music.spec.channels,
|
||||||
|
static_cast<short*>(chunk),
|
||||||
|
MUSIC_CHUNK_SHORTS);
|
||||||
|
if (SAMPLES <= 0) {
|
||||||
|
stb_vorbis_seek_start(music.vorbis);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const int BYTES = SAMPLES * music.spec.channels * MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
SDL_PutAudioStreamData(outgoing_music_.stream, static_cast<const void*>(chunk), BYTES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const float PROGRESS = fadeProgress(outgoing_music_.fade);
|
||||||
|
if (PROGRESS >= 1.0F) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||||
|
outgoing_music_.stream = nullptr;
|
||||||
|
outgoing_music_.fade.active = false;
|
||||||
|
// Deixem el Vorbis del Music original en un estat conegut per
|
||||||
|
// a la pròxima reproducció. (playMusic también fa seek_start,
|
||||||
|
// pero fer-ho ací evita estats intermedis si algú consulta.)
|
||||||
|
if (outgoing_music_.music != nullptr && outgoing_music_.music->vorbis != nullptr) {
|
||||||
|
stb_vorbis_seek_start(outgoing_music_.music->vorbis);
|
||||||
|
}
|
||||||
|
outgoing_music_.music = nullptr;
|
||||||
|
} else {
|
||||||
|
SDL_SetAudioStreamGain(outgoing_music_.stream, outgoing_music_.fade.initial_volume * (1.0F - PROGRESS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::updateIncomingFade() {
|
||||||
|
if (!incoming_fade_.active) { return; }
|
||||||
|
|
||||||
|
const float PROGRESS = fadeProgress(incoming_fade_);
|
||||||
|
if (PROGRESS >= 1.0F) {
|
||||||
|
incoming_fade_.active = false;
|
||||||
|
SDL_SetAudioStreamGain(current_music_->stream, music_volume_);
|
||||||
|
} else {
|
||||||
|
SDL_SetAudioStreamGain(current_music_->stream, music_volume_ * PROGRESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::updateCurrentMusic() {
|
||||||
|
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
|
||||||
|
|
||||||
|
updateIncomingFade();
|
||||||
|
|
||||||
|
pumpMusic(current_music_);
|
||||||
|
if (current_music_->times == 0 && SDL_GetAudioStreamAvailable(current_music_->stream) == 0) {
|
||||||
|
// La pista ha acabat de drenar naturalment. L'aturem primer (deixa
|
||||||
|
// l'engine en estat consistent) i entonces invoquem el callback;
|
||||||
|
// así un eventual playMusic des del callback comença net.
|
||||||
|
stopMusic();
|
||||||
|
if (on_music_ended_) { on_music_ended_(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::updateSoundChannels() {
|
||||||
|
for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; ++i) {
|
||||||
|
if (channels_[i].state != ChannelState::PLAYING) { continue; }
|
||||||
|
|
||||||
|
if (channels_[i].times != 0) {
|
||||||
|
if (static_cast<Uint32>(SDL_GetAudioStreamAvailable(channels_[i].stream)) < (channels_[i].sound->length / 2)) {
|
||||||
|
SDL_PutAudioStreamData(channels_[i].stream, channels_[i].sound->buffer.get(), channels_[i].sound->length);
|
||||||
|
if (channels_[i].times > 0) { channels_[i].times--; }
|
||||||
|
}
|
||||||
|
} else if (SDL_GetAudioStreamAvailable(channels_[i].stream) == 0) {
|
||||||
|
stopChannel(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::stealCurrentIntoOutgoing(const int duration_ms) {
|
||||||
|
if (outgoing_music_.stream != nullptr) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||||
|
outgoing_music_.stream = nullptr;
|
||||||
|
outgoing_music_.fade.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING || current_music_->stream == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
preFillOutgoing(current_music_, duration_ms);
|
||||||
|
|
||||||
|
outgoing_music_.stream = current_music_->stream;
|
||||||
|
// Guardem la referència al Music porque updateOutgoingFade puga
|
||||||
|
// seguir bombant Vorbis sin al stream durante tota la fosa. NO fem
|
||||||
|
// seek_start ací: la decompressió ha de continuar des d'on estava
|
||||||
|
// porque el so siga continu. El seek_start es farà cuando la fosa
|
||||||
|
// acabe (o cuando playMusic la interrompi via stopMusic).
|
||||||
|
outgoing_music_.music = current_music_;
|
||||||
|
outgoing_music_.fade = {
|
||||||
|
.active = true,
|
||||||
|
.start_time = SDL_GetTicks(),
|
||||||
|
.duration_ms = duration_ms,
|
||||||
|
.initial_volume = music_volume_,
|
||||||
|
};
|
||||||
|
current_music_->stream = nullptr;
|
||||||
|
current_music_->state = MusicState::STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Fn>
|
||||||
|
void Engine::forEachTargetChannel(const int channel, Fn&& fn) {
|
||||||
|
if (channel == -1) {
|
||||||
|
for (auto& ch : channels_) { fn(ch); }
|
||||||
|
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
|
||||||
|
fn(channels_[channel]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Engine public API ---
|
||||||
|
|
||||||
|
void Engine::update() {
|
||||||
|
updateOutgoingFade();
|
||||||
|
updateCurrentMusic();
|
||||||
|
updateSoundChannels();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::playMusic(Music* music, const int loop) {
|
||||||
|
if (music == nullptr || music->vorbis == nullptr) { return; }
|
||||||
|
|
||||||
|
stopMusic();
|
||||||
|
|
||||||
|
current_music_ = music;
|
||||||
|
current_music_->state = MusicState::PLAYING;
|
||||||
|
current_music_->times = loop;
|
||||||
|
|
||||||
|
stb_vorbis_seek_start(current_music_->vorbis);
|
||||||
|
|
||||||
|
current_music_->stream = SDL_CreateAudioStream(¤t_music_->spec, &audio_spec_);
|
||||||
|
if (current_music_->stream == nullptr) {
|
||||||
|
std::fprintf(stderr, "Ja::Engine::playMusic: Failed to create audio stream!\n");
|
||||||
|
current_music_->state = MusicState::STOPPED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_SetAudioStreamGain(current_music_->stream, music_volume_);
|
||||||
|
|
||||||
|
pumpMusic(current_music_);
|
||||||
|
|
||||||
|
if (!SDL_BindAudioStream(sdl_audio_device_, current_music_->stream)) {
|
||||||
|
std::fprintf(stderr, "Ja::Engine::playMusic: SDL_BindAudioStream failed!\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::setMusicSpeed(float ratio) {
|
||||||
|
if (current_music_ == nullptr || current_music_->stream == nullptr) { return; }
|
||||||
|
SDL_SetAudioStreamFrequencyRatio(current_music_->stream, ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::pauseMusic() {
|
||||||
|
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
|
||||||
|
|
||||||
|
current_music_->state = MusicState::PAUSED;
|
||||||
|
SDL_UnbindAudioStream(current_music_->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::resumeMusic() {
|
||||||
|
if (current_music_ == nullptr || current_music_->state != MusicState::PAUSED) { return; }
|
||||||
|
|
||||||
|
current_music_->state = MusicState::PLAYING;
|
||||||
|
SDL_BindAudioStream(sdl_audio_device_, current_music_->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::stopMusic() {
|
||||||
|
if (outgoing_music_.stream != nullptr) {
|
||||||
|
SDL_DestroyAudioStream(outgoing_music_.stream);
|
||||||
|
outgoing_music_.stream = nullptr;
|
||||||
|
outgoing_music_.fade.active = false;
|
||||||
|
if (outgoing_music_.music != nullptr && outgoing_music_.music->vorbis != nullptr) {
|
||||||
|
stb_vorbis_seek_start(outgoing_music_.music->vorbis);
|
||||||
|
}
|
||||||
|
outgoing_music_.music = nullptr;
|
||||||
|
}
|
||||||
|
incoming_fade_.active = false;
|
||||||
|
|
||||||
|
if (current_music_ == nullptr || current_music_->state == MusicState::INVALID || current_music_->state == MusicState::STOPPED) { return; }
|
||||||
|
|
||||||
|
current_music_->state = MusicState::STOPPED;
|
||||||
|
if (current_music_->stream != nullptr) {
|
||||||
|
SDL_DestroyAudioStream(current_music_->stream);
|
||||||
|
current_music_->stream = nullptr;
|
||||||
|
}
|
||||||
|
if (current_music_->vorbis != nullptr) {
|
||||||
|
stb_vorbis_seek_start(current_music_->vorbis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::fadeOutMusic(const int milliseconds) {
|
||||||
|
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
|
||||||
|
|
||||||
|
stealCurrentIntoOutgoing(milliseconds);
|
||||||
|
incoming_fade_.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::crossfadeMusic(Music* music, const int crossfade_ms, const int loop) {
|
||||||
|
if (music == nullptr || music->vorbis == nullptr) { return; }
|
||||||
|
|
||||||
|
stealCurrentIntoOutgoing(crossfade_ms);
|
||||||
|
|
||||||
|
current_music_ = music;
|
||||||
|
current_music_->state = MusicState::PLAYING;
|
||||||
|
current_music_->times = loop;
|
||||||
|
|
||||||
|
stb_vorbis_seek_start(current_music_->vorbis);
|
||||||
|
current_music_->stream = SDL_CreateAudioStream(¤t_music_->spec, &audio_spec_);
|
||||||
|
if (current_music_->stream == nullptr) {
|
||||||
|
std::fprintf(stderr, "Ja::Engine::crossfadeMusic: Failed to create audio stream!\n");
|
||||||
|
current_music_->state = MusicState::STOPPED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_SetAudioStreamGain(current_music_->stream, 0.0F);
|
||||||
|
pumpMusic(current_music_);
|
||||||
|
SDL_BindAudioStream(sdl_audio_device_, current_music_->stream);
|
||||||
|
|
||||||
|
incoming_fade_ = {
|
||||||
|
.active = true,
|
||||||
|
.start_time = SDL_GetTicks(),
|
||||||
|
.duration_ms = crossfade_ms,
|
||||||
|
.initial_volume = 0.0F,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Engine::getMusicState() const -> MusicState {
|
||||||
|
if (current_music_ == nullptr) { return MusicState::INVALID; }
|
||||||
|
return current_music_->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Engine::setMusicVolume(float volume) -> float {
|
||||||
|
music_volume_ = SDL_clamp(volume, 0.0F, 1.0F);
|
||||||
|
if (current_music_ != nullptr && current_music_->stream != nullptr) {
|
||||||
|
SDL_SetAudioStreamGain(current_music_->stream, music_volume_);
|
||||||
|
}
|
||||||
|
return music_volume_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::setOnMusicEnded(std::function<void()> callback) {
|
||||||
|
on_music_ended_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::onMusicDeleted(const Music* music) {
|
||||||
|
if (music == nullptr) { return; }
|
||||||
|
if (current_music_ == music) {
|
||||||
|
stopMusic();
|
||||||
|
current_music_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sound ---
|
||||||
|
|
||||||
|
auto Engine::playSound(Sound* sound, const int loop, const int group) -> int {
|
||||||
|
if (sound == nullptr) { return -1; }
|
||||||
|
|
||||||
|
int channel = 0;
|
||||||
|
while (channel < MAX_SIMULTANEOUS_CHANNELS && channels_[channel].state != ChannelState::FREE) { channel++; }
|
||||||
|
if (channel == MAX_SIMULTANEOUS_CHANNELS) {
|
||||||
|
// No hay canal libre, reemplazamos el primero
|
||||||
|
channel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return playSoundOnChannel(sound, channel, loop, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Engine::playSoundOnChannel(Sound* sound, const int channel, const int loop, const int group) -> int {
|
||||||
|
if (sound == nullptr) { return -1; }
|
||||||
|
if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return -1; }
|
||||||
|
|
||||||
|
stopChannel(channel);
|
||||||
|
|
||||||
|
channels_[channel].sound = sound;
|
||||||
|
channels_[channel].times = loop;
|
||||||
|
channels_[channel].pos = 0;
|
||||||
|
channels_[channel].group = group;
|
||||||
|
channels_[channel].state = ChannelState::PLAYING;
|
||||||
|
channels_[channel].stream = SDL_CreateAudioStream(&channels_[channel].sound->spec, &audio_spec_);
|
||||||
|
|
||||||
|
if (channels_[channel].stream == nullptr) {
|
||||||
|
std::fprintf(stderr, "Ja::Engine::playSoundOnChannel: Failed to create audio stream!\n");
|
||||||
|
channels_[channel].state = ChannelState::FREE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_PutAudioStreamData(channels_[channel].stream, channels_[channel].sound->buffer.get(), channels_[channel].sound->length);
|
||||||
|
SDL_SetAudioStreamGain(channels_[channel].stream, sound_volume_[group]);
|
||||||
|
SDL_BindAudioStream(sdl_audio_device_, channels_[channel].stream);
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::setChannelSpeed(const int channel, const float ratio) {
|
||||||
|
if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return; }
|
||||||
|
if (channels_[channel].stream == nullptr) { return; }
|
||||||
|
SDL_SetAudioStreamFrequencyRatio(channels_[channel].stream, ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::pauseChannel(const int channel) {
|
||||||
|
forEachTargetChannel(channel, [](Channel& ch) {
|
||||||
|
if (ch.state == ChannelState::PLAYING) {
|
||||||
|
ch.state = ChannelState::PAUSED;
|
||||||
|
SDL_UnbindAudioStream(ch.stream);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::resumeChannel(const int channel) {
|
||||||
|
const SDL_AudioDeviceID DEVICE = sdl_audio_device_;
|
||||||
|
forEachTargetChannel(channel, [DEVICE](Channel& ch) {
|
||||||
|
if (ch.state == ChannelState::PAUSED) {
|
||||||
|
ch.state = ChannelState::PLAYING;
|
||||||
|
SDL_BindAudioStream(DEVICE, ch.stream);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::stopChannel(const int channel) {
|
||||||
|
forEachTargetChannel(channel, [this](Channel& ch) {
|
||||||
|
if (ch.state != ChannelState::FREE) {
|
||||||
|
if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); }
|
||||||
|
ch.stream = nullptr;
|
||||||
|
ch.state = ChannelState::FREE;
|
||||||
|
ch.pos = 0;
|
||||||
|
ch.sound = nullptr;
|
||||||
|
if (ch.has_effect) {
|
||||||
|
ch.has_effect = false;
|
||||||
|
if (effect_channels_active_ > 0) { --effect_channels_active_; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Engine::setSoundVolume(float volume, const int group) -> float {
|
||||||
|
const float V = SDL_clamp(volume, 0.0F, 1.0F);
|
||||||
|
|
||||||
|
if (group == -1) {
|
||||||
|
std::ranges::fill(sound_volume_, V);
|
||||||
|
} else if (group >= 0 && group < MAX_GROUPS) {
|
||||||
|
sound_volume_[group] = V;
|
||||||
|
} else {
|
||||||
|
return V;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& ch : channels_) {
|
||||||
|
if ((ch.state == ChannelState::PLAYING) || (ch.state == ChannelState::PAUSED)) {
|
||||||
|
if (group == -1 || ch.group == group) {
|
||||||
|
if (ch.stream != nullptr) {
|
||||||
|
SDL_SetAudioStreamGain(ch.stream, sound_volume_[ch.group]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return V;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::onSoundDeleted(const Sound* sound) {
|
||||||
|
if (sound == nullptr) { return; }
|
||||||
|
for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; ++i) {
|
||||||
|
if (channels_[i].sound == sound) { stopChannel(i); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Engine::playProcessedOnFreeChannel(const std::vector<std::uint8_t>& bytes, const SDL_AudioSpec& spec, const int group) -> int {
|
||||||
|
// El sin de canals con efecte es valida antes de reservar slot —
|
||||||
|
// así evitem crear y destruir un stream solo per descartar el play.
|
||||||
|
if (effect_channels_active_ >= MAX_EFFECT_CHANNELS) { return -1; }
|
||||||
|
|
||||||
|
int channel = 0;
|
||||||
|
while (channel < MAX_SIMULTANEOUS_CHANNELS && channels_[channel].state != ChannelState::FREE) { ++channel; }
|
||||||
|
if (channel == MAX_SIMULTANEOUS_CHANNELS) { channel = 0; }
|
||||||
|
|
||||||
|
stopChannel(channel);
|
||||||
|
|
||||||
|
// El stream es crea contra l'spec del buffer processat (S16, ...)
|
||||||
|
// porque SDL faci el resampling sin a audio_spec_ del device.
|
||||||
|
channels_[channel].stream = SDL_CreateAudioStream(&spec, &audio_spec_);
|
||||||
|
if (channels_[channel].stream == nullptr) {
|
||||||
|
std::fprintf(stderr, "Ja::Engine::playProcessedOnFreeChannel: Failed to create audio stream!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
channels_[channel].sound = nullptr; // El buffer no es propietat de sin Ja::Sound.
|
||||||
|
channels_[channel].times = 0;
|
||||||
|
channels_[channel].pos = 0;
|
||||||
|
const int CLAMPED_GROUP = (group >= 0 && group < MAX_GROUPS) ? group : 0;
|
||||||
|
channels_[channel].group = CLAMPED_GROUP;
|
||||||
|
channels_[channel].state = ChannelState::PLAYING;
|
||||||
|
channels_[channel].has_effect = true;
|
||||||
|
++effect_channels_active_;
|
||||||
|
|
||||||
|
SDL_PutAudioStreamData(channels_[channel].stream, bytes.data(), static_cast<int>(bytes.size()));
|
||||||
|
SDL_SetAudioStreamGain(channels_[channel].stream, sound_volume_[CLAMPED_GROUP]);
|
||||||
|
SDL_BindAudioStream(sdl_audio_device_, channels_[channel].stream);
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Engine::playSoundWithEcho(const Sound* sound, const EchoParams& params, const int group) -> int {
|
||||||
|
if (sound == nullptr) { return -1; }
|
||||||
|
auto processed = AudioEffects::applyEcho(*sound, params);
|
||||||
|
if (!processed) { return -1; }
|
||||||
|
return playProcessedOnFreeChannel(processed->bytes, processed->spec, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Engine::playSoundWithReverb(const Sound* sound, const ReverbParams& params, const int group) -> int {
|
||||||
|
if (sound == nullptr) { return -1; }
|
||||||
|
auto processed = AudioEffects::applyReverb(*sound, params);
|
||||||
|
if (!processed) { return -1; }
|
||||||
|
return playProcessedOnFreeChannel(processed->bytes, processed->spec, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Factories y destructors (permanents) ---
|
||||||
|
|
||||||
|
auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* {
|
||||||
|
if (buffer == nullptr || length == 0) { return nullptr; }
|
||||||
|
|
||||||
|
// Allocem el Music primer per aprofitar el seu `std::vector<Uint8>`
|
||||||
|
// como a propietari del OGG comprimit. stb_vorbis guarda un punter
|
||||||
|
// persistent al buffer; como que ací no el resize'jem, el .data() es
|
||||||
|
// estable durante tot el cicle de vida del music.
|
||||||
|
auto music = std::make_unique<Music>();
|
||||||
|
music->ogg_data.assign(buffer, buffer + length);
|
||||||
|
|
||||||
|
int vorbis_error = 0;
|
||||||
|
music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(),
|
||||||
|
static_cast<int>(length),
|
||||||
|
&vorbis_error,
|
||||||
|
nullptr);
|
||||||
|
if (music->vorbis == nullptr) {
|
||||||
|
std::fprintf(stderr, "Ja::loadMusic: stb_vorbis_open_memory failed (error %d)\n", vorbis_error);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stb_vorbis_info INFO = stb_vorbis_get_info(music->vorbis);
|
||||||
|
music->spec.channels = static_cast<int>(INFO.channels);
|
||||||
|
music->spec.freq = static_cast<int>(INFO.sample_rate);
|
||||||
|
music->spec.format = SDL_AUDIO_S16;
|
||||||
|
// Pre-cálculo de la duración en ms a partir del header. stb_vorbis ya
|
||||||
|
// ha decodificat la informació necessària a `stb_vorbis_open_memory`;
|
||||||
|
// esta consulta no descodifica àudio, solo llig el comptador
|
||||||
|
// de samples. Si el sample_rate fos 0 (header malmès) deixem
|
||||||
|
// duration_ms a 0.
|
||||||
|
if (INFO.sample_rate > 0) {
|
||||||
|
const auto SAMPLES = stb_vorbis_stream_length_in_samples(music->vorbis);
|
||||||
|
music->duration_ms = static_cast<int>((static_cast<std::uint64_t>(SAMPLES) * 1000ULL) / INFO.sample_rate);
|
||||||
|
}
|
||||||
|
music->state = MusicState::STOPPED;
|
||||||
|
|
||||||
|
return music.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload con filename. Resource::Cache l'usa per registrar el path dins
|
||||||
|
// del propi Ja::Music (camp `filename`); la capa Audio l'usa per recuperar
|
||||||
|
// el nom después d'un playMusic(Ja::Music*, ...) — veure PATCH-02.
|
||||||
|
auto loadMusic(const Uint8* buffer, Uint32 length, const char* filename) -> Music* {
|
||||||
|
Music* music = loadMusic(buffer, length);
|
||||||
|
if (music != nullptr && filename != nullptr) { music->filename = filename; }
|
||||||
|
return music;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteMusic(Music* music) {
|
||||||
|
if (music == nullptr) { return; }
|
||||||
|
// Notifiquem el motor actiu porque pari la pista si es la current_music.
|
||||||
|
// Si no hay motor (shutdown-order invertit), passem: los recursos
|
||||||
|
// propis del Music es lliberen igualment a sota.
|
||||||
|
if (Engine* eng = Engine::active()) { eng->onMusicDeleted(music); }
|
||||||
|
|
||||||
|
if (music->stream != nullptr) { SDL_DestroyAudioStream(music->stream); }
|
||||||
|
if (music->vorbis != nullptr) { stb_vorbis_close(music->vorbis); }
|
||||||
|
delete music;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound* {
|
||||||
|
auto sound = std::make_unique<Sound>();
|
||||||
|
Uint8* raw = nullptr;
|
||||||
|
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), true, &sound->spec, &raw, &sound->length)) {
|
||||||
|
std::fprintf(stderr, "Ja::loadSound: Failed to load WAV from memory: %s\n", SDL_GetError());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
|
||||||
|
return sound.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteSound(Sound* sound) {
|
||||||
|
if (sound == nullptr) { return; }
|
||||||
|
if (Engine* eng = Engine::active()) { eng->onSoundDeleted(sound); }
|
||||||
|
// buffer es destrueix automàticament via RAII (SdlFreeDeleter).
|
||||||
|
delete sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ja
|
||||||
|
|
||||||
|
// --- stb_vorbis macro leak cleanup ---
|
||||||
|
// stb_vorbis.c filtra noms curts (L, C, R i PLAYBACK_*) al TU que el compila.
|
||||||
|
// Xocarien con parámetros de plantilla d'altres headers si estas definicions
|
||||||
|
// s'escapessin. Els netegem al final del TU per tancar la porta.
|
||||||
|
// clang-format off
|
||||||
|
#undef L
|
||||||
|
#undef C
|
||||||
|
#undef R
|
||||||
|
#undef PLAYBACK_MONO
|
||||||
|
#undef PLAYBACK_LEFT
|
||||||
|
#undef PLAYBACK_RIGHT
|
||||||
|
// clang-format on
|
||||||
+242
-466
@@ -2,481 +2,257 @@
|
|||||||
|
|
||||||
// --- Includes ---
|
// --- Includes ---
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <stdint.h> // Para uint32_t, uint8_t
|
|
||||||
#include <stdio.h> // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
|
|
||||||
#include <stdlib.h> // Para free, malloc
|
|
||||||
#include <string.h> // Para strcpy, strlen
|
|
||||||
|
|
||||||
#define STB_VORBIS_HEADER_ONLY
|
#include <cstdint>
|
||||||
#include "external/stb_vorbis.h" // Para stb_vorbis_decode_memory
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// --- Public Enums ---
|
// Forward-declaració del decoder de vorbis. La implementació viu a
|
||||||
enum JA_Channel_state { JA_CHANNEL_INVALID,
|
// jail_audio.cpp (únic TU que compila external/stb_vorbis.c). Qualsevol caller
|
||||||
JA_CHANNEL_FREE,
|
// solo necessita `stb_vorbis*` per punter — nunca per valor — así que el
|
||||||
JA_CHANNEL_PLAYING,
|
// forward decl n'hay prou i evita arrossegar el .c a tots los TU.
|
||||||
JA_CHANNEL_PAUSED,
|
// NOLINTNEXTLINE(readability-identifier-naming) — nom imposat per l'API de stb_vorbis
|
||||||
JA_SOUND_DISABLED };
|
struct stb_vorbis;
|
||||||
enum JA_Music_state { JA_MUSIC_INVALID,
|
|
||||||
JA_MUSIC_PLAYING,
|
|
||||||
JA_MUSIC_PAUSED,
|
|
||||||
JA_MUSIC_STOPPED,
|
|
||||||
JA_MUSIC_DISABLED };
|
|
||||||
|
|
||||||
// --- Struct Definitions ---
|
// Deleter stateless para buffers reservats con `SDL_malloc` / `SDL_LoadWAV*`.
|
||||||
#define JA_MAX_SIMULTANEOUS_CHANNELS 20
|
// Compatible con `std::unique_ptr<Uint8[], SdlFreeDeleter>` — zero size overhead
|
||||||
#define JA_MAX_GROUPS 2
|
// gràcies a EBO, igual que un unique_ptr con default_delete.
|
||||||
|
struct SdlFreeDeleter {
|
||||||
struct JA_Sound_t {
|
void operator()(Uint8* p) const noexcept {
|
||||||
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
if (p != nullptr) { SDL_free(p); }
|
||||||
Uint32 length{0};
|
}
|
||||||
Uint8* buffer{NULL};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct JA_Channel_t {
|
// Motor de baix nivell d'àudio del projecte jailgames: streaming OGG
|
||||||
JA_Sound_t* sound{nullptr};
|
// (stb_vorbis) + N canals d'efectes (SDL3 audio). No depèn d'Options ni de sin
|
||||||
|
// singleton del joc; solo de SDL3 i stb_vorbis. La capa superior (Audio) li
|
||||||
|
// passa recursos pel punter i fa el bookkeeping d'usuari.
|
||||||
|
namespace Ja {
|
||||||
|
|
||||||
|
// --- Public Enums ---
|
||||||
|
enum class ChannelState : std::uint8_t {
|
||||||
|
FREE,
|
||||||
|
PLAYING,
|
||||||
|
PAUSED,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class MusicState : std::uint8_t {
|
||||||
|
INVALID, // Music carregat pero nunca play-ejat
|
||||||
|
PLAYING,
|
||||||
|
PAUSED,
|
||||||
|
STOPPED,
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Constants ---
|
||||||
|
inline constexpr int MAX_SIMULTANEOUS_CHANNELS = 20;
|
||||||
|
inline constexpr int MAX_GROUPS = 2;
|
||||||
|
// Cap superior de canals que poden estar simultàniament reproduint un so
|
||||||
|
// con efecte (eco/reverb). Si está al límit, las noves crides con efecte
|
||||||
|
// cauen al camí sec — l'usuari sent el so igualment, sin la cua.
|
||||||
|
inline constexpr int MAX_EFFECT_CHANNELS = 4;
|
||||||
|
|
||||||
|
// --- Paràmetres d'efectes ---
|
||||||
|
// Els camps los fixa el caller (Audio) llegint sounds.yaml; el motor solo
|
||||||
|
// los passa a AudioEffects::applyEcho/applyReverb. Els defaults són
|
||||||
|
// sensats pero los presets los sobreescriuen.
|
||||||
|
struct EchoParams {
|
||||||
|
float delay_ms{220.0F}; // Temps hasta al primer rebot.
|
||||||
|
float feedback{0.45F}; // Reinjecció (0..0.95).
|
||||||
|
float wet{0.35F}; // Mescla humida (0..1).
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReverbParams {
|
||||||
|
float room_size{0.7F}; // Tamaño percebuda (0..1).
|
||||||
|
float damping{0.5F}; // Atenuació d'aguts per rebot (0..1).
|
||||||
|
float wet{0.4F}; // Mescla humida (0..1).
|
||||||
|
};
|
||||||
|
|
||||||
|
// Spec de fallback del dispositiu. S'aplica antes que l'Engine s'iniciï i
|
||||||
|
// como a valor inicial de Sound/Music. L'spec real d'ús l'imposa el ctor
|
||||||
|
// d'Engine, alimentat des de Defaults::Audio via Audio.
|
||||||
|
inline constexpr SDL_AudioSpec DEFAULT_SPEC{SDL_AUDIO_S16, 2, 48000};
|
||||||
|
|
||||||
|
// --- Struct Definitions ---
|
||||||
|
struct Sound {
|
||||||
|
SDL_AudioSpec spec{DEFAULT_SPEC};
|
||||||
|
Uint32 length{0};
|
||||||
|
// Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV
|
||||||
|
// via SDL_malloc; el deleter `SdlFreeDeleter` allibera con SDL_free.
|
||||||
|
std::unique_ptr<Uint8[], SdlFreeDeleter> buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
// L'ordre (punters primer, ints después, enum de 8 bits al final) minimitza
|
||||||
|
// el padding a 64-bit (evita avisos de clang-analyzer-optin.performance.Padding).
|
||||||
|
struct Channel {
|
||||||
|
Sound* sound{nullptr};
|
||||||
|
SDL_AudioStream* stream{nullptr};
|
||||||
int pos{0};
|
int pos{0};
|
||||||
int times{0};
|
int times{0};
|
||||||
int group{0};
|
int group{0};
|
||||||
|
ChannelState state{ChannelState::FREE};
|
||||||
|
// Marca si este canal va arrencar con so processat per un efecte.
|
||||||
|
// El motor compta canals actius con efecte per fer complir
|
||||||
|
// MAX_EFFECT_CHANNELS i alliberar el comptador en parar.
|
||||||
|
bool has_effect{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Music {
|
||||||
|
SDL_AudioSpec spec{DEFAULT_SPEC};
|
||||||
|
|
||||||
|
// OGG comprimit en memòria. Propietat nostra; es copia des del buffer
|
||||||
|
// d'entrada una sola vegada en loadMusic i es descomprimix en chunks
|
||||||
|
// per streaming. Como que stb_vorbis guarda un punter persistent al
|
||||||
|
// `.data()` d'este vector, no el podem resize'jar un cop establert
|
||||||
|
// (una reallocation invalidaria el punter que el decoder conserva).
|
||||||
|
std::vector<Uint8> ogg_data;
|
||||||
|
stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del Music
|
||||||
|
|
||||||
|
std::string filename;
|
||||||
|
|
||||||
|
int times{0}; // loops restants (-1 = infinit, 0 = un sol play)
|
||||||
|
// Duración total de la pista en mil·lisegons, mesurada via
|
||||||
|
// `stb_vorbis_stream_length_in_samples / sample_rate` al
|
||||||
|
// `loadMusic`. 0 si el cálculo no es possible (header malmès).
|
||||||
|
// L'usen consumidors que necessiten un timeline pre-calculat —
|
||||||
|
// p. ex. la FSM de sala — sin dependre de callbacks de fi.
|
||||||
|
int duration_ms{0};
|
||||||
SDL_AudioStream* stream{nullptr};
|
SDL_AudioStream* stream{nullptr};
|
||||||
JA_Channel_state state{JA_CHANNEL_FREE};
|
MusicState state{MusicState::INVALID};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct JA_Music_t {
|
struct FadeState {
|
||||||
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
bool active{false};
|
||||||
Uint32 length{0};
|
Uint64 start_time{0};
|
||||||
Uint8* buffer{nullptr};
|
int duration_ms{0};
|
||||||
char* filename{nullptr};
|
float initial_volume{0.0F};
|
||||||
|
};
|
||||||
|
|
||||||
int pos{0};
|
struct OutgoingMusic {
|
||||||
int times{0};
|
|
||||||
SDL_AudioStream* stream{nullptr};
|
SDL_AudioStream* stream{nullptr};
|
||||||
JA_Music_state state{JA_MUSIC_INVALID};
|
// Referència al Music original porque updateOutgoingFade puga
|
||||||
};
|
// continuar descomprimint des de Vorbis sin al stream durante
|
||||||
|
// tota la fosa. Sense això, solo tenim el pre-fill puntual i
|
||||||
// --- Internal Global State ---
|
// SDL drena el stream més ràpid del previst cuando hay sounds
|
||||||
// Marcado 'inline' (C++17) para asegurar una única instancia.
|
// bound a la misma device (~2x), buidant-lo a meitat del
|
||||||
|
// fade i sentint-se como un tall sec.
|
||||||
inline JA_Music_t* current_music{nullptr};
|
Music* music{nullptr};
|
||||||
inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];
|
FadeState fade;
|
||||||
|
};
|
||||||
inline SDL_AudioSpec JA_audioSpec{SDL_AUDIO_S16, 2, 48000};
|
|
||||||
inline float JA_musicVolume{1.0F};
|
// --- Engine ---
|
||||||
inline float JA_soundVolume[JA_MAX_GROUPS];
|
// Encapsula tot l'estat que antes vivia como a globals inline. Un sol Engine
|
||||||
inline bool JA_musicEnabled{true};
|
// viu per procés (enforceat via assert al ctor contra `active_`). El ctor
|
||||||
inline bool JA_soundEnabled{true};
|
// obre el device SDL; el dtor el tanca (RAII). Els deleters
|
||||||
inline SDL_AudioDeviceID sdlAudioDevice{0};
|
// `Ja::deleteMusic`/`Ja::deleteSound` accedeixen al motor actiu via
|
||||||
|
// `Engine::active()` per parar canals antes d'alliberar.
|
||||||
inline bool fading{false};
|
class Engine {
|
||||||
inline int fade_start_time{0};
|
public:
|
||||||
inline int fade_duration{0};
|
Engine(int freq, SDL_AudioFormat format, int num_channels);
|
||||||
inline float fade_initial_volume{0.0F}; // Corregido de 'int' a 'float'
|
~Engine();
|
||||||
|
Engine(const Engine&) = delete;
|
||||||
// --- Forward Declarations ---
|
auto operator=(const Engine&) -> Engine& = delete;
|
||||||
inline void JA_StopMusic();
|
Engine(Engine&&) = delete;
|
||||||
inline void JA_StopChannel(const int channel);
|
auto operator=(Engine&&) -> Engine& = delete;
|
||||||
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0);
|
|
||||||
|
// Retorna el motor actiu o nullptr si sin ha estat construït. L'usen
|
||||||
// --- Core Functions ---
|
// los deleters de recursos porque no los arriba sin referència directa.
|
||||||
|
[[nodiscard]] static auto active() noexcept -> Engine*;
|
||||||
inline void JA_Update() {
|
|
||||||
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) {
|
void update();
|
||||||
if (fading) {
|
|
||||||
int time = SDL_GetTicks();
|
// --- Música ---
|
||||||
if (time > (fade_start_time + fade_duration)) {
|
void playMusic(Music* music, int loop = -1);
|
||||||
fading = false;
|
void pauseMusic();
|
||||||
JA_StopMusic();
|
void resumeMusic();
|
||||||
return;
|
void stopMusic();
|
||||||
} else {
|
void fadeOutMusic(int milliseconds);
|
||||||
const int time_passed = time - fade_start_time;
|
void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1);
|
||||||
const float percent = (float)time_passed / (float)fade_duration;
|
[[nodiscard]] auto getMusicState() const -> MusicState;
|
||||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * (1.0 - percent));
|
auto setMusicVolume(float volume) -> float;
|
||||||
}
|
// Multiplicador de velocitat de reproducció de la música actual
|
||||||
}
|
// via `SDL_SetAudioStreamFrequencyRatio`. 1.0 = normal, 2.0 =
|
||||||
|
// doble velocitat. Cal saber que también puja el to (efecte
|
||||||
if (current_music->times != 0) {
|
// "chipmunk") — es el comportament arcade clàssic dels comptes
|
||||||
if ((Uint32)SDL_GetAudioStreamAvailable(current_music->stream) < (current_music->length / 2)) {
|
// enrere. Cada `playMusic` crea un stream nuevo con ratio 1.0,
|
||||||
SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length);
|
// así que un canvi de track reseteja la velocitat
|
||||||
}
|
// implícitament. No-op si no hay música activa.
|
||||||
if (current_music->times > 0) current_music->times--;
|
void setMusicSpeed(float ratio);
|
||||||
} else {
|
// Registra un callback que es disparà cuando la música actual acabi de
|
||||||
if (SDL_GetAudioStreamAvailable(current_music->stream) == 0) JA_StopMusic();
|
// drenar naturalment (times == 0 + stream buit). Es crida DESPRÉS de
|
||||||
}
|
// stopMusic, así que el callback pot invocar playMusic sin córrer.
|
||||||
}
|
// S'executa al mismo thread que Engine::update (render loop); no fer
|
||||||
|
// operacions blocants.
|
||||||
if (JA_soundEnabled) {
|
void setOnMusicEnded(std::function<void()> callback);
|
||||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
|
// Notifica al motor que un Music s'está destruint: si es el current_music
|
||||||
if (channels[i].state == JA_CHANNEL_PLAYING) {
|
// s'atura antes que los seus recursos (stream/vorbis) deixin de ser vàlids.
|
||||||
if (channels[i].times != 0) {
|
void onMusicDeleted(const Music* music);
|
||||||
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) {
|
|
||||||
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length);
|
// --- So ---
|
||||||
if (channels[i].times > 0) channels[i].times--;
|
auto playSound(Sound* sound, int loop = 0, int group = 0) -> int;
|
||||||
}
|
auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int;
|
||||||
} else {
|
// Ajusta la velocitat de reproducció d'un canal actiu via
|
||||||
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i);
|
// `SDL_SetAudioStreamFrequencyRatio`. 1.0 = normal. Igual que a
|
||||||
}
|
// `setMusicSpeed`, puja/baixa el to junt con la velocitat
|
||||||
}
|
// (efecte "chipmunk"); para SFX curts arcade es el que volem.
|
||||||
}
|
// No-op si el canal no está actiu. Cridar-lo just después de
|
||||||
}
|
// `playSound`/`playSoundOnChannel` porque el ratio cobreixi
|
||||||
|
// tota la reproducció.
|
||||||
inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) {
|
void setChannelSpeed(int channel, float ratio);
|
||||||
#ifdef _DEBUG
|
// Reproducció con so processat per un efecte. Retorna el canal
|
||||||
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
|
// assignat o -1 si no queden slots d'efecte (MAX_EFFECT_CHANNELS).
|
||||||
#endif
|
// El sound original solo s'usa per consultar el spec/buffer; el
|
||||||
|
// canal manipula el buffer ya processat (no reapunta a `sound`).
|
||||||
JA_audioSpec = {format, num_channels, freq};
|
auto playSoundWithEcho(const Sound* sound, const EchoParams& params, int group = 0) -> int;
|
||||||
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice
|
auto playSoundWithReverb(const Sound* sound, const ReverbParams& params, int group = 0) -> int;
|
||||||
sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
|
void pauseChannel(int channel);
|
||||||
if (sdlAudioDevice == 0) SDL_Log("Failed to initialize SDL audio!");
|
void resumeChannel(int channel);
|
||||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE;
|
void stopChannel(int channel);
|
||||||
for (int i = 0; i < JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5F;
|
auto setSoundVolume(float volume, int group = -1) -> float;
|
||||||
}
|
// Notifica al motor que un Sound s'está destruint: los canals que el
|
||||||
|
// referenciïn es paren antes d'alliberar el buffer.
|
||||||
inline void JA_Quit() {
|
void onSoundDeleted(const Sound* sound);
|
||||||
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); // Corregido: !sdlAudioDevice -> sdlAudioDevice
|
|
||||||
sdlAudioDevice = 0;
|
private:
|
||||||
}
|
void stealCurrentIntoOutgoing(int duration_ms);
|
||||||
|
void updateOutgoingFade();
|
||||||
// --- Music Functions ---
|
void updateIncomingFade();
|
||||||
|
void updateCurrentMusic();
|
||||||
inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
|
void updateSoundChannels();
|
||||||
JA_Music_t* music = new JA_Music_t();
|
// Empenta un buffer ya processat (S16) a un canal lliure y el deixa
|
||||||
|
// sonar sin bucle. Camí comú dels dos overloads playSoundWith*.
|
||||||
int chan, samplerate;
|
// Retorna el canal o -1 si no queden slots.
|
||||||
short* output;
|
auto playProcessedOnFreeChannel(const std::vector<std::uint8_t>& bytes, const SDL_AudioSpec& spec, int group) -> int;
|
||||||
music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;
|
|
||||||
|
template <typename Fn>
|
||||||
music->spec.channels = chan;
|
void forEachTargetChannel(int channel, Fn&& fn);
|
||||||
music->spec.freq = samplerate;
|
|
||||||
music->spec.format = SDL_AUDIO_S16;
|
Music* current_music_{nullptr};
|
||||||
music->buffer = static_cast<Uint8*>(SDL_malloc(music->length));
|
Channel channels_[MAX_SIMULTANEOUS_CHANNELS]{};
|
||||||
SDL_memcpy(music->buffer, output, music->length);
|
SDL_AudioSpec audio_spec_{DEFAULT_SPEC};
|
||||||
free(output);
|
float music_volume_{1.0F};
|
||||||
music->pos = 0;
|
float sound_volume_[MAX_GROUPS]{};
|
||||||
music->state = JA_MUSIC_STOPPED;
|
SDL_AudioDeviceID sdl_audio_device_{0};
|
||||||
|
OutgoingMusic outgoing_music_;
|
||||||
return music;
|
FadeState incoming_fade_;
|
||||||
}
|
std::function<void()> on_music_ended_;
|
||||||
|
// Comptador derivat de Channel::has_effect — evita haver-lo de
|
||||||
inline JA_Music_t* JA_LoadMusic(const char* filename) {
|
// recalcular cada vegada que algú demana un play con efecte.
|
||||||
// [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid.
|
int effect_channels_active_{0};
|
||||||
FILE* f = fopen(filename, "rb");
|
|
||||||
if (!f) return NULL; // Añadida comprobación de apertura
|
// NOLINTNEXTLINE(readability-identifier-naming) — convenció projecte: private static con sufix _
|
||||||
fseek(f, 0, SEEK_END);
|
static Engine* active_;
|
||||||
long fsize = ftell(f);
|
};
|
||||||
fseek(f, 0, SEEK_SET);
|
|
||||||
auto* buffer = static_cast<Uint8*>(malloc(fsize + 1));
|
// --- Factories y destructors (permanents) ---
|
||||||
if (!buffer) { // Añadida comprobación de malloc
|
// No depenen de l'estat del motor: loadMusic/loadSound solo construeixen
|
||||||
fclose(f);
|
// objectes, deleteMusic/deleteSound consulten Engine::active() per parar
|
||||||
return NULL;
|
// canals antes d'alliberar (si el motor aún viu).
|
||||||
}
|
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length) -> Music*;
|
||||||
if (fread(buffer, fsize, 1, f) != 1) {
|
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length, const char* filename) -> Music*;
|
||||||
fclose(f);
|
void deleteMusic(Music* music);
|
||||||
free(buffer);
|
[[nodiscard]] auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound*;
|
||||||
return NULL;
|
void deleteSound(Sound* sound);
|
||||||
}
|
|
||||||
fclose(f);
|
} // namespace Ja
|
||||||
|
|
||||||
JA_Music_t* music = JA_LoadMusic(buffer, fsize);
|
|
||||||
if (music) { // Comprobar que JA_LoadMusic tuvo éxito
|
|
||||||
music->filename = static_cast<char*>(malloc(strlen(filename) + 1));
|
|
||||||
if (music->filename) {
|
|
||||||
strcpy(music->filename, filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(buffer);
|
|
||||||
|
|
||||||
return music;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
|
|
||||||
if (!JA_musicEnabled || !music) return; // Añadida comprobación de music
|
|
||||||
|
|
||||||
JA_StopMusic();
|
|
||||||
|
|
||||||
current_music = music;
|
|
||||||
current_music->pos = 0;
|
|
||||||
current_music->state = JA_MUSIC_PLAYING;
|
|
||||||
current_music->times = loop;
|
|
||||||
|
|
||||||
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
|
||||||
if (!current_music->stream) { // Comprobar creación de stream
|
|
||||||
SDL_Log("Failed to create audio stream!");
|
|
||||||
current_music->state = JA_MUSIC_STOPPED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length)) printf("[ERROR] SDL_PutAudioStreamData failed!\n");
|
|
||||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
|
||||||
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
inline char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) {
|
|
||||||
if (!music) music = current_music;
|
|
||||||
if (!music) return nullptr; // Añadida comprobación
|
|
||||||
return music->filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_PauseMusic() {
|
|
||||||
if (!JA_musicEnabled) return;
|
|
||||||
if (!current_music || current_music->state != JA_MUSIC_PLAYING) return; // Comprobación mejorada
|
|
||||||
|
|
||||||
current_music->state = JA_MUSIC_PAUSED;
|
|
||||||
SDL_UnbindAudioStream(current_music->stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_ResumeMusic() {
|
|
||||||
if (!JA_musicEnabled) return;
|
|
||||||
if (!current_music || current_music->state != JA_MUSIC_PAUSED) return; // Comprobación mejorada
|
|
||||||
|
|
||||||
current_music->state = JA_MUSIC_PLAYING;
|
|
||||||
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_StopMusic() {
|
|
||||||
if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return;
|
|
||||||
|
|
||||||
current_music->pos = 0;
|
|
||||||
current_music->state = JA_MUSIC_STOPPED;
|
|
||||||
if (current_music->stream) {
|
|
||||||
SDL_DestroyAudioStream(current_music->stream);
|
|
||||||
current_music->stream = nullptr;
|
|
||||||
}
|
|
||||||
// No liberamos filename aquí, se debería liberar en JA_DeleteMusic
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_FadeOutMusic(const int milliseconds) {
|
|
||||||
if (!JA_musicEnabled) return;
|
|
||||||
if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return;
|
|
||||||
|
|
||||||
fading = true;
|
|
||||||
fade_start_time = SDL_GetTicks();
|
|
||||||
fade_duration = milliseconds;
|
|
||||||
fade_initial_volume = JA_musicVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline JA_Music_state JA_GetMusicState() {
|
|
||||||
if (!JA_musicEnabled) return JA_MUSIC_DISABLED;
|
|
||||||
if (!current_music) return JA_MUSIC_INVALID;
|
|
||||||
|
|
||||||
return current_music->state;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_DeleteMusic(JA_Music_t* music) {
|
|
||||||
if (!music) return;
|
|
||||||
if (current_music == music) {
|
|
||||||
JA_StopMusic();
|
|
||||||
current_music = nullptr;
|
|
||||||
}
|
|
||||||
SDL_free(music->buffer);
|
|
||||||
if (music->stream) SDL_DestroyAudioStream(music->stream);
|
|
||||||
free(music->filename); // filename se libera aquí
|
|
||||||
delete music;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float JA_SetMusicVolume(float volume) {
|
|
||||||
JA_musicVolume = SDL_clamp(volume, 0.0F, 1.0F);
|
|
||||||
if (current_music && current_music->stream) {
|
|
||||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
|
||||||
}
|
|
||||||
return JA_musicVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_SetMusicPosition(float value) {
|
|
||||||
if (!current_music) return;
|
|
||||||
current_music->pos = value * current_music->spec.freq;
|
|
||||||
// Nota: Esta implementación de 'pos' no parece usarse en JA_Update para
|
|
||||||
// el streaming. El streaming siempre parece empezar desde el principio.
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float JA_GetMusicPosition() {
|
|
||||||
if (!current_music) return 0;
|
|
||||||
return float(current_music->pos) / float(current_music->spec.freq);
|
|
||||||
// Nota: Ver `JA_SetMusicPosition`
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_EnableMusic(const bool value) {
|
|
||||||
if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic();
|
|
||||||
|
|
||||||
JA_musicEnabled = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Sound Functions ---
|
|
||||||
|
|
||||||
inline JA_Sound_t* JA_NewSound(Uint8* buffer, Uint32 length) {
|
|
||||||
JA_Sound_t* sound = new JA_Sound_t();
|
|
||||||
sound->buffer = buffer;
|
|
||||||
sound->length = length;
|
|
||||||
// Nota: spec se queda con los valores por defecto.
|
|
||||||
return sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) {
|
|
||||||
JA_Sound_t* sound = new JA_Sound_t();
|
|
||||||
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &sound->buffer, &sound->length)) {
|
|
||||||
SDL_Log("Failed to load WAV from memory: %s", SDL_GetError());
|
|
||||||
delete sound;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline JA_Sound_t* JA_LoadSound(const char* filename) {
|
|
||||||
JA_Sound_t* sound = new JA_Sound_t();
|
|
||||||
if (!SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length)) {
|
|
||||||
SDL_Log("Failed to load WAV file: %s", SDL_GetError());
|
|
||||||
delete sound;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) {
|
|
||||||
if (!JA_soundEnabled || !sound) return -1;
|
|
||||||
|
|
||||||
int channel = 0;
|
|
||||||
while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; }
|
|
||||||
if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) {
|
|
||||||
// No hay canal libre, reemplazamos el primero
|
|
||||||
channel = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return JA_PlaySoundOnChannel(sound, channel, loop, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop, const int group) {
|
|
||||||
if (!JA_soundEnabled || !sound) return -1;
|
|
||||||
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1;
|
|
||||||
|
|
||||||
JA_StopChannel(channel); // Detiene y limpia el canal si estaba en uso
|
|
||||||
|
|
||||||
channels[channel].sound = sound;
|
|
||||||
channels[channel].times = loop;
|
|
||||||
channels[channel].pos = 0;
|
|
||||||
channels[channel].group = group; // Asignar grupo
|
|
||||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
|
||||||
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
|
|
||||||
|
|
||||||
if (!channels[channel].stream) {
|
|
||||||
SDL_Log("Failed to create audio stream for sound!");
|
|
||||||
channels[channel].state = JA_CHANNEL_FREE;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
|
|
||||||
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
|
|
||||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
|
||||||
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_DeleteSound(JA_Sound_t* sound) {
|
|
||||||
if (!sound) return;
|
|
||||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
|
||||||
if (channels[i].sound == sound) JA_StopChannel(i);
|
|
||||||
}
|
|
||||||
SDL_free(sound->buffer);
|
|
||||||
delete sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_PauseChannel(const int channel) {
|
|
||||||
if (!JA_soundEnabled) return;
|
|
||||||
|
|
||||||
if (channel == -1) {
|
|
||||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
|
||||||
if (channels[i].state == JA_CHANNEL_PLAYING) {
|
|
||||||
channels[i].state = JA_CHANNEL_PAUSED;
|
|
||||||
SDL_UnbindAudioStream(channels[i].stream);
|
|
||||||
}
|
|
||||||
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
|
||||||
if (channels[channel].state == JA_CHANNEL_PLAYING) {
|
|
||||||
channels[channel].state = JA_CHANNEL_PAUSED;
|
|
||||||
SDL_UnbindAudioStream(channels[channel].stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_ResumeChannel(const int channel) {
|
|
||||||
if (!JA_soundEnabled) return;
|
|
||||||
|
|
||||||
if (channel == -1) {
|
|
||||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
|
||||||
if (channels[i].state == JA_CHANNEL_PAUSED) {
|
|
||||||
channels[i].state = JA_CHANNEL_PLAYING;
|
|
||||||
SDL_BindAudioStream(sdlAudioDevice, channels[i].stream);
|
|
||||||
}
|
|
||||||
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
|
||||||
if (channels[channel].state == JA_CHANNEL_PAUSED) {
|
|
||||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
|
||||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_StopChannel(const int channel) {
|
|
||||||
if (channel == -1) {
|
|
||||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
|
||||||
if (channels[i].state != JA_CHANNEL_FREE) {
|
|
||||||
if (channels[i].stream) SDL_DestroyAudioStream(channels[i].stream);
|
|
||||||
channels[i].stream = nullptr;
|
|
||||||
channels[i].state = JA_CHANNEL_FREE;
|
|
||||||
channels[i].pos = 0;
|
|
||||||
channels[i].sound = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
|
||||||
if (channels[channel].state != JA_CHANNEL_FREE) {
|
|
||||||
if (channels[channel].stream) SDL_DestroyAudioStream(channels[channel].stream);
|
|
||||||
channels[channel].stream = nullptr;
|
|
||||||
channels[channel].state = JA_CHANNEL_FREE;
|
|
||||||
channels[channel].pos = 0;
|
|
||||||
channels[channel].sound = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline JA_Channel_state JA_GetChannelState(const int channel) {
|
|
||||||
if (!JA_soundEnabled) return JA_SOUND_DISABLED;
|
|
||||||
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID;
|
|
||||||
|
|
||||||
return channels[channel].state;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float JA_SetSoundVolume(float volume, const int group = -1) // -1 para todos los grupos
|
|
||||||
{
|
|
||||||
const float v = SDL_clamp(volume, 0.0F, 1.0F);
|
|
||||||
|
|
||||||
if (group == -1) {
|
|
||||||
for (int i = 0; i < JA_MAX_GROUPS; ++i) {
|
|
||||||
JA_soundVolume[i] = v;
|
|
||||||
}
|
|
||||||
} else if (group >= 0 && group < JA_MAX_GROUPS) {
|
|
||||||
JA_soundVolume[group] = v;
|
|
||||||
} else {
|
|
||||||
return v; // Grupo inválido
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aplicar volumen a canales activos
|
|
||||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
|
||||||
if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) {
|
|
||||||
if (group == -1 || channels[i].group == group) {
|
|
||||||
if (channels[i].stream) {
|
|
||||||
SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume[channels[i].group]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void JA_EnableSound(const bool value) {
|
|
||||||
if (!value) {
|
|
||||||
JA_StopChannel(-1); // Detener todos los canales
|
|
||||||
}
|
|
||||||
JA_soundEnabled = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float JA_SetVolume(float volume) {
|
|
||||||
float v = JA_SetMusicVolume(volume);
|
|
||||||
JA_SetSoundVolume(v, -1); // Aplicar a todos los grupos de sonido
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#include "core/audio/sound_effects_config.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/resources/resource_helper.hpp"
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Lector de camp con fallback: deixa el destí intacte si la clau no
|
||||||
|
// existeix (los defaults dels Ja::*Params s'inicialitzen al ctor del
|
||||||
|
// struct, así que el comportament es "preset parcial = preset complet
|
||||||
|
// con defaults per als camps que falten").
|
||||||
|
template <typename T>
|
||||||
|
void readField(const fkyaml::node& node, const char* key, T& dst) {
|
||||||
|
if (node.contains(key)) { dst = node[key].get_value<T>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto SoundEffectsConfig::get() -> SoundEffectsConfig& {
|
||||||
|
static SoundEffectsConfig instance_;
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundEffectsConfig::load(const std::string& file_path) {
|
||||||
|
auto bytes = Resource::Helper::loadFile(file_path);
|
||||||
|
if (bytes.empty()) {
|
||||||
|
std::cerr << "[SoundEffectsConfig] no se ha podido abrir " << file_path
|
||||||
|
<< " — sin presets de efecto disponibles\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||||
|
const auto* end = begin + bytes.size();
|
||||||
|
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||||
|
|
||||||
|
if (yaml.contains("echo") && yaml["echo"].is_mapping()) {
|
||||||
|
for (auto it = yaml["echo"].begin(); it != yaml["echo"].end(); ++it) {
|
||||||
|
const auto NAME = it.key().get_value<std::string>();
|
||||||
|
const auto& node = it.value();
|
||||||
|
Ja::EchoParams params{};
|
||||||
|
readField(node, "delay_ms", params.delay_ms);
|
||||||
|
readField(node, "feedback", params.feedback);
|
||||||
|
readField(node, "wet", params.wet);
|
||||||
|
echoes_[NAME] = params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yaml.contains("reverb") && yaml["reverb"].is_mapping()) {
|
||||||
|
for (auto it = yaml["reverb"].begin(); it != yaml["reverb"].end(); ++it) {
|
||||||
|
const auto NAME = it.key().get_value<std::string>();
|
||||||
|
const auto& node = it.value();
|
||||||
|
Ja::ReverbParams params{};
|
||||||
|
readField(node, "room_size", params.room_size);
|
||||||
|
readField(node, "damping", params.damping);
|
||||||
|
readField(node, "wet", params.wet);
|
||||||
|
reverbs_[NAME] = params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[SoundEffectsConfig] " << echoes_.size() << " preset(s) de echo y "
|
||||||
|
<< reverbs_.size() << " de reverb desde " << file_path << "\n";
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[SoundEffectsConfig] error parseando " << file_path << ": " << e.what() << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SoundEffectsConfig::findEcho(const std::string& name) const -> const Ja::EchoParams* {
|
||||||
|
const auto IT = echoes_.find(name);
|
||||||
|
return (IT == echoes_.end()) ? nullptr : &IT->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SoundEffectsConfig::findReverb(const std::string& name) const -> const Ja::ReverbParams* {
|
||||||
|
const auto IT = reverbs_.find(name);
|
||||||
|
return (IT == reverbs_.end()) ? nullptr : &IT->second;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "core/audio/jail_audio.hpp" // Para Ja::EchoParams / Ja::ReverbParams
|
||||||
|
|
||||||
|
// Catàleg de presets d'efectes carregat des de data/config/sounds.yaml. La capa
|
||||||
|
// Audio (playSoundWithEcho/playSoundWithReverb) hi accedeix per nom: si el
|
||||||
|
// preset no existeix, el so es reprodueix sec con un avís a stderr.
|
||||||
|
//
|
||||||
|
// Patró Meyers idèntic a UiConfig/Locale: un sol load() a l'arrencada, sense
|
||||||
|
// hot-reload. Si el archivo no existeix, el catàleg queda buit (sin preset
|
||||||
|
// disponible) i tots los playSoundWith* es comporten como playSound dry.
|
||||||
|
class SoundEffectsConfig {
|
||||||
|
public:
|
||||||
|
static auto get() -> SoundEffectsConfig&;
|
||||||
|
|
||||||
|
SoundEffectsConfig(const SoundEffectsConfig&) = delete;
|
||||||
|
SoundEffectsConfig(SoundEffectsConfig&&) = delete;
|
||||||
|
auto operator=(const SoundEffectsConfig&) -> SoundEffectsConfig& = delete;
|
||||||
|
auto operator=(SoundEffectsConfig&&) -> SoundEffectsConfig& = delete;
|
||||||
|
|
||||||
|
void load(const std::string& file_path);
|
||||||
|
|
||||||
|
// Retorna nullptr si el preset no existeix.
|
||||||
|
[[nodiscard]] auto findEcho(const std::string& name) const -> const Ja::EchoParams*;
|
||||||
|
[[nodiscard]] auto findReverb(const std::string& name) const -> const Ja::ReverbParams*;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SoundEffectsConfig() = default;
|
||||||
|
~SoundEffectsConfig() = default;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Ja::EchoParams> echoes_;
|
||||||
|
std::unordered_map<std::string, Ja::ReverbParams> reverbs_;
|
||||||
|
};
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
// postfx_config.cpp - Implementación del cargador de YAML del postpro.
|
||||||
|
|
||||||
|
#include "core/config/postfx_config.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/resources/resource_helper.hpp"
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
|
||||||
|
namespace Config::PostFx {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Helper: lee `key` en `node` solo si existe; deja `dst` intacto en caso
|
||||||
|
// contrario. Así, un YAML parcial sigue funcionando con los defaults del
|
||||||
|
// struct para los campos que falten.
|
||||||
|
template <typename T>
|
||||||
|
void readField(const fkyaml::node& node, const char* key, T& dst) {
|
||||||
|
if (node.contains(key)) {
|
||||||
|
dst = node[key].get_value<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lee un array RGB [r, g, b] (0..255) y lo normaliza a [0..1] sobre tres
|
||||||
|
// destinos floats. Si la clave no existe o no es secuencia de 3, deja los
|
||||||
|
// destinos como están.
|
||||||
|
void readRgb255(const fkyaml::node& node, const char* key,
|
||||||
|
float& dst_r, float& dst_g, float& dst_b) {
|
||||||
|
if (!node.contains(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& arr = node[key];
|
||||||
|
if (!arr.is_sequence() || arr.size() < 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const auto R = arr[0].get_value<int>();
|
||||||
|
const auto G = arr[1].get_value<int>();
|
||||||
|
const auto B = arr[2].get_value<int>();
|
||||||
|
dst_r = static_cast<float>(R) / 255.0F;
|
||||||
|
dst_g = static_cast<float>(G) / 255.0F;
|
||||||
|
dst_b = static_cast<float>(B) / 255.0F;
|
||||||
|
} catch (...) {
|
||||||
|
// Mantiene los defaults si algún elemento no es entero.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto load(const std::string& path) -> Rendering::GPU::PostFxParams {
|
||||||
|
Rendering::GPU::PostFxParams params{}; // valores por defecto del struct
|
||||||
|
|
||||||
|
auto bytes = Resource::Helper::loadFile(path);
|
||||||
|
if (bytes.empty()) {
|
||||||
|
std::cerr << "[PostFxConfig] No se pudo cargar " << path
|
||||||
|
<< " — usando defaults built-in\n";
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||||
|
const auto* end = begin + bytes.size();
|
||||||
|
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||||
|
|
||||||
|
if (yaml.contains("bloom") && yaml["bloom"].is_mapping()) {
|
||||||
|
const auto& node = yaml["bloom"];
|
||||||
|
readField(node, "enabled", params.bloom_enabled);
|
||||||
|
readField(node, "intensity", params.bloom_intensity);
|
||||||
|
readField(node, "threshold", params.bloom_threshold);
|
||||||
|
readField(node, "radius_px", params.bloom_radius_px);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yaml.contains("flicker") && yaml["flicker"].is_mapping()) {
|
||||||
|
const auto& node = yaml["flicker"];
|
||||||
|
readField(node, "enabled", params.flicker_enabled);
|
||||||
|
readField(node, "amplitude", params.flicker_amplitude);
|
||||||
|
readField(node, "frequency_hz", params.flicker_frequency_hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yaml.contains("background") && yaml["background"].is_mapping()) {
|
||||||
|
const auto& node = yaml["background"];
|
||||||
|
readField(node, "enabled", params.background_enabled);
|
||||||
|
readRgb255(node, "color_min",
|
||||||
|
params.background_min_r,
|
||||||
|
params.background_min_g,
|
||||||
|
params.background_min_b);
|
||||||
|
readRgb255(node, "color_max",
|
||||||
|
params.background_max_r,
|
||||||
|
params.background_max_g,
|
||||||
|
params.background_max_b);
|
||||||
|
readField(node, "pulse_frequency_hz", params.background_pulse_freq_hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[PostFxConfig] Cargado " << path
|
||||||
|
<< " (bloom=" << (params.bloom_enabled ? "on" : "off")
|
||||||
|
<< " intensity=" << params.bloom_intensity
|
||||||
|
<< ", flicker=" << (params.flicker_enabled ? "on" : "off")
|
||||||
|
<< " amp=" << params.flicker_amplitude
|
||||||
|
<< ", bg=" << (params.background_enabled ? "on" : "off")
|
||||||
|
<< ")\n";
|
||||||
|
} catch (const fkyaml::exception& e) {
|
||||||
|
std::cerr << "[PostFxConfig] Error parseando " << path << ": " << e.what()
|
||||||
|
<< " — usando defaults built-in\n";
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Config::PostFx
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// postfx_config.hpp - Carga de los parámetros del shader de postpro desde YAML.
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Lee `config/postfx.yaml` (dentro de resources.pack) y devuelve un struct
|
||||||
|
// PostFxParams listo para pasar a GpuFrameRenderer::setPostFx(). Si el YAML
|
||||||
|
// no existe o falla el parser, retorna los defaults built-in.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_frame_renderer.hpp"
|
||||||
|
|
||||||
|
namespace Config::PostFx {
|
||||||
|
|
||||||
|
// Carga desde el resource pack. Path relativo dentro del pack (p.ej.
|
||||||
|
// "config/postfx.yaml"). Si falla, devuelve un PostFxParams construido por
|
||||||
|
// defecto (valores embebidos en el struct).
|
||||||
|
[[nodiscard]] auto load(const std::string& path) -> Rendering::GPU::PostFxParams;
|
||||||
|
|
||||||
|
} // namespace Config::PostFx
|
||||||
+121
-126
@@ -8,38 +8,38 @@
|
|||||||
namespace Defaults {
|
namespace Defaults {
|
||||||
// Configuración de ventana
|
// Configuración de ventana
|
||||||
namespace Window {
|
namespace Window {
|
||||||
constexpr int WIDTH = 640;
|
constexpr int WIDTH = 1280;
|
||||||
constexpr int HEIGHT = 480;
|
constexpr int HEIGHT = 720;
|
||||||
constexpr int MIN_WIDTH = 320; // Mínimo: mitad del original
|
constexpr int MIN_WIDTH = 640; // Mínimo: mitad del baseline (16:9)
|
||||||
constexpr int MIN_HEIGHT = 240;
|
constexpr int MIN_HEIGHT = 360;
|
||||||
// Zoom system
|
// Zoom system
|
||||||
constexpr float BASE_ZOOM = 1.0F; // 640x480 baseline
|
constexpr float BASE_ZOOM = 1.0F; // 1280x720 baseline (16:9)
|
||||||
constexpr float MIN_ZOOM = 0.5F; // 320x240 minimum
|
constexpr float MIN_ZOOM = 0.5F; // 640x360 minimum
|
||||||
constexpr float ZOOM_INCREMENT = 0.1F; // 10% steps (F1/F2)
|
constexpr float ZOOM_INCREMENT = 0.1F; // 10% steps (F1/F2)
|
||||||
constexpr bool FULLSCREEN = true; // Pantalla completa activadapor defecto
|
constexpr bool FULLSCREEN = true; // Pantalla completa activada por defecto
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
||||||
// Dimensions base del joc (coordenades lògiques)
|
// Dimensiones base del juego (coordenadas lógicas, 16:9)
|
||||||
namespace Game {
|
namespace Game {
|
||||||
constexpr int WIDTH = 640;
|
constexpr int WIDTH = 1280;
|
||||||
constexpr int HEIGHT = 480;
|
constexpr int HEIGHT = 720;
|
||||||
} // namespace Game
|
} // namespace Game
|
||||||
|
|
||||||
// Zones del joc (SDL_FRect amb càlculs automàtics basat en percentatges)
|
// Zones del juego (SDL_FRect con cálculos automáticos basat en porcentajes)
|
||||||
namespace Zones {
|
namespace Zones {
|
||||||
// --- CONFIGURACIÓ DE PORCENTATGES ---
|
// --- CONFIGURACIÓ DE PORCENTATGES ---
|
||||||
// Totes les zones definides com a percentatges de Game::WIDTH (640) i Game::HEIGHT (480)
|
// Todas las zones definides como a porcentajes de Game::WIDTH (640) i Game::HEIGHT (480)
|
||||||
|
|
||||||
// Percentatges d'alçada (divisió vertical)
|
// Percentatges de height (divisió vertical)
|
||||||
constexpr float SCOREBOARD_TOP_HEIGHT_PERCENT = 0.02F; // 10% superior
|
constexpr float SCOREBOARD_TOP_HEIGHT_PERCENT = 0.02F; // 10% superior
|
||||||
constexpr float MAIN_PLAYAREA_HEIGHT_PERCENT = 0.88F; // 80% central
|
constexpr float MAIN_PLAYAREA_HEIGHT_PERCENT = 0.88F; // 80% central
|
||||||
constexpr float SCOREBOARD_BOTTOM_HEIGHT_PERCENT = 0.10F; // 10% inferior
|
constexpr float SCOREBOARD_BOTTOM_HEIGHT_PERCENT = 0.10F; // 10% inferior
|
||||||
|
|
||||||
// Padding horizontal per a PLAYAREA (dins de MAIN_PLAYAREA)
|
// Padding horizontal para PLAYAREA (dentro de MAIN_PLAYAREA)
|
||||||
constexpr float PLAYAREA_PADDING_HORIZONTAL_PERCENT = 0.015F; // 5% a cada costat
|
constexpr float PLAYAREA_PADDING_HORIZONTAL_PERCENT = 0.015F; // 5% a cada costat
|
||||||
|
|
||||||
// --- CÀLCULS AUTOMÀTICS DE PÍXELS ---
|
// --- CÀLCULS AUTOMÀTICS DE PÍXELS ---
|
||||||
// Càlculs automàtics a partir dels percentatges
|
// Cálculos automáticos a partir dels porcentajes
|
||||||
|
|
||||||
// Alçades
|
// Alçades
|
||||||
constexpr float SCOREBOARD_TOP_H = Game::HEIGHT * SCOREBOARD_TOP_HEIGHT_PERCENT;
|
constexpr float SCOREBOARD_TOP_H = Game::HEIGHT * SCOREBOARD_TOP_HEIGHT_PERCENT;
|
||||||
@@ -56,44 +56,44 @@ constexpr float PLAYAREA_PADDING_H = Game::WIDTH * PLAYAREA_PADDING_HORIZONTAL_P
|
|||||||
|
|
||||||
// --- ZONES FINALS (SDL_FRect) ---
|
// --- ZONES FINALS (SDL_FRect) ---
|
||||||
|
|
||||||
// Marcador superior (reservat per a futur ús)
|
// Marcador superior (reservado para futuro uso)
|
||||||
// Ocupa: 10% superior (0-48px)
|
// Ocupa el 2% superior
|
||||||
constexpr SDL_FRect SCOREBOARD_TOP = {
|
constexpr SDL_FRect SCOREBOARD_TOP = {
|
||||||
0.0F, // x = 0.0
|
0.0F, // x = 0.0
|
||||||
SCOREBOARD_TOP_Y, // y = 0.0
|
SCOREBOARD_TOP_Y, // y = 0.0
|
||||||
static_cast<float>(Game::WIDTH), // w = 640.0
|
static_cast<float>(Game::WIDTH), // ancho completo
|
||||||
SCOREBOARD_TOP_H // h = 48.0
|
SCOREBOARD_TOP_H // alto
|
||||||
};
|
};
|
||||||
|
|
||||||
// Àrea de joc principal (contenidor del 80% central, sense padding)
|
// Área de juego principal (contenedor del 80% central, sin padding)
|
||||||
// Ocupa: 10-90% (48-432px), ample complet
|
// Ocupa el 88% central, ancho completo
|
||||||
constexpr SDL_FRect MAIN_PLAYAREA = {
|
constexpr SDL_FRect MAIN_PLAYAREA = {
|
||||||
0.0F, // x = 0.0
|
0.0F, // x = 0.0
|
||||||
MAIN_PLAYAREA_Y, // y = 48.0
|
MAIN_PLAYAREA_Y, // debajo del scoreboard superior
|
||||||
static_cast<float>(Game::WIDTH), // w = 640.0
|
static_cast<float>(Game::WIDTH), // ancho completo
|
||||||
MAIN_PLAYAREA_H // h = 384.0
|
MAIN_PLAYAREA_H // alto
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zona de joc real (amb padding horizontal del 5%)
|
// Zona de juego real (con padding horizontal del 5%)
|
||||||
// Ocupa: dins de MAIN_PLAYAREA, amb marges laterals
|
// Ocupa: dentro de MAIN_PLAYAREA, con márgenes laterales
|
||||||
// S'utilitza per a límits del joc, col·lisions, spawn
|
// Se utiliza para límites del juego, colisiones, spawn
|
||||||
constexpr SDL_FRect PLAYAREA = {
|
constexpr SDL_FRect PLAYAREA = {
|
||||||
PLAYAREA_PADDING_H, // x = 32.0
|
PLAYAREA_PADDING_H, // padding horizontal
|
||||||
MAIN_PLAYAREA_Y, // y = 48.0 (igual que MAIN_PLAYAREA)
|
MAIN_PLAYAREA_Y, // debajo del scoreboard superior (igual que MAIN_PLAYAREA)
|
||||||
Game::WIDTH - (2.0F * PLAYAREA_PADDING_H), // w = 576.0
|
Game::WIDTH - (2.0F * PLAYAREA_PADDING_H), // ancho con padding
|
||||||
MAIN_PLAYAREA_H // h = 384.0 (igual que MAIN_PLAYAREA)
|
MAIN_PLAYAREA_H // alto (igual que MAIN_PLAYAREA)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Marcador inferior (marcador actual)
|
// Marcador inferior (marcador actual)
|
||||||
// Ocupa: 10% inferior (432-480px)
|
// Ocupa el 10% inferior
|
||||||
constexpr SDL_FRect SCOREBOARD = {
|
constexpr SDL_FRect SCOREBOARD = {
|
||||||
0.0F, // x = 0.0
|
0.0F, // x = 0.0
|
||||||
SCOREBOARD_BOTTOM_Y, // y = 432.0
|
SCOREBOARD_BOTTOM_Y, // fondo
|
||||||
static_cast<float>(Game::WIDTH), // w = 640.0
|
static_cast<float>(Game::WIDTH), // ancho completo
|
||||||
SCOREBOARD_BOTTOM_H // h = 48.0
|
SCOREBOARD_BOTTOM_H // alto
|
||||||
};
|
};
|
||||||
|
|
||||||
// Padding horizontal del marcador (per alinear zones esquerra/dreta amb PLAYAREA)
|
// Padding horizontal del marcador (para alinear zonas izquierda/derecha con PLAYAREA)
|
||||||
constexpr float SCOREBOARD_PADDING_H = 0.0F; // Game::WIDTH * 0.015f;
|
constexpr float SCOREBOARD_PADDING_H = 0.0F; // Game::WIDTH * 0.015f;
|
||||||
} // namespace Zones
|
} // namespace Zones
|
||||||
|
|
||||||
@@ -101,14 +101,24 @@ constexpr float SCOREBOARD_PADDING_H = 0.0F; // Game::WIDTH * 0.015f;
|
|||||||
namespace Entities {
|
namespace Entities {
|
||||||
constexpr int MAX_ORNIS = 15;
|
constexpr int MAX_ORNIS = 15;
|
||||||
constexpr int MAX_BALES = 3;
|
constexpr int MAX_BALES = 3;
|
||||||
constexpr int MAX_IPUNTS = 30;
|
|
||||||
|
|
||||||
constexpr float SHIP_RADIUS = 12.0F;
|
constexpr float SHIP_RADIUS = 12.0F;
|
||||||
constexpr float ENEMY_RADIUS = 20.0F;
|
constexpr float ENEMY_RADIUS = 20.0F;
|
||||||
constexpr float BULLET_RADIUS = 3.0F;
|
constexpr float BULLET_RADIUS = 3.0F;
|
||||||
} // namespace Entities
|
} // namespace Entities
|
||||||
|
|
||||||
// Ship (nave del jugador)
|
// Paleta semántica por tipo de entidad. Si una entity declara color, lo
|
||||||
|
// pasa al pipeline con alpha=255 (sentinela "color válido"); si no, se
|
||||||
|
// usa el color global del oscilador (g_current_line_color).
|
||||||
|
namespace Palette {
|
||||||
|
constexpr SDL_Color SHIP = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanco neutro
|
||||||
|
constexpr SDL_Color BULLET = {.r = 120, .g = 255, .b = 140, .a = 255}; // Verde laser
|
||||||
|
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
|
||||||
|
} // namespace Palette
|
||||||
|
|
||||||
|
// Ship (nave del player)
|
||||||
namespace Ship {
|
namespace Ship {
|
||||||
// Invulnerabilidad post-respawn
|
// Invulnerabilidad post-respawn
|
||||||
constexpr float INVULNERABILITY_DURATION = 3.0F; // Segundos de invulnerabilidad
|
constexpr float INVULNERABILITY_DURATION = 3.0F; // Segundos de invulnerabilidad
|
||||||
@@ -144,7 +154,7 @@ constexpr float LEVEL_COMPLETED_TYPING_RATIO = 0.0F; // 0.0 = sin typewriter (d
|
|||||||
constexpr float INIT_HUD_DURATION = 3.0F; // Duración total del estado
|
constexpr float INIT_HUD_DURATION = 3.0F; // Duración total del estado
|
||||||
|
|
||||||
// Ratios de animación (inicio y fin como porcentajes del tiempo total)
|
// Ratios de animación (inicio y fin como porcentajes del tiempo total)
|
||||||
// RECT (rectángulo de marges)
|
// RECT (rectángulo de márgenes)
|
||||||
constexpr float INIT_HUD_RECT_RATIO_INIT = 0.30F;
|
constexpr float INIT_HUD_RECT_RATIO_INIT = 0.30F;
|
||||||
constexpr float INIT_HUD_RECT_RATIO_END = 0.85F;
|
constexpr float INIT_HUD_RECT_RATIO_END = 0.85F;
|
||||||
|
|
||||||
@@ -152,11 +162,11 @@ constexpr float INIT_HUD_RECT_RATIO_END = 0.85F;
|
|||||||
constexpr float INIT_HUD_SCORE_RATIO_INIT = 0.60F;
|
constexpr float INIT_HUD_SCORE_RATIO_INIT = 0.60F;
|
||||||
constexpr float INIT_HUD_SCORE_RATIO_END = 0.90F;
|
constexpr float INIT_HUD_SCORE_RATIO_END = 0.90F;
|
||||||
|
|
||||||
// SHIP1 (nave jugador 1)
|
// SHIP1 (nave player 1)
|
||||||
constexpr float INIT_HUD_SHIP1_RATIO_INIT = 0.0F;
|
constexpr float INIT_HUD_SHIP1_RATIO_INIT = 0.0F;
|
||||||
constexpr float INIT_HUD_SHIP1_RATIO_END = 1.0F;
|
constexpr float INIT_HUD_SHIP1_RATIO_END = 1.0F;
|
||||||
|
|
||||||
// SHIP2 (nave jugador 2)
|
// SHIP2 (nave player 2)
|
||||||
constexpr float INIT_HUD_SHIP2_RATIO_INIT = 0.20F;
|
constexpr float INIT_HUD_SHIP2_RATIO_INIT = 0.20F;
|
||||||
constexpr float INIT_HUD_SHIP2_RATIO_END = 1.0F;
|
constexpr float INIT_HUD_SHIP2_RATIO_END = 1.0F;
|
||||||
|
|
||||||
@@ -212,21 +222,21 @@ constexpr float VELOCITY_SCALE = 20.0F; // factor conversión frame→tiempo
|
|||||||
|
|
||||||
// Explosions (debris physics)
|
// Explosions (debris physics)
|
||||||
namespace Debris {
|
namespace Debris {
|
||||||
constexpr float VELOCITAT_BASE = 80.0F; // Velocitat inicial (px/s)
|
constexpr float VELOCITAT_BASE = 80.0F; // Velocidad inicial (px/s)
|
||||||
constexpr float VARIACIO_VELOCITAT = 40.0F; // ±variació aleatòria (px/s)
|
constexpr float VARIACIO_VELOCITAT = 40.0F; // ±variació aleatòria (px/s)
|
||||||
constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²)
|
constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²)
|
||||||
constexpr float ROTACIO_MIN = 0.1F; // Rotació mínima (rad/s ~5.7°/s)
|
constexpr float ROTACIO_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s)
|
||||||
constexpr float ROTACIO_MAX = 0.3F; // Rotació màxima (rad/s ~17.2°/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 = 2.0F; // Duració màxima (segons) - enemy/bullet debris
|
||||||
constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris lifetime (matches DEATH_DURATION)
|
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 SHRINK_RATE = 0.5F; // Reducció de mida (factor/s)
|
||||||
|
|
||||||
// Herència de velocitat angular (trayectorias curvas)
|
// Herència de velocity angular (trayectorias curvas)
|
||||||
constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat
|
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 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ó angular (rad/s²)
|
||||||
|
|
||||||
// Angular velocity cap for trajectory inheritance
|
// Angular velocity sin for trajectory inheritance
|
||||||
// Excess above this threshold is converted to tangential linear velocity
|
// Excess above this threshold is converted to tangential linear velocity
|
||||||
// Prevents "vortex trap" problem with high-rotation enemies
|
// Prevents "vortex trap" problem with high-rotation enemies
|
||||||
constexpr float VELOCITAT_ROT_MAX = 1.5F; // rad/s (~86°/s)
|
constexpr float VELOCITAT_ROT_MAX = 1.5F; // rad/s (~86°/s)
|
||||||
@@ -238,59 +248,46 @@ namespace Math {
|
|||||||
constexpr float PI = std::numbers::pi_v<float>;
|
constexpr float PI = std::numbers::pi_v<float>;
|
||||||
} // namespace Math
|
} // namespace Math
|
||||||
|
|
||||||
// Colores (oscilación para efecto CRT)
|
// La antigua oscilación CPU (namespace Color) se ha migrado al shader de
|
||||||
namespace Color {
|
// postpro. Los parámetros de flicker / background pulse viven ahora en
|
||||||
// Frecuencia de oscilación
|
// data/config/postfx.yaml y se aplican en shaders/postfx.frag.glsl.
|
||||||
constexpr float FREQUENCY = 6.0F; // 1 Hz (1 ciclo/segundo)
|
|
||||||
|
|
||||||
// Color de líneas (efecto fósforo verde CRT)
|
// Brillantor (control de intensitat per cada type de entidad)
|
||||||
constexpr uint8_t LINE_MIN_R = 100; // Verde oscuro
|
|
||||||
constexpr uint8_t LINE_MIN_G = 200;
|
|
||||||
constexpr uint8_t LINE_MIN_B = 100;
|
|
||||||
|
|
||||||
constexpr uint8_t LINE_MAX_R = 100; // Verde brillante
|
|
||||||
constexpr uint8_t LINE_MAX_G = 255;
|
|
||||||
constexpr uint8_t LINE_MAX_B = 100;
|
|
||||||
|
|
||||||
// Color de fondo (pulso sutil verde oscuro)
|
|
||||||
constexpr uint8_t BACKGROUND_MIN_R = 0; // Negro
|
|
||||||
constexpr uint8_t BACKGROUND_MIN_G = 5;
|
|
||||||
constexpr uint8_t BACKGROUND_MIN_B = 0;
|
|
||||||
|
|
||||||
constexpr uint8_t BACKGROUND_MAX_R = 0; // Verde muy oscuro
|
|
||||||
constexpr uint8_t BACKGROUND_MAX_G = 15;
|
|
||||||
constexpr uint8_t BACKGROUND_MAX_B = 0;
|
|
||||||
} // namespace Color
|
|
||||||
|
|
||||||
// Brillantor (control de intensitat per cada tipus d'entitat)
|
|
||||||
namespace Brightness {
|
namespace Brightness {
|
||||||
// Brillantor estàtica per entitats de joc (0.0-1.0)
|
// Brillantor estàtica per entidades de juego (0.0-1.0)
|
||||||
constexpr float NAU = 1.0F; // Màxima visibilitat (jugador)
|
constexpr float NAU = 1.0F; // Màxima visibilitat (player)
|
||||||
constexpr float ENEMIC = 0.7F; // 30% més tènue (destaca menys)
|
constexpr float ENEMIC = 0.7F; // 30% més tènue (destaca menys)
|
||||||
constexpr float BALA = 1.0F; // Brillo a tope (màxima visibilitat)
|
constexpr float BALA = 1.0F; // Brillo a tope (màxima visibilitat)
|
||||||
|
|
||||||
// Starfield: gradient segons distància al centre
|
// Starfield: gradient segons distancia al centro
|
||||||
// distancia_centre: 0.0 (centre) → 1.0 (vora pantalla)
|
// distancia_centre: 0.0 (centro) → 1.0 (vora pantalla)
|
||||||
// brightness = MIN + (MAX - MIN) * distancia_centre
|
// brightness = MIN + (MAX - MIN) * distancia_centre
|
||||||
constexpr float STARFIELD_MIN = 0.3F; // Estrelles llunyanes (prop del centre)
|
constexpr float STARFIELD_MIN = 0.3F; // Estrelles llunyanes (prop del centro)
|
||||||
constexpr float STARFIELD_MAX = 0.8F; // Estrelles properes (vora pantalla)
|
constexpr float STARFIELD_MAX = 0.8F; // Estrelles properes (vora pantalla)
|
||||||
} // namespace Brightness
|
} // namespace Brightness
|
||||||
|
|
||||||
// Renderització (V-Sync i altres opcions de render)
|
// Renderització (V-Sync i altres opciones de render)
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
constexpr int VSYNC_DEFAULT = 1; // 0=disabled, 1=enabled
|
constexpr int VSYNC_DEFAULT = 1; // 0=disabled, 1=enabled
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|
||||||
// Audio (sistema de so i música)
|
// Audio (sistema de sonido y música) — usado por Audio::Config en init()
|
||||||
namespace Audio {
|
namespace Audio {
|
||||||
constexpr float VOLUME = 1.0F; // Volumen maestro (0.0 a 1.0)
|
constexpr bool ENABLED = true; // Audio habilitado por defecto
|
||||||
constexpr bool ENABLED = true; // Audio habilitado por defecto
|
constexpr float VOLUME = 1.0F; // Volumen maestro (0..1)
|
||||||
|
constexpr bool MUSIC_ENABLED = true; // Música habilitada
|
||||||
|
constexpr float MUSIC_VOLUME = 0.8F; // Volumen música (0..1)
|
||||||
|
constexpr bool SOUND_ENABLED = true; // Efectos habilitados
|
||||||
|
constexpr float SOUND_VOLUME = 1.0F; // Volumen efectos (0..1)
|
||||||
|
constexpr float VOLUME_STEP = 0.05F; // Paso UI (5%)
|
||||||
|
constexpr int FREQUENCY = 48000; // Frecuencia de muestreo (Hz)
|
||||||
|
constexpr int CROSSFADE_MS = 1500; // Crossfade por defecto entre pistas (ms)
|
||||||
|
constexpr SDL_AudioFormat FORMAT = SDL_AUDIO_S16; // PCM 16-bit signed nativo
|
||||||
|
constexpr int CHANNELS = 2; // Estéreo
|
||||||
} // namespace Audio
|
} // namespace Audio
|
||||||
|
|
||||||
// Música (pistas de fondo)
|
// Música (pistas de fondo)
|
||||||
namespace Music {
|
namespace Music {
|
||||||
constexpr float VOLUME = 0.8F; // Volumen música
|
|
||||||
constexpr bool ENABLED = true; // Música habilitada
|
|
||||||
constexpr const char* GAME_TRACK = "game.ogg"; // Pista de juego
|
constexpr const char* GAME_TRACK = "game.ogg"; // Pista de juego
|
||||||
constexpr const char* TITLE_TRACK = "title.ogg"; // Pista de titulo
|
constexpr const char* TITLE_TRACK = "title.ogg"; // Pista de titulo
|
||||||
constexpr int FADE_DURATION_MS = 1000; // Fade out duration
|
constexpr int FADE_DURATION_MS = 1000; // Fade out duration
|
||||||
@@ -298,8 +295,6 @@ constexpr int FADE_DURATION_MS = 1000; // Fade out duration
|
|||||||
|
|
||||||
// Efectes de so (sons puntuals)
|
// Efectes de so (sons puntuals)
|
||||||
namespace Sound {
|
namespace Sound {
|
||||||
constexpr float VOLUME = 1.0F; // Volumen efectos
|
|
||||||
constexpr bool ENABLED = true; // Sonidos habilitados
|
|
||||||
constexpr const char* CONTINUE = "effects/continue.wav"; // Cuenta atras
|
constexpr const char* CONTINUE = "effects/continue.wav"; // Cuenta atras
|
||||||
constexpr const char* EXPLOSION = "effects/explosion.wav"; // Explosión
|
constexpr const char* EXPLOSION = "effects/explosion.wav"; // Explosión
|
||||||
constexpr const char* EXPLOSION2 = "effects/explosion2.wav"; // Explosión alternativa
|
constexpr const char* EXPLOSION2 = "effects/explosion2.wav"; // Explosión alternativa
|
||||||
@@ -307,7 +302,7 @@ constexpr const char* FRIENDLY_FIRE_HIT = "effects/friendly_fire.wav"; //
|
|||||||
constexpr const char* INIT_HUD = "effects/init_hud.wav"; // Para la animación del HUD
|
constexpr const char* INIT_HUD = "effects/init_hud.wav"; // Para la animación del HUD
|
||||||
constexpr const char* LASER = "effects/laser_shoot.wav"; // Disparo
|
constexpr const char* LASER = "effects/laser_shoot.wav"; // Disparo
|
||||||
constexpr const char* LOGO = "effects/logo.wav"; // Logo
|
constexpr const char* LOGO = "effects/logo.wav"; // Logo
|
||||||
constexpr const char* START = "effects/start.wav"; // El jugador pulsa START
|
constexpr const char* START = "effects/start.wav"; // El player pulsa START
|
||||||
constexpr const char* GOOD_JOB_COMMANDER = "voices/good_job_commander.wav"; // Voz: "Good job, commander"
|
constexpr const char* GOOD_JOB_COMMANDER = "voices/good_job_commander.wav"; // Voz: "Good job, commander"
|
||||||
} // namespace Sound
|
} // namespace Sound
|
||||||
|
|
||||||
@@ -328,7 +323,7 @@ constexpr SDL_Keycode SHOOT = SDLK_LSHIFT;
|
|||||||
} // namespace P2
|
} // namespace P2
|
||||||
} // namespace Controls
|
} // namespace Controls
|
||||||
|
|
||||||
// Enemy type configuration (tipus d'enemics)
|
// Enemy type configuration (type de enemigos)
|
||||||
namespace Enemies {
|
namespace Enemies {
|
||||||
// Pentagon (esquivador - zigzag evasion)
|
// Pentagon (esquivador - zigzag evasion)
|
||||||
namespace Pentagon {
|
namespace Pentagon {
|
||||||
@@ -340,15 +335,15 @@ constexpr float DROTACIO_MAX = 3.75F; // Max visual rotation (rad/s) [+50%]
|
|||||||
constexpr const char* SHAPE_FILE = "enemy_pentagon.shp";
|
constexpr const char* SHAPE_FILE = "enemy_pentagon.shp";
|
||||||
} // namespace Pentagon
|
} // namespace Pentagon
|
||||||
|
|
||||||
// Quadrat (perseguidor - tracks player)
|
// Cuadrado (perseguidor - tracks player)
|
||||||
namespace Quadrat {
|
namespace Cuadrado {
|
||||||
constexpr float VELOCITAT = 40.0F; // px/s (medium speed)
|
constexpr float VELOCITAT = 40.0F; // px/s (medium speed)
|
||||||
constexpr float TRACKING_STRENGTH = 0.5F; // Interpolation toward player (0.0-1.0)
|
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 TRACKING_INTERVAL = 1.0F; // Seconds between angle updates
|
||||||
constexpr float DROTACIO_MIN = 0.3F; // Slow rotation [+50%]
|
constexpr float DROTACIO_MIN = 0.3F; // Slow rotation [+50%]
|
||||||
constexpr float DROTACIO_MAX = 1.5F; // [+50%]
|
constexpr float DROTACIO_MAX = 1.5F; // [+50%]
|
||||||
constexpr const char* SHAPE_FILE = "enemy_square.shp";
|
constexpr const char* SHAPE_FILE = "enemy_square.shp";
|
||||||
} // namespace Quadrat
|
} // namespace Cuadrado
|
||||||
|
|
||||||
// Molinillo (agressiu - fast straight lines, proximity spin-up)
|
// Molinillo (agressiu - fast straight lines, proximity spin-up)
|
||||||
namespace Molinillo {
|
namespace Molinillo {
|
||||||
@@ -396,54 +391,54 @@ constexpr float INVULNERABILITY_SCALE_START = 0.0F; // Invisible
|
|||||||
constexpr float INVULNERABILITY_SCALE_END = 1.0F; // Full size
|
constexpr float INVULNERABILITY_SCALE_END = 1.0F; // Full size
|
||||||
} // namespace Spawn
|
} // namespace Spawn
|
||||||
|
|
||||||
// Scoring system (puntuació per tipus d'enemic)
|
// Scoring system (puntuación per type de enemy)
|
||||||
namespace Scoring {
|
namespace Scoring {
|
||||||
constexpr int PENTAGON_SCORE = 100; // Pentàgon (esquivador, 35 px/s)
|
constexpr int PENTAGON_SCORE = 100; // Pentágono (esquivador, 35 px/s)
|
||||||
constexpr int QUADRAT_SCORE = 150; // Quadrat (perseguidor, 40 px/s)
|
constexpr int QUADRAT_SCORE = 150; // Cuadrado (perseguidor, 40 px/s)
|
||||||
constexpr int MOLINILLO_SCORE = 200; // Molinillo (agressiu, 50 px/s)
|
constexpr int MOLINILLO_SCORE = 200; // Molinillo (agressiu, 50 px/s)
|
||||||
} // namespace Scoring
|
} // namespace Scoring
|
||||||
|
|
||||||
} // namespace Enemies
|
} // namespace Enemies
|
||||||
|
|
||||||
// Title scene ship animations (naus 3D flotants a l'escena de títol)
|
// Title scene ship animations (naves 3D flotantes a l'escena de título)
|
||||||
namespace Title {
|
namespace Title {
|
||||||
namespace Ships {
|
namespace Ships {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// PARÀMETRES BASE (ajustar aquí per experimentar)
|
// PARÀMETRES BASE (ajustar aquí per experimentar)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// 1. Escala global de les naus
|
// 1. Escala global de las naves
|
||||||
constexpr float SHIP_BASE_SCALE = 2.5F; // Multiplicador (1.0 = mida original del .shp)
|
constexpr float SHIP_BASE_SCALE = 2.5F; // Multiplicador (1.0 = mida original del .shp)
|
||||||
|
|
||||||
// 2. Altura vertical (cercanía al centro)
|
// 2. Altura vertical (cercanía al centro)
|
||||||
// Ratio Y desde el centro de la pantalla (0.0 = centro, 1.0 = bottom de pantalla)
|
// Ratio Y desde el centro de la pantalla (0.0 = centro, 1.0 = bottom de pantalla)
|
||||||
constexpr float TARGET_Y_RATIO = 0.15625F;
|
constexpr float TARGET_Y_RATIO = 0.15625F;
|
||||||
|
|
||||||
// 3. Radio orbital (distancia radial desde centro en coordenadas polares)
|
// 3. Radio orbital (distance radial desde centro en coordenadas polares)
|
||||||
constexpr float CLOCK_RADIUS = 150.0F; // Distància des del centre
|
constexpr float CLOCK_RADIUS = 150.0F; // Distancia des del centro
|
||||||
|
|
||||||
// 4. Ángulos de posición (clock positions en coordenadas polares)
|
// 4. Ángulos de posición (clock positions en coordenadas polares)
|
||||||
// En coordenades de pantalla: 0° = dreta, 90° = baix, 180° = esquerra, 270° = dalt
|
// En coordenadas de pantalla: 0° = derecha, 90° = baix, 180° = izquierda, 270° = dalt
|
||||||
constexpr float CLOCK_8_ANGLE = 150.0F * Math::PI / 180.0F; // 8 o'clock (bottom-left)
|
constexpr float CLOCK_8_ANGLE = 150.0F * Math::PI / 180.0F; // 8 o'clock (bottom-left)
|
||||||
constexpr float CLOCK_4_ANGLE = 30.0F * Math::PI / 180.0F; // 4 o'clock (bottom-right)
|
constexpr float CLOCK_4_ANGLE = 30.0F * Math::PI / 180.0F; // 4 o'clock (bottom-right)
|
||||||
|
|
||||||
// 5. Radio máximo de la forma de la nave (para calcular offset automáticamente)
|
// 5. Radio máximo de la shape de la nave (para calcular offset automáticamente)
|
||||||
constexpr float SHIP_MAX_RADIUS = 30.0F; // Radi del cercle circumscrit a ship_starfield.shp
|
constexpr float SHIP_MAX_RADIUS = 30.0F; // Radi del cercle circumscrit a ship_starfield.shp
|
||||||
|
|
||||||
// 6. Margen de seguridad para offset de entrada
|
// 6. Margen de seguridad para offset de entrada
|
||||||
constexpr float ENTRY_OFFSET_MARGIN = 227.5F; // Para offset total de ~340px (ajustado)
|
constexpr float ENTRY_OFFSET_MARGIN = 227.5F; // Para offset total de ~340px (ajustado)
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// VALORS DERIVATS (calculats automàticament - NO modificar)
|
// VALORS DERIVATS (calculats automáticoament - NO modificar)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Centre de la pantalla (punt de referència)
|
// Centro de la pantalla (point de referència)
|
||||||
constexpr float CENTER_X = Game::WIDTH / 2.0F; // 320.0f
|
constexpr float CENTER_X = Game::WIDTH / 2.0F; // auto-derivado de Game::WIDTH
|
||||||
constexpr float CENTER_Y = Game::HEIGHT / 2.0F; // 240.0f
|
constexpr float CENTER_Y = Game::HEIGHT / 2.0F; // auto-derivado de Game::HEIGHT
|
||||||
|
|
||||||
// Posicions target (calculades dinàmicament des dels paràmetres base)
|
// Posicions target (calculades dinàmicament des dels parámetros base)
|
||||||
// Nota: std::cos/sin no són constexpr en C++20, però funcionen en runtime
|
// Nota: std::cos/sin no són constexpr en C++20, pero funcionen en runtime
|
||||||
// Les funcions inline són optimitzades pel compilador (zero overhead)
|
// Les funciones inline són optimitzades por el compilador (zero overhead)
|
||||||
inline float P1_TARGET_X() {
|
inline float P1_TARGET_X() {
|
||||||
return CENTER_X + (CLOCK_RADIUS * std::cos(CLOCK_8_ANGLE));
|
return CENTER_X + (CLOCK_RADIUS * std::cos(CLOCK_8_ANGLE));
|
||||||
}
|
}
|
||||||
@@ -457,27 +452,27 @@ inline float P2_TARGET_Y() {
|
|||||||
return CENTER_Y + ((Game::HEIGHT / 2.0F) * TARGET_Y_RATIO);
|
return CENTER_Y + ((Game::HEIGHT / 2.0F) * TARGET_Y_RATIO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escales d'animació (relatives a SHIP_BASE_SCALE)
|
// Escales de animación (relatives a SHIP_BASE_SCALE)
|
||||||
constexpr float ENTRY_SCALE_START = 1.5F * SHIP_BASE_SCALE; // Entrada: 50% més gran
|
constexpr float ENTRY_SCALE_START = 1.5F * SHIP_BASE_SCALE; // Entrada: 50% més grande
|
||||||
constexpr float FLOATING_SCALE = 1.0F * SHIP_BASE_SCALE; // Flotant: escala base
|
constexpr float FLOATING_SCALE = 1.0F * SHIP_BASE_SCALE; // Flotante: scale base
|
||||||
|
|
||||||
// Offset d'entrada (ajustat automàticament a l'escala)
|
// Offset de entrada (ajustat automáticoament a l'scale)
|
||||||
// Fórmula: (radi màxim de la nau * escala d'entrada) + marge
|
// Fórmula: (radi màxim de la ship * scale de entrada) + margen
|
||||||
constexpr float ENTRY_OFFSET = (SHIP_MAX_RADIUS * ENTRY_SCALE_START) + ENTRY_OFFSET_MARGIN;
|
constexpr float ENTRY_OFFSET = (SHIP_MAX_RADIUS * ENTRY_SCALE_START) + ENTRY_OFFSET_MARGIN;
|
||||||
|
|
||||||
// Punt de fuga (centre per a l'animació de sortida)
|
// Vec2 de fuga (centro para l'animación de salida)
|
||||||
constexpr float VANISHING_POINT_X = CENTER_X; // 320.0f
|
constexpr float VANISHING_POINT_X = CENTER_X; // auto-derivado de Game::WIDTH
|
||||||
constexpr float VANISHING_POINT_Y = CENTER_Y; // 240.0f
|
constexpr float VANISHING_POINT_Y = CENTER_Y; // auto-derivado de Game::HEIGHT
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ANIMACIONS (durades, oscil·lacions, delays)
|
// ANIMACIONS (durades, oscil·lacions, delays)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Durades d'animació
|
// Durades de animación
|
||||||
constexpr float ENTRY_DURATION = 2.0F; // Entrada (segons)
|
constexpr float ENTRY_DURATION = 2.0F; // Entrada (segons)
|
||||||
constexpr float EXIT_DURATION = 1.0F; // Sortida (segons)
|
constexpr float EXIT_DURATION = 1.0F; // Salida (segons)
|
||||||
|
|
||||||
// Flotació (oscil·lació reduïda i diferenciada per nau)
|
// Flotació (oscil·lació reduïda y diferenciada per ship)
|
||||||
constexpr float FLOAT_AMPLITUDE_X = 4.0F; // Amplitud X (píxels)
|
constexpr float FLOAT_AMPLITUDE_X = 4.0F; // Amplitud X (píxels)
|
||||||
constexpr float FLOAT_AMPLITUDE_Y = 2.5F; // Amplitud Y (píxels)
|
constexpr float FLOAT_AMPLITUDE_Y = 2.5F; // Amplitud Y (píxels)
|
||||||
|
|
||||||
@@ -486,21 +481,21 @@ constexpr float FLOAT_FREQUENCY_X_BASE = 0.5F; // Hz
|
|||||||
constexpr float FLOAT_FREQUENCY_Y_BASE = 0.7F; // Hz
|
constexpr float FLOAT_FREQUENCY_Y_BASE = 0.7F; // Hz
|
||||||
constexpr float FLOAT_PHASE_OFFSET = 1.57F; // π/2 (90°)
|
constexpr float FLOAT_PHASE_OFFSET = 1.57F; // π/2 (90°)
|
||||||
|
|
||||||
// Delays d'entrada (per a entrada escalonada)
|
// Delays de entrada (per a entrada escalonada)
|
||||||
constexpr float P1_ENTRY_DELAY = 0.0F; // P1 entra immediatament
|
constexpr float P1_ENTRY_DELAY = 0.0F; // P1 entra immediatament
|
||||||
constexpr float P2_ENTRY_DELAY = 0.5F; // P2 entra 0.5s després
|
constexpr float P2_ENTRY_DELAY = 0.5F; // P2 entra 0.5s después
|
||||||
|
|
||||||
// Delay global abans d'iniciar l'animació d'entrada al estat MAIN
|
// Delay global antes de start l'animación de entrada al state MAIN
|
||||||
constexpr float ENTRANCE_DELAY = 5.0F; // Temps d'espera abans que les naus entrin
|
constexpr float ENTRANCE_DELAY = 5.0F; // Temps de espera antes que las naves entrin
|
||||||
|
|
||||||
// Multiplicadors de freqüència per a cada nau (variació sutil ±12%)
|
// Multiplicadors de freqüència para cada ship (variació sutil ±12%)
|
||||||
constexpr float P1_FREQUENCY_MULTIPLIER = 0.88F; // 12% més lenta
|
constexpr float P1_FREQUENCY_MULTIPLIER = 0.88F; // 12% més lenta
|
||||||
constexpr float P2_FREQUENCY_MULTIPLIER = 1.12F; // 12% més ràpida
|
constexpr float P2_FREQUENCY_MULTIPLIER = 1.12F; // 12% més ràpida
|
||||||
|
|
||||||
} // namespace Ships
|
} // namespace Ships
|
||||||
|
|
||||||
namespace Layout {
|
namespace Layout {
|
||||||
// Posicions verticals (anclatges des del TOP de pantalla lògica, 0.0-1.0)
|
// Posicions verticals (anclatges des del TOP de pantalla lógica, 0.0-1.0)
|
||||||
constexpr float LOGO_POS = 0.20F; // Logo "ORNI"
|
constexpr float LOGO_POS = 0.20F; // Logo "ORNI"
|
||||||
constexpr float PRESS_START_POS = 0.75F; // "PRESS START TO PLAY"
|
constexpr float PRESS_START_POS = 0.75F; // "PRESS START TO PLAY"
|
||||||
constexpr float COPYRIGHT1_POS = 0.90F; // Primera línia copyright
|
constexpr float COPYRIGHT1_POS = 0.90F; // Primera línia copyright
|
||||||
@@ -509,21 +504,21 @@ constexpr float COPYRIGHT1_POS = 0.90F; // Primera línia copyright
|
|||||||
constexpr float LOGO_LINE_SPACING = 0.02F; // Entre "ORNI" i "ATTACK!" (10px)
|
constexpr float LOGO_LINE_SPACING = 0.02F; // Entre "ORNI" i "ATTACK!" (10px)
|
||||||
constexpr float COPYRIGHT_LINE_SPACING = 0.0F; // Entre línies copyright (5px)
|
constexpr float COPYRIGHT_LINE_SPACING = 0.0F; // Entre línies copyright (5px)
|
||||||
|
|
||||||
// Factors d'escala
|
// Factors de scale
|
||||||
constexpr float LOGO_SCALE = 0.6F; // Escala "ORNI ATTACK!"
|
constexpr float LOGO_SCALE = 0.6F; // Escala "ORNI ATTACK!"
|
||||||
constexpr float PRESS_START_SCALE = 1.0F; // Escala "PRESS START TO PLAY"
|
constexpr float PRESS_START_SCALE = 1.0F; // Escala "PRESS START TO PLAY"
|
||||||
constexpr float COPYRIGHT_SCALE = 0.5F; // Escala copyright
|
constexpr float COPYRIGHT_SCALE = 0.5F; // Escala copyright
|
||||||
|
|
||||||
// Espaiat entre caràcters (usat per VectorText)
|
// Espaiat entre caràcters (usado per VectorText)
|
||||||
constexpr float TEXT_SPACING = 2.0F;
|
constexpr float TEXT_SPACING = 2.0F;
|
||||||
} // namespace Layout
|
} // namespace Layout
|
||||||
} // namespace Title
|
} // namespace Title
|
||||||
|
|
||||||
// Floating score numbers (números flotants de puntuació)
|
// Floating score numbers (números flotantes de puntuación)
|
||||||
namespace FloatingScore {
|
namespace FloatingScore {
|
||||||
constexpr float LIFETIME = 2.0F; // Duració màxima (segons)
|
constexpr float LIFETIME = 2.0F; // Duració màxima (segons)
|
||||||
constexpr float VELOCITY_Y = -30.0F; // Velocitat vertical (px/s, negatiu = amunt)
|
constexpr float VELOCITY_Y = -30.0F; // Velocidad vertical (px/s, negatiu = amunt)
|
||||||
constexpr float VELOCITY_X = 0.0F; // Velocitat horizontal (px/s)
|
constexpr float VELOCITY_X = 0.0F; // Velocidad horizontal (px/s)
|
||||||
constexpr float SCALE = 0.45F; // Escala del text (0.6 = 60% del marcador)
|
constexpr float SCALE = 0.45F; // Escala del text (0.6 = 60% del marcador)
|
||||||
constexpr float SPACING = 0.0F; // Espaiat entre caràcters
|
constexpr float SPACING = 0.0F; // Espaiat entre caràcters
|
||||||
constexpr int MAX_CONCURRENT = 15; // Pool size (= MAX_ORNIS)
|
constexpr int MAX_CONCURRENT = 15; // Pool size (= MAX_ORNIS)
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
// entitat.hpp - Classe base abstracta per a totes les entitats del joc
|
|
||||||
// © 2025 Orni Attack - Arquitectura d'entitats
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "core/graphics/shape.hpp"
|
|
||||||
#include "core/types.hpp"
|
|
||||||
|
|
||||||
namespace Entities {
|
|
||||||
|
|
||||||
class Entitat {
|
|
||||||
public:
|
|
||||||
virtual ~Entitat() = default;
|
|
||||||
|
|
||||||
// Interfície principal (virtual pur)
|
|
||||||
virtual void inicialitzar() = 0;
|
|
||||||
virtual void actualitzar(float delta_time) = 0;
|
|
||||||
virtual void dibuixar() const = 0;
|
|
||||||
[[nodiscard]] virtual bool esta_actiu() const = 0;
|
|
||||||
|
|
||||||
// Interfície de col·lisió (override opcional)
|
|
||||||
[[nodiscard]] virtual float get_collision_radius() const { return 0.0F; }
|
|
||||||
[[nodiscard]] virtual bool es_collidable() const { return false; }
|
|
||||||
|
|
||||||
// Getters comuns (inline, sense overhead)
|
|
||||||
[[nodiscard]] const Punt& get_centre() const { return centre_; }
|
|
||||||
[[nodiscard]] float get_angle() const { return angle_; }
|
|
||||||
[[nodiscard]] float get_brightness() const { return brightness_; }
|
|
||||||
[[nodiscard]] const std::shared_ptr<Graphics::Shape>& get_forma() const { return forma_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// Estat comú (accés directe, sense overhead)
|
|
||||||
SDL_Renderer* renderer_;
|
|
||||||
std::shared_ptr<Graphics::Shape> forma_;
|
|
||||||
Punt centre_;
|
|
||||||
float angle_{0.0F};
|
|
||||||
float brightness_{1.0F};
|
|
||||||
|
|
||||||
// Constructor protegit (classe abstracta)
|
|
||||||
Entitat(SDL_Renderer* renderer = nullptr)
|
|
||||||
: renderer_(renderer),
|
|
||||||
centre_({.x = 0.0F, .y = 0.0F}) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Entities
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// entity.hpp - Clase base abstracta para todas las entidades del juego
|
||||||
|
// © 2025 Orni Attack - Arquitectura de entidades
|
||||||
|
//
|
||||||
|
// Cada Entity incluye un Physics::RigidBody como member. Las entidades que
|
||||||
|
// se simulen físicamente lo configuran en init() y registran en el
|
||||||
|
// PhysicsWorld del GameScene. Las que no, ignoran el body (queda con
|
||||||
|
// defaults inocuos: mass=1, radius=0).
|
||||||
|
//
|
||||||
|
// Flujo por frame (gestionado por GameScene):
|
||||||
|
// 1. entity.update(dt) — aplicar fuerzas, decidir lógica
|
||||||
|
// 2. world.update(dt) — integrar bodies, resolver colisiones
|
||||||
|
// 3. entity.postUpdate(dt) — sincronizar mirror (center_, angle_)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "core/graphics/shape.hpp"
|
||||||
|
#include "core/physics/rigid_body.hpp"
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
#include "core/types.hpp"
|
||||||
|
|
||||||
|
namespace Entities {
|
||||||
|
|
||||||
|
class Entity {
|
||||||
|
public:
|
||||||
|
virtual ~Entity() = default;
|
||||||
|
|
||||||
|
// Interfaz principal (virtual pur)
|
||||||
|
virtual void init() = 0;
|
||||||
|
virtual void update(float delta_time) = 0;
|
||||||
|
virtual void draw() const = 0;
|
||||||
|
[[nodiscard]] virtual bool isActive() const = 0;
|
||||||
|
|
||||||
|
// Sincronización post-física (override opcional).
|
||||||
|
// Llamado por GameScene tras world.update(). Default: no-op.
|
||||||
|
virtual void postUpdate(float /*delta_time*/) {}
|
||||||
|
|
||||||
|
// Interfaz de colisión (override opcional)
|
||||||
|
[[nodiscard]] virtual float getCollisionRadius() const { return 0.0F; }
|
||||||
|
[[nodiscard]] virtual bool isCollidable() const { return false; }
|
||||||
|
|
||||||
|
// Getters comunes (inline, sin overhead)
|
||||||
|
[[nodiscard]] const Vec2& getCenter() const { return center_; }
|
||||||
|
[[nodiscard]] float getAngle() const { return angle_; }
|
||||||
|
[[nodiscard]] float getBrightness() const { return brightness_; }
|
||||||
|
[[nodiscard]] const std::shared_ptr<Graphics::Shape>& getShape() const { return shape_; }
|
||||||
|
|
||||||
|
// Acceso al cuerpo físico (Fase 6+). El PhysicsWorld lo registra
|
||||||
|
// por puntero; la entidad lo configura en init().
|
||||||
|
[[nodiscard]] auto getBody() -> Physics::RigidBody& { return body_; }
|
||||||
|
[[nodiscard]] auto getBody() const -> const Physics::RigidBody& { return body_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Estado común (acceso directo, sin overhead)
|
||||||
|
Rendering::Renderer* renderer_;
|
||||||
|
std::shared_ptr<Graphics::Shape> shape_;
|
||||||
|
Vec2 center_;
|
||||||
|
float angle_{0.0F};
|
||||||
|
float brightness_{1.0F};
|
||||||
|
|
||||||
|
// Cuerpo físico (Fase 6). Las entidades que se mueven por
|
||||||
|
// física actualizan center_/angle_ en postUpdate() desde body_.
|
||||||
|
Physics::RigidBody body_;
|
||||||
|
|
||||||
|
// Constructor protegido (clase abstracta)
|
||||||
|
Entity(Rendering::Renderer* renderer = nullptr)
|
||||||
|
: renderer_(renderer),
|
||||||
|
center_({.x = 0.0F, .y = 0.0F}) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Entities
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// shape.cpp - Implementació del sistema de formes vectorials
|
// shape.cpp - Implementació del sistema de formes vectorials
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "core/graphics/shape.hpp"
|
#include "core/graphics/shape.hpp"
|
||||||
|
|
||||||
@@ -11,31 +11,31 @@
|
|||||||
namespace Graphics {
|
namespace Graphics {
|
||||||
|
|
||||||
Shape::Shape(const std::string& filepath)
|
Shape::Shape(const std::string& filepath)
|
||||||
: centre_({.x = 0.0F, .y = 0.0F}),
|
: center_({.x = 0.0F, .y = 0.0F}),
|
||||||
escala_defecte_(1.0F),
|
escala_defecte_(1.0F),
|
||||||
nom_("unnamed") {
|
nom_("unnamed") {
|
||||||
carregar(filepath);
|
load(filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shape::carregar(const std::string& filepath) {
|
bool Shape::load(const std::string& filepath) {
|
||||||
// Llegir fitxer
|
// Llegir file
|
||||||
std::ifstream file(filepath);
|
std::ifstream file(filepath);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
std::cerr << "[Shape] Error: no es pot obrir " << filepath << '\n';
|
std::cerr << "[Shape] Error: no es pot obrir " << filepath << '\n';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Llegir tot el contingut
|
// Llegir todo el contingut
|
||||||
std::stringstream buffer;
|
std::stringstream buffer;
|
||||||
buffer << file.rdbuf();
|
buffer << file.rdbuf();
|
||||||
std::string contingut = buffer.str();
|
std::string contingut = buffer.str();
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
// Parsejar
|
// Parsejar
|
||||||
return parsejar_fitxer(contingut);
|
return parseFile(contingut);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shape::parsejar_fitxer(const std::string& contingut) {
|
bool Shape::parseFile(const std::string& contingut) {
|
||||||
std::istringstream iss(contingut);
|
std::istringstream iss(contingut);
|
||||||
std::string line;
|
std::string line;
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ bool Shape::parsejar_fitxer(const std::string& contingut) {
|
|||||||
try {
|
try {
|
||||||
escala_defecte_ = std::stof(extract_value(line));
|
escala_defecte_ = std::stof(extract_value(line));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::cerr << "[Shape] Warning: escala invàlida, usant 1.0" << '\n';
|
std::cerr << "[Shape] Warning: scale invàlida, usant 1.0" << '\n';
|
||||||
escala_defecte_ = 1.0F;
|
escala_defecte_ = 1.0F;
|
||||||
}
|
}
|
||||||
} else if (starts_with(line, "center:")) {
|
} else if (starts_with(line, "center:")) {
|
||||||
@@ -65,7 +65,7 @@ bool Shape::parsejar_fitxer(const std::string& contingut) {
|
|||||||
if (points.size() >= 2) {
|
if (points.size() >= 2) {
|
||||||
primitives_.push_back({PrimitiveType::POLYLINE, points});
|
primitives_.push_back({PrimitiveType::POLYLINE, points});
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[Shape] Warning: polyline amb menys de 2 punts ignorada"
|
std::cerr << "[Shape] Warning: polyline con menys de 2 points ignorada"
|
||||||
<< '\n';
|
<< '\n';
|
||||||
}
|
}
|
||||||
} else if (starts_with(line, "line:")) {
|
} else if (starts_with(line, "line:")) {
|
||||||
@@ -73,7 +73,7 @@ bool Shape::parsejar_fitxer(const std::string& contingut) {
|
|||||||
if (points.size() == 2) {
|
if (points.size() == 2) {
|
||||||
primitives_.push_back({PrimitiveType::LINE, points});
|
primitives_.push_back({PrimitiveType::LINE, points});
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[Shape] Warning: line ha de tenir exactament 2 punts"
|
std::cerr << "[Shape] Warning: line ha de tenir exactament 2 points"
|
||||||
<< '\n';
|
<< '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ bool Shape::parsejar_fitxer(const std::string& contingut) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (primitives_.empty()) {
|
if (primitives_.empty()) {
|
||||||
std::cerr << "[Shape] Error: cap primitiva carregada" << '\n';
|
std::cerr << "[Shape] Error: sin primitiva carregada" << '\n';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,18 +124,18 @@ void Shape::parse_center(const std::string& value) {
|
|||||||
size_t comma = val.find(',');
|
size_t comma = val.find(',');
|
||||||
if (comma != std::string::npos) {
|
if (comma != std::string::npos) {
|
||||||
try {
|
try {
|
||||||
centre_.x = std::stof(trim(val.substr(0, comma)));
|
center_.x = std::stof(trim(val.substr(0, comma)));
|
||||||
centre_.y = std::stof(trim(val.substr(comma + 1)));
|
center_.y = std::stof(trim(val.substr(comma + 1)));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::cerr << "[Shape] Warning: centre invàlid, usant (0,0)" << '\n';
|
std::cerr << "[Shape] Warning: centro invàlid, usant (0,0)" << '\n';
|
||||||
centre_ = {.x = 0.0F, .y = 0.0F};
|
center_ = {.x = 0.0F, .y = 0.0F};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: parse points "x1,y1 x2,y2 x3,y3"
|
// Helper: parse points "x1,y1 x2,y2 x3,y3"
|
||||||
std::vector<Punt> Shape::parse_points(const std::string& str) const {
|
std::vector<Vec2> Shape::parse_points(const std::string& str) const {
|
||||||
std::vector<Punt> points;
|
std::vector<Vec2> points;
|
||||||
std::istringstream iss(trim(str));
|
std::istringstream iss(trim(str));
|
||||||
std::string pair;
|
std::string pair;
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ std::vector<Punt> Shape::parse_points(const std::string& str) const {
|
|||||||
float y = std::stof(pair.substr(comma + 1));
|
float y = std::stof(pair.substr(comma + 1));
|
||||||
points.push_back({x, y});
|
points.push_back({x, y});
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::cerr << "[Shape] Warning: punt invàlid ignorat: " << pair
|
std::cerr << "[Shape] Warning: point invàlid ignorat: " << pair
|
||||||
<< '\n';
|
<< '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// shape.hpp - Sistema de formes vectorials
|
// shape.hpp - Sistema de formes vectorials
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -10,38 +10,38 @@
|
|||||||
|
|
||||||
namespace Graphics {
|
namespace Graphics {
|
||||||
|
|
||||||
// Tipus de primitiva dins d'una forma
|
// Tipo de primitiva dins de una shape
|
||||||
enum class PrimitiveType {
|
enum class PrimitiveType {
|
||||||
POLYLINE, // Seqüència de punts connectats
|
POLYLINE, // Secuencia de points connectats
|
||||||
LINE // Línia individual (2 punts)
|
LINE // Línia individual (2 points)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Primitiva individual (polyline o line)
|
// Primitiva individual (polyline o line)
|
||||||
struct ShapePrimitive {
|
struct ShapePrimitive {
|
||||||
PrimitiveType type;
|
PrimitiveType type;
|
||||||
std::vector<Punt> points; // 2+ punts per polyline, exactament 2 per line
|
std::vector<Vec2> points; // 2+ points per polyline, exactament 2 per line
|
||||||
};
|
};
|
||||||
|
|
||||||
// Classe Shape - representa una forma vectorial carregada des de .shp
|
// Clase Shape - representa una shape vectorial carregada desde .shp
|
||||||
class Shape {
|
class Shape {
|
||||||
public:
|
public:
|
||||||
// Constructors
|
// Constructors
|
||||||
Shape() = default;
|
Shape() = default;
|
||||||
explicit Shape(const std::string& filepath);
|
explicit Shape(const std::string& filepath);
|
||||||
|
|
||||||
// Carregar forma des de fitxer .shp
|
// Carregar shape desde file .shp
|
||||||
bool carregar(const std::string& filepath);
|
bool load(const std::string& filepath);
|
||||||
|
|
||||||
// Parsejar forma des de buffer de memòria (per al sistema de recursos)
|
// Parsejar shape desde buffer de memòria (per al sistema de recursos)
|
||||||
bool parsejar_fitxer(const std::string& contingut);
|
bool parseFile(const std::string& contingut);
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
[[nodiscard]] const std::vector<ShapePrimitive>& get_primitives() const {
|
[[nodiscard]] const std::vector<ShapePrimitive>& get_primitives() const {
|
||||||
return primitives_;
|
return primitives_;
|
||||||
}
|
}
|
||||||
[[nodiscard]] const Punt& get_centre() const { return centre_; }
|
[[nodiscard]] const Vec2& getCenter() const { return center_; }
|
||||||
[[nodiscard]] float get_escala_defecte() const { return escala_defecte_; }
|
[[nodiscard]] float get_escala_defecte() const { return escala_defecte_; }
|
||||||
[[nodiscard]] bool es_valida() const { return !primitives_.empty(); }
|
[[nodiscard]] bool isValid() const { return !primitives_.empty(); }
|
||||||
|
|
||||||
// Info de depuració
|
// Info de depuració
|
||||||
[[nodiscard]] std::string get_nom() const { return nom_; }
|
[[nodiscard]] std::string get_nom() const { return nom_; }
|
||||||
@@ -49,16 +49,16 @@ class Shape {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<ShapePrimitive> primitives_;
|
std::vector<ShapePrimitive> primitives_;
|
||||||
Punt centre_; // Centre/origen de la forma
|
Vec2 center_; // Centro/origin de la shape
|
||||||
float escala_defecte_; // Escala per defecte (normalment 1.0)
|
float escala_defecte_; // Escala per defecte (normalment 1.0)
|
||||||
std::string nom_; // Nom de la forma (per depuració)
|
std::string nom_; // Nom de la shape (per depuració)
|
||||||
|
|
||||||
// Helpers privats per parsejar
|
// Helpers privats per parsejar
|
||||||
[[nodiscard]] std::string trim(const std::string& str) const;
|
[[nodiscard]] std::string trim(const std::string& str) const;
|
||||||
[[nodiscard]] bool starts_with(const std::string& str, const std::string& prefix) const;
|
[[nodiscard]] bool starts_with(const std::string& str, const std::string& prefix) const;
|
||||||
[[nodiscard]] std::string extract_value(const std::string& line) const;
|
[[nodiscard]] std::string extract_value(const std::string& line) const;
|
||||||
void parse_center(const std::string& value);
|
void parse_center(const std::string& value);
|
||||||
[[nodiscard]] std::vector<Punt> parse_points(const std::string& str) const;
|
[[nodiscard]] std::vector<Vec2> parse_points(const std::string& str) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Graphics
|
} // namespace Graphics
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// shape_loader.cpp - Implementació del carregador amb caché
|
// shape_loader.cpp - Implementació del carregador con caché
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "core/graphics/shape_loader.hpp"
|
#include "core/graphics/shape_loader.hpp"
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
namespace Graphics {
|
namespace Graphics {
|
||||||
|
|
||||||
// Inicialització de variables estàtiques
|
// Inicialización de variables estàtiques
|
||||||
std::unordered_map<std::string, std::shared_ptr<Shape>> ShapeLoader::cache_;
|
std::unordered_map<std::string, std::shared_ptr<Shape>> ShapeLoader::cache_;
|
||||||
std::string ShapeLoader::base_path_ = "data/shapes/";
|
std::string ShapeLoader::base_path_ = "data/shapes/";
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ std::shared_ptr<Shape> ShapeLoader::load(const std::string& filename) {
|
|||||||
// Load from resource system
|
// Load from resource system
|
||||||
std::vector<uint8_t> data = Resource::Helper::loadFile(normalized);
|
std::vector<uint8_t> data = Resource::Helper::loadFile(normalized);
|
||||||
if (data.empty()) {
|
if (data.empty()) {
|
||||||
std::cerr << "[ShapeLoader] Error: no s'ha pogut carregar " << normalized
|
std::cerr << "[ShapeLoader] Error: no s'ha pogut load " << normalized
|
||||||
<< '\n';
|
<< '\n';
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -40,15 +40,15 @@ std::shared_ptr<Shape> ShapeLoader::load(const std::string& filename) {
|
|||||||
// Convert bytes to string and parse
|
// Convert bytes to string and parse
|
||||||
std::string file_content(data.begin(), data.end());
|
std::string file_content(data.begin(), data.end());
|
||||||
auto shape = std::make_shared<Shape>();
|
auto shape = std::make_shared<Shape>();
|
||||||
if (!shape->parsejar_fitxer(file_content)) {
|
if (!shape->parseFile(file_content)) {
|
||||||
std::cerr << "[ShapeLoader] Error: no s'ha pogut parsejar " << normalized
|
std::cerr << "[ShapeLoader] Error: no s'ha pogut parsejar " << normalized
|
||||||
<< '\n';
|
<< '\n';
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify shape is valid
|
// Verify shape is valid
|
||||||
if (!shape->es_valida()) {
|
if (!shape->isValid()) {
|
||||||
std::cerr << "[ShapeLoader] Error: forma invàlida " << normalized << '\n';
|
std::cerr << "[ShapeLoader] Error: shape invàlida " << normalized << '\n';
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,17 +69,17 @@ void ShapeLoader::clear_cache() {
|
|||||||
size_t ShapeLoader::get_cache_size() { return cache_.size(); }
|
size_t ShapeLoader::get_cache_size() { return cache_.size(); }
|
||||||
|
|
||||||
std::string ShapeLoader::resolve_path(const std::string& filename) {
|
std::string ShapeLoader::resolve_path(const std::string& filename) {
|
||||||
// Si és un path absolut (comença amb '/'), usar-lo directament
|
// Si es un path absolut (comença con '/'), usar-lo directament
|
||||||
if (!filename.empty() && filename[0] == '/') {
|
if (!filename.empty() && filename[0] == '/') {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si ja conté el prefix base_path, usar-lo directament
|
// Si ya conté el prefix base_path, usar-lo directament
|
||||||
if (filename.starts_with(base_path_)) {
|
if (filename.starts_with(base_path_)) {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Altrament, afegir base_path (ara suporta subdirectoris)
|
// Altrament, añadir base_path (ara suporta subdirectoris)
|
||||||
return base_path_ + filename;
|
return base_path_ + filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// shape_loader.hpp - Carregador estàtic de formes amb caché
|
// shape_loader.hpp - Carregador estàtic de formes con caché
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -11,13 +11,13 @@
|
|||||||
|
|
||||||
namespace Graphics {
|
namespace Graphics {
|
||||||
|
|
||||||
// Carregador estàtic de formes amb caché
|
// Carregador estàtic de formes con caché
|
||||||
class ShapeLoader {
|
class ShapeLoader {
|
||||||
public:
|
public:
|
||||||
// No instanciable (tot estàtic)
|
// No instanciable (tot estàtic)
|
||||||
ShapeLoader() = delete;
|
ShapeLoader() = delete;
|
||||||
|
|
||||||
// Carregar forma des de fitxer (amb caché)
|
// Carregar shape desde file (con caché)
|
||||||
// Retorna punter compartit (nullptr si error)
|
// Retorna punter compartit (nullptr si error)
|
||||||
// Exemple: load("ship.shp") → busca a "data/shapes/ship.shp"
|
// Exemple: load("ship.shp") → busca a "data/shapes/ship.shp"
|
||||||
static std::shared_ptr<Shape> load(const std::string& filename);
|
static std::shared_ptr<Shape> load(const std::string& filename);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// starfield.cpp - Implementació del sistema d'estrelles de fons
|
// starfield.cpp - Implementació del sistema de estrelles de fons
|
||||||
// © 2025 Orni Attack
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
#include "core/graphics/starfield.hpp"
|
#include "core/graphics/starfield.hpp"
|
||||||
@@ -14,38 +14,38 @@
|
|||||||
namespace Graphics {
|
namespace Graphics {
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Starfield::Starfield(SDL_Renderer* renderer,
|
Starfield::Starfield(Rendering::Renderer* renderer,
|
||||||
const Punt& punt_fuga,
|
const Vec2& punt_fuga,
|
||||||
const SDL_FRect& area,
|
const SDL_FRect& area,
|
||||||
int densitat)
|
int densitat)
|
||||||
: renderer_(renderer),
|
: renderer_(renderer),
|
||||||
punt_fuga_(punt_fuga),
|
punt_fuga_(punt_fuga),
|
||||||
area_(area),
|
area_(area),
|
||||||
densitat_(densitat) {
|
densitat_(densitat) {
|
||||||
// Carregar forma d'estrella amb ShapeLoader
|
// Carregar shape de estrella con ShapeLoader
|
||||||
shape_estrella_ = ShapeLoader::load("star.shp");
|
shape_estrella_ = ShapeLoader::load("star.shp");
|
||||||
|
|
||||||
if (!shape_estrella_ || !shape_estrella_->es_valida()) {
|
if (!shape_estrella_ || !shape_estrella_->isValid()) {
|
||||||
std::cerr << "ERROR: No s'ha pogut carregar star.shp" << '\n';
|
std::cerr << "ERROR: No s'ha pogut load star.shp" << '\n';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configurar 3 capes amb diferents velocitats i escales
|
// Configurar 3 capes con diferents velocitats i escales
|
||||||
// Capa 0: Fons llunyà (lenta, petita)
|
// Capa 0: Fons llunyà (lenta, pequeña)
|
||||||
capes_.push_back({20.0F, 0.3F, 0.8F, densitat / 3});
|
capes_.push_back({20.0F, 0.3F, 0.8F, densitat / 3});
|
||||||
|
|
||||||
// Capa 1: Profunditat mitjana
|
// Capa 1: Profunditat mitjana
|
||||||
capes_.push_back({40.0F, 0.5F, 1.2F, densitat / 3});
|
capes_.push_back({40.0F, 0.5F, 1.2F, densitat / 3});
|
||||||
|
|
||||||
// Capa 2: Primer pla (ràpida, gran)
|
// Capa 2: Primer pla (ràpida, grande)
|
||||||
capes_.push_back({80.0F, 0.8F, 2.0F, densitat / 3});
|
capes_.push_back({80.0F, 0.8F, 2.0F, densitat / 3});
|
||||||
|
|
||||||
// Calcular radi màxim (distància del centre al racó més llunyà)
|
// Calcular radi màxim (distancia del centro al racó més llunyà)
|
||||||
float dx = std::max(punt_fuga_.x, area_.w - punt_fuga_.x);
|
float dx = std::max(punt_fuga_.x, area_.w - punt_fuga_.x);
|
||||||
float dy = std::max(punt_fuga_.y, area_.h - punt_fuga_.y);
|
float dy = std::max(punt_fuga_.y, area_.h - punt_fuga_.y);
|
||||||
radi_max_ = std::sqrt((dx * dx) + (dy * dy));
|
radi_max_ = std::sqrt((dx * dx) + (dy * dy));
|
||||||
|
|
||||||
// Inicialitzar estrelles amb posicions distribuïdes (pre-omplir pantalla)
|
// Inicialitzar estrelles con posicions distribuïdes (pre-omplir pantalla)
|
||||||
for (int capa_idx = 0; capa_idx < 3; capa_idx++) {
|
for (int capa_idx = 0; capa_idx < 3; capa_idx++) {
|
||||||
int num = capes_[capa_idx].num_estrelles;
|
int num = capes_[capa_idx].num_estrelles;
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
@@ -55,55 +55,55 @@ Starfield::Starfield(SDL_Renderer* renderer,
|
|||||||
// Angle aleatori
|
// Angle aleatori
|
||||||
estrella.angle = (static_cast<float>(rand()) / RAND_MAX) * 2.0F * Defaults::Math::PI;
|
estrella.angle = (static_cast<float>(rand()) / RAND_MAX) * 2.0F * Defaults::Math::PI;
|
||||||
|
|
||||||
// Distància aleatòria (0.0 a 1.0) per omplir tota la pantalla
|
// Distancia aleatòria (0.0 a 1.0) per omplir toda la pantalla
|
||||||
estrella.distancia_centre = static_cast<float>(rand()) / RAND_MAX;
|
estrella.distancia_centre = static_cast<float>(rand()) / RAND_MAX;
|
||||||
|
|
||||||
// Calcular posició des de la distància
|
// Calcular posición desde la distancia
|
||||||
float radi = estrella.distancia_centre * radi_max_;
|
float radi = estrella.distancia_centre * radi_max_;
|
||||||
estrella.posicio.x = punt_fuga_.x + (radi * std::cos(estrella.angle));
|
estrella.position.x = punt_fuga_.x + (radi * std::cos(estrella.angle));
|
||||||
estrella.posicio.y = punt_fuga_.y + (radi * std::sin(estrella.angle));
|
estrella.position.y = punt_fuga_.y + (radi * std::sin(estrella.angle));
|
||||||
|
|
||||||
estrelles_.push_back(estrella);
|
estrelles_.push_back(estrella);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicialitzar una estrella (nova o regenerada)
|
// Inicialitzar una estrella (nueva o regenerada)
|
||||||
void Starfield::inicialitzar_estrella(Estrella& estrella) const {
|
void Starfield::inicialitzar_estrella(Estrella& estrella) const {
|
||||||
// Angle aleatori des del punt de fuga cap a fora
|
// Angle aleatori des del point de fuga hacia fuera
|
||||||
estrella.angle = (static_cast<float>(rand()) / RAND_MAX) * 2.0F * Defaults::Math::PI;
|
estrella.angle = (static_cast<float>(rand()) / RAND_MAX) * 2.0F * Defaults::Math::PI;
|
||||||
|
|
||||||
// Distància inicial petita (5% del radi màxim) - neix prop del centre
|
// Distancia inicial pequeña (5% del radi màxim) - neix prop del centro
|
||||||
estrella.distancia_centre = 0.05F;
|
estrella.distancia_centre = 0.05F;
|
||||||
|
|
||||||
// Posició inicial: molt prop del punt de fuga
|
// Posición inicial: mucho prop del point de fuga
|
||||||
float radi = estrella.distancia_centre * radi_max_;
|
float radi = estrella.distancia_centre * radi_max_;
|
||||||
estrella.posicio.x = punt_fuga_.x + (radi * std::cos(estrella.angle));
|
estrella.position.x = punt_fuga_.x + (radi * std::cos(estrella.angle));
|
||||||
estrella.posicio.y = punt_fuga_.y + (radi * std::sin(estrella.angle));
|
estrella.position.y = punt_fuga_.y + (radi * std::sin(estrella.angle));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar si una estrella està fora de l'àrea
|
// Verificar si una estrella está fuera de l'àrea
|
||||||
bool Starfield::fora_area(const Estrella& estrella) const {
|
bool Starfield::fora_area(const Estrella& estrella) const {
|
||||||
return (estrella.posicio.x < area_.x ||
|
return (estrella.position.x < area_.x ||
|
||||||
estrella.posicio.x > area_.x + area_.w ||
|
estrella.position.x > area_.x + area_.w ||
|
||||||
estrella.posicio.y < area_.y ||
|
estrella.position.y < area_.y ||
|
||||||
estrella.posicio.y > area_.y + area_.h);
|
estrella.position.y > area_.y + area_.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular escala dinàmica segons distància del centre
|
// Calcular scale dinàmica segons distancia del centro
|
||||||
float Starfield::calcular_escala(const Estrella& estrella) const {
|
float Starfield::calcular_escala(const Estrella& estrella) const {
|
||||||
const CapaConfig& capa = capes_[estrella.capa];
|
const CapaConfig& capa = capes_[estrella.capa];
|
||||||
|
|
||||||
// Interpolació lineal basada en distància del centre
|
// Interpolació lineal basada en distancia del centro
|
||||||
// distancia_centre: 0.0 (centre) → 1.0 (vora)
|
// distancia_centre: 0.0 (centro) → 1.0 (vora)
|
||||||
return capa.escala_min +
|
return capa.escala_min +
|
||||||
((capa.escala_max - capa.escala_min) * estrella.distancia_centre);
|
((capa.escala_max - capa.escala_min) * estrella.distancia_centre);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular brightness dinàmica segons distància del centre
|
// Calcular brightness dinàmica segons distancia del centro
|
||||||
float Starfield::calcular_brightness(const Estrella& estrella) const {
|
float Starfield::calcular_brightness(const Estrella& estrella) const {
|
||||||
// Interpolació lineal: estrelles properes (vora) més brillants
|
// Interpolació lineal: estrelles properes (vora) més brillants
|
||||||
// distancia_centre: 0.0 (centre, llunyanes) → 1.0 (vora, properes)
|
// distancia_centre: 0.0 (centro, llunyanes) → 1.0 (vora, properes)
|
||||||
float brightness_base = Defaults::Brightness::STARFIELD_MIN +
|
float brightness_base = Defaults::Brightness::STARFIELD_MIN +
|
||||||
((Defaults::Brightness::STARFIELD_MAX - Defaults::Brightness::STARFIELD_MIN) *
|
((Defaults::Brightness::STARFIELD_MAX - Defaults::Brightness::STARFIELD_MIN) *
|
||||||
estrella.distancia_centre);
|
estrella.distancia_centre);
|
||||||
@@ -112,23 +112,23 @@ float Starfield::calcular_brightness(const Estrella& estrella) const {
|
|||||||
return std::min(1.0F, brightness_base * multiplicador_brightness_);
|
return std::min(1.0F, brightness_base * multiplicador_brightness_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualitzar posicions de les estrelles
|
// Actualitzar posicions de las estrelles
|
||||||
void Starfield::actualitzar(float delta_time) {
|
void Starfield::update(float delta_time) {
|
||||||
for (auto& estrella : estrelles_) {
|
for (auto& estrella : estrelles_) {
|
||||||
// Obtenir configuració de la capa
|
// Obtenir configuración de la capa
|
||||||
const CapaConfig& capa = capes_[estrella.capa];
|
const CapaConfig& capa = capes_[estrella.capa];
|
||||||
|
|
||||||
// Moure cap a fora des del centre
|
// Moure hacia fuera des del centro
|
||||||
float velocitat = capa.velocitat_base;
|
float velocity = capa.velocitat_base;
|
||||||
float dx = velocitat * std::cos(estrella.angle) * delta_time;
|
float dx = velocity * std::cos(estrella.angle) * delta_time;
|
||||||
float dy = velocitat * std::sin(estrella.angle) * delta_time;
|
float dy = velocity * std::sin(estrella.angle) * delta_time;
|
||||||
|
|
||||||
estrella.posicio.x += dx;
|
estrella.position.x += dx;
|
||||||
estrella.posicio.y += dy;
|
estrella.position.y += dy;
|
||||||
|
|
||||||
// Actualitzar distància del centre
|
// Actualitzar distancia del centro
|
||||||
float dx_centre = estrella.posicio.x - punt_fuga_.x;
|
float dx_centre = estrella.position.x - punt_fuga_.x;
|
||||||
float dy_centre = estrella.posicio.y - punt_fuga_.y;
|
float dy_centre = estrella.position.y - punt_fuga_.y;
|
||||||
float dist_px = std::sqrt((dx_centre * dx_centre) + (dy_centre * dy_centre));
|
float dist_px = std::sqrt((dx_centre * dx_centre) + (dy_centre * dy_centre));
|
||||||
estrella.distancia_centre = dist_px / radi_max_;
|
estrella.distancia_centre = dist_px / radi_max_;
|
||||||
|
|
||||||
@@ -144,26 +144,25 @@ void Starfield::set_brightness(float multiplier) {
|
|||||||
multiplicador_brightness_ = std::max(0.0F, multiplier); // Evitar valors negatius
|
multiplicador_brightness_ = std::max(0.0F, multiplier); // Evitar valors negatius
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dibuixar totes les estrelles
|
// Dibuixar todas las estrelles
|
||||||
void Starfield::dibuixar() {
|
void Starfield::draw() {
|
||||||
if (!shape_estrella_->es_valida()) {
|
if (!shape_estrella_->isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& estrella : estrelles_) {
|
for (const auto& estrella : estrelles_) {
|
||||||
// Calcular escala i brightness dinàmicament
|
// Calcular scale i brightness dinàmicament
|
||||||
float escala = calcular_escala(estrella);
|
float scale = calcular_escala(estrella);
|
||||||
float brightness = calcular_brightness(estrella);
|
float brightness = calcular_brightness(estrella);
|
||||||
|
|
||||||
// Renderitzar estrella sense rotació
|
// Renderizar estrella sin rotación
|
||||||
Rendering::render_shape(
|
Rendering::render_shape(
|
||||||
renderer_,
|
renderer_,
|
||||||
shape_estrella_,
|
shape_estrella_,
|
||||||
estrella.posicio,
|
estrella.position,
|
||||||
0.0F, // angle (les estrelles no giren)
|
0.0F, // angle (las estrelles no giren)
|
||||||
escala, // escala dinàmica
|
scale, // scale dinàmica
|
||||||
true, // dibuixar
|
1.0F, // progress (siempre visible)
|
||||||
1.0F, // progress (sempre visible)
|
|
||||||
brightness // brightness dinàmica
|
brightness // brightness dinàmica
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// starfield.hpp - Sistema d'estrelles de fons amb efecte de profunditat
|
// starfield.hpp - Sistema de estrelles de fons con efecte de profunditat
|
||||||
// © 2025 Orni Attack
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -13,70 +15,70 @@
|
|||||||
|
|
||||||
namespace Graphics {
|
namespace Graphics {
|
||||||
|
|
||||||
// Configuració per cada capa de profunditat
|
// Configuración per cada capa de profunditat
|
||||||
struct CapaConfig {
|
struct CapaConfig {
|
||||||
float velocitat_base; // Velocitat base d'aquesta capa (px/s)
|
float velocitat_base; // Velocidad base de esta capa (px/s)
|
||||||
float escala_min; // Escala mínima prop del centre
|
float escala_min; // Escala mínima prop del centro
|
||||||
float escala_max; // Escala màxima al límit de pantalla
|
float escala_max; // Escala màxima al límit de pantalla
|
||||||
int num_estrelles; // Nombre d'estrelles en aquesta capa
|
int num_estrelles; // Nombre de estrelles en esta capa
|
||||||
};
|
};
|
||||||
|
|
||||||
// Classe Starfield - camp d'estrelles animat amb efecte de profunditat
|
// Clase Starfield - camp de estrelles animat con efecte de profunditat
|
||||||
class Starfield {
|
class Starfield {
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
// - renderer: SDL renderer
|
// - renderer: SDL renderer
|
||||||
// - punt_fuga: punt d'origen/fuga des d'on surten les estrelles
|
// - punt_fuga: point de origin/fuga des de on surten las estrelles
|
||||||
// - area: rectangle on actuen les estrelles (SDL_FRect)
|
// - area: rectangle on actuen las estrelles (SDL_FRect)
|
||||||
// - densitat: nombre total d'estrelles (es divideix entre capes)
|
// - densitat: nombre total de estrelles (es divideix entre capes)
|
||||||
Starfield(SDL_Renderer* renderer,
|
Starfield(Rendering::Renderer* renderer,
|
||||||
const Punt& punt_fuga,
|
const Vec2& punt_fuga,
|
||||||
const SDL_FRect& area,
|
const SDL_FRect& area,
|
||||||
int densitat = 150);
|
int densitat = 150);
|
||||||
|
|
||||||
// Actualitzar posicions de les estrelles
|
// Actualitzar posicions de las estrelles
|
||||||
void actualitzar(float delta_time);
|
void update(float delta_time);
|
||||||
|
|
||||||
// Dibuixar totes les estrelles
|
// Dibuixar todas las estrelles
|
||||||
void dibuixar();
|
void draw();
|
||||||
|
|
||||||
// Setters per ajustar paràmetres en temps real
|
// Setters per ajustar parámetros en time real
|
||||||
void set_punt_fuga(const Punt& punt) { punt_fuga_ = punt; }
|
void set_punt_fuga(const Vec2& point) { punt_fuga_ = point; }
|
||||||
void set_brightness(float multiplier);
|
void set_brightness(float multiplier);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Estructura interna per cada estrella
|
// Estructura interna per cada estrella
|
||||||
struct Estrella {
|
struct Estrella {
|
||||||
Punt posicio; // Posició actual
|
Vec2 position; // Posición actual
|
||||||
float angle; // Angle de moviment (radians)
|
float angle; // Angle de movement (radians)
|
||||||
float distancia_centre; // Distància normalitzada del centre (0.0-1.0)
|
float distancia_centre; // Distancia normalitzada del centro (0.0-1.0)
|
||||||
int capa; // Índex de capa (0=lluny, 1=mitjà, 2=prop)
|
int capa; // Índex de capa (0=lluny, 1=mitjà, 2=prop)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inicialitzar una estrella (nova o regenerada)
|
// Inicialitzar una estrella (nueva o regenerada)
|
||||||
void inicialitzar_estrella(Estrella& estrella) const;
|
void inicialitzar_estrella(Estrella& estrella) const;
|
||||||
|
|
||||||
// Verificar si una estrella està fora de l'àrea
|
// Verificar si una estrella está fuera de l'àrea
|
||||||
[[nodiscard]] bool fora_area(const Estrella& estrella) const;
|
[[nodiscard]] bool fora_area(const Estrella& estrella) const;
|
||||||
|
|
||||||
// Calcular escala dinàmica segons distància del centre
|
// Calcular scale dinàmica segons distancia del centro
|
||||||
[[nodiscard]] float calcular_escala(const Estrella& estrella) const;
|
[[nodiscard]] float calcular_escala(const Estrella& estrella) const;
|
||||||
|
|
||||||
// Calcular brightness dinàmica segons distància del centre
|
// Calcular brightness dinàmica segons distancia del centro
|
||||||
[[nodiscard]] float calcular_brightness(const Estrella& estrella) const;
|
[[nodiscard]] float calcular_brightness(const Estrella& estrella) const;
|
||||||
|
|
||||||
// Dades
|
// Dades
|
||||||
std::vector<Estrella> estrelles_;
|
std::vector<Estrella> estrelles_;
|
||||||
std::vector<CapaConfig> capes_; // Configuració de les 3 capes
|
std::vector<CapaConfig> capes_; // Configuración de las 3 capes
|
||||||
std::shared_ptr<Shape> shape_estrella_;
|
std::shared_ptr<Shape> shape_estrella_;
|
||||||
SDL_Renderer* renderer_;
|
Rendering::Renderer* renderer_;
|
||||||
|
|
||||||
// Configuració
|
// Configuración
|
||||||
Punt punt_fuga_; // Punt d'origen de les estrelles
|
Vec2 punt_fuga_; // Vec2 de origin de las estrelles
|
||||||
SDL_FRect area_; // Àrea activa
|
SDL_FRect area_; // Àrea activa
|
||||||
float radi_max_; // Distància màxima del centre al límit de pantalla
|
float radi_max_; // Distancia màxima del centro al límit de pantalla
|
||||||
int densitat_; // Nombre total d'estrelles
|
int densitat_; // Nombre total de estrelles
|
||||||
float multiplicador_brightness_{1.0F}; // Multiplicador de brillantor (1.0 = default)
|
float multiplicador_brightness_{1.0F}; // Multiplicador de brightness (1.0 = default)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Graphics
|
} // namespace Graphics
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// vector_text.cpp - Implementació del sistema de text vectorial
|
// vector_text.cpp - Implementació del sistema de text vectorial
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
// Test pre-commit hook
|
// Test pre-commit hook
|
||||||
|
|
||||||
#include "core/graphics/vector_text.hpp"
|
#include "core/graphics/vector_text.hpp"
|
||||||
@@ -11,11 +11,11 @@
|
|||||||
|
|
||||||
namespace Graphics {
|
namespace Graphics {
|
||||||
|
|
||||||
// Constants per a mides base dels caràcters
|
// Constants para mides base dels caràcters
|
||||||
constexpr float char_width = 20.0F; // Amplada base del caràcter
|
constexpr float char_width = 20.0F; // Amplada base del caràcter
|
||||||
constexpr float char_height = 40.0F; // Altura base del caràcter
|
constexpr float char_height = 40.0F; // Altura base del caràcter
|
||||||
|
|
||||||
VectorText::VectorText(SDL_Renderer* renderer)
|
VectorText::VectorText(Rendering::Renderer* renderer)
|
||||||
: renderer_(renderer) {
|
: renderer_(renderer) {
|
||||||
load_charset();
|
load_charset();
|
||||||
}
|
}
|
||||||
@@ -26,10 +26,10 @@ void VectorText::load_charset() {
|
|||||||
std::string filename = get_shape_filename(c);
|
std::string filename = get_shape_filename(c);
|
||||||
auto shape = ShapeLoader::load(filename);
|
auto shape = ShapeLoader::load(filename);
|
||||||
|
|
||||||
if (shape && shape->es_valida()) {
|
if (shape && shape->isValid()) {
|
||||||
chars_[c] = shape;
|
chars_[c] = shape;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[VectorText] Warning: no s'ha pogut carregar " << filename
|
std::cerr << "[VectorText] Warning: no s'ha pogut load " << filename
|
||||||
<< '\n';
|
<< '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,10 @@ void VectorText::load_charset() {
|
|||||||
std::string filename = get_shape_filename(c);
|
std::string filename = get_shape_filename(c);
|
||||||
auto shape = ShapeLoader::load(filename);
|
auto shape = ShapeLoader::load(filename);
|
||||||
|
|
||||||
if (shape && shape->es_valida()) {
|
if (shape && shape->isValid()) {
|
||||||
chars_[c] = shape;
|
chars_[c] = shape;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[VectorText] Warning: no s'ha pogut carregar " << filename
|
std::cerr << "[VectorText] Warning: no s'ha pogut load " << filename
|
||||||
<< '\n';
|
<< '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,25 +54,25 @@ void VectorText::load_charset() {
|
|||||||
std::string filename = get_shape_filename(c);
|
std::string filename = get_shape_filename(c);
|
||||||
auto shape = ShapeLoader::load(filename);
|
auto shape = ShapeLoader::load(filename);
|
||||||
|
|
||||||
if (shape && shape->es_valida()) {
|
if (shape && shape->isValid()) {
|
||||||
chars_[c] = shape;
|
chars_[c] = shape;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[VectorText] Warning: no s'ha pogut carregar " << filename
|
std::cerr << "[VectorText] Warning: no s'ha pogut load " << filename
|
||||||
<< '\n';
|
<< '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cargar símbolo de copyright (©) - UTF-8 U+00A9
|
// Cargar símbolo de copyright (©) - UTF-8 U+00A9
|
||||||
// Usem el segon byte (0xA9) com a key interna
|
// Usem el segon byte (0xA9) como a key interna
|
||||||
{
|
{
|
||||||
char c = '\xA9'; // 169 decimal
|
char c = '\xA9'; // 169 decimal
|
||||||
std::string filename = "font/char_copyright.shp";
|
std::string filename = "font/char_copyright.shp";
|
||||||
auto shape = ShapeLoader::load(filename);
|
auto shape = ShapeLoader::load(filename);
|
||||||
|
|
||||||
if (shape && shape->es_valida()) {
|
if (shape && shape->isValid()) {
|
||||||
chars_[c] = shape;
|
chars_[c] = shape;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[VectorText] Warning: no s'ha pogut carregar " << filename
|
std::cerr << "[VectorText] Warning: no s'ha pogut load " << filename
|
||||||
<< '\n';
|
<< '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ void VectorText::load_charset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string VectorText::get_shape_filename(char c) const {
|
std::string VectorText::get_shape_filename(char c) const {
|
||||||
// Mapeo carácter → nombre de archivo (amb prefix "font/")
|
// Mapeo carácter → nombre de archivo (con prefix "font/")
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '0':
|
case '0':
|
||||||
case '1':
|
case '1':
|
||||||
@@ -168,7 +168,7 @@ std::string VectorText::get_shape_filename(char c) const {
|
|||||||
case '?':
|
case '?':
|
||||||
return "font/char_question.shp";
|
return "font/char_question.shp";
|
||||||
case ' ':
|
case ' ':
|
||||||
return ""; // Espai es maneja sense carregar shape
|
return ""; // Espai es maneja sin load shape
|
||||||
|
|
||||||
case '\xA9': // Copyright symbol (©) - UTF-8 U+00A9
|
case '\xA9': // Copyright symbol (©) - UTF-8 U+00A9
|
||||||
return "font/char_copyright.shp";
|
return "font/char_copyright.shp";
|
||||||
@@ -182,23 +182,23 @@ bool VectorText::is_supported(char c) const {
|
|||||||
return chars_.contains(c);
|
return chars_.contains(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VectorText::render(const std::string& text, const Punt& posicio, float escala, float spacing, float brightness) const {
|
void VectorText::render(const std::string& text, const Vec2& position, float scale, float spacing, float brightness) const {
|
||||||
if (renderer_ == nullptr) {
|
if (renderer_ == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ancho de un carácter base (20 px a escala 1.0)
|
// Ancho de un carácter base (20 px a scale 1.0)
|
||||||
const float char_width_scaled = char_width * escala;
|
const float char_width_scaled = char_width * scale;
|
||||||
|
|
||||||
// Spacing escalado
|
// Spacing escalado
|
||||||
const float spacing_scaled = spacing * escala;
|
const float spacing_scaled = spacing * scale;
|
||||||
|
|
||||||
// Altura de un carácter escalado (necesario para ajustar Y)
|
// Altura de un carácter escalado (necesario para ajustar Y)
|
||||||
const float char_height_scaled = char_height * escala;
|
const float char_height_scaled = char_height * scale;
|
||||||
|
|
||||||
// Posición X del borde izquierdo del carácter actual
|
// Posición X del borde izquierdo del carácter actual
|
||||||
// (se ajustará +char_width/2 para obtener el centro al renderizar)
|
// (se ajustará +char_width/2 para obtener el centro al renderizar)
|
||||||
float current_x = posicio.x;
|
float current_x = position.x;
|
||||||
|
|
||||||
// Iterar sobre cada byte del string (con detecció UTF-8)
|
// Iterar sobre cada byte del string (con detecció UTF-8)
|
||||||
for (size_t i = 0; i < text.length(); i++) {
|
for (size_t i = 0; i < text.length(); i++) {
|
||||||
@@ -207,7 +207,7 @@ void VectorText::render(const std::string& text, const Punt& posicio, float esca
|
|||||||
// Detectar copyright UTF-8 (0xC2 0xA9)
|
// Detectar copyright UTF-8 (0xC2 0xA9)
|
||||||
if (c == 0xC2 && i + 1 < text.length() &&
|
if (c == 0xC2 && i + 1 < text.length() &&
|
||||||
static_cast<unsigned char>(text[i + 1]) == 0xA9) {
|
static_cast<unsigned char>(text[i + 1]) == 0xA9) {
|
||||||
c = 0xA9; // Usar segon byte com a key
|
c = 0xA9; // Usar segon byte como a key
|
||||||
i++; // Saltar el següent byte
|
i++; // Saltar el següent byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,10 +221,10 @@ void VectorText::render(const std::string& text, const Punt& posicio, float esca
|
|||||||
auto it = chars_.find(c);
|
auto it = chars_.find(c);
|
||||||
if (it != chars_.end()) {
|
if (it != chars_.end()) {
|
||||||
// Renderizar carácter
|
// Renderizar carácter
|
||||||
// Ajustar X e Y para que posicio represente esquina superior izquierda
|
// Ajustar X e Y para que position represente esquina superior izquierda
|
||||||
// (render_shape espera el centro, así que sumamos la mitad de ancho y altura)
|
// (render_shape espera el centro, así que sumamos la mitad de ancho y altura)
|
||||||
Punt char_pos = {.x = current_x + (char_width_scaled / 2.0F), .y = posicio.y + (char_height_scaled / 2.0F)};
|
Vec2 char_pos = {.x = current_x + (char_width_scaled / 2.0F), .y = position.y + (char_height_scaled / 2.0F)};
|
||||||
Rendering::render_shape(renderer_, it->second, char_pos, 0.0F, escala, true, 1.0F, brightness);
|
Rendering::render_shape(renderer_, it->second, char_pos, 0.0F, scale, 1.0F, brightness);
|
||||||
|
|
||||||
// Avanzar posición
|
// Avanzar posición
|
||||||
current_x += char_width_scaled + spacing_scaled;
|
current_x += char_width_scaled + spacing_scaled;
|
||||||
@@ -237,28 +237,28 @@ void VectorText::render(const std::string& text, const Punt& posicio, float esca
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VectorText::render_centered(const std::string& text, const Punt& centre_punt, float escala, float spacing, float brightness) const {
|
void VectorText::renderCentered(const std::string& text, const Vec2& centre_punt, float scale, float spacing, float brightness) const {
|
||||||
// Calcular dimensions del text
|
// Calcular dimensions del text
|
||||||
float text_width = get_text_width(text, escala, spacing);
|
float text_width = get_text_width(text, scale, spacing);
|
||||||
float text_height = get_text_height(escala);
|
float text_height = get_text_height(scale);
|
||||||
|
|
||||||
// Calcular posició de l'esquina superior esquerra
|
// Calcular posición de l'esquina superior izquierda
|
||||||
// restant la meitat de les dimensions del punt central
|
// restant la meitat de las dimensions del point central
|
||||||
Punt posicio_esquerra = {
|
Vec2 posicio_esquerra = {
|
||||||
.x = centre_punt.x - (text_width / 2.0F),
|
.x = centre_punt.x - (text_width / 2.0F),
|
||||||
.y = centre_punt.y - (text_height / 2.0F)};
|
.y = centre_punt.y - (text_height / 2.0F)};
|
||||||
|
|
||||||
// Delegar al mètode render() existent
|
// Delegar al método render() existent
|
||||||
render(text, posicio_esquerra, escala, spacing, brightness);
|
render(text, posicio_esquerra, scale, spacing, brightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
float VectorText::get_text_width(const std::string& text, float escala, float spacing) const {
|
float VectorText::get_text_width(const std::string& text, float scale, float spacing) const {
|
||||||
if (text.empty()) {
|
if (text.empty()) {
|
||||||
return 0.0F;
|
return 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float char_width_scaled = char_width * escala;
|
const float char_width_scaled = char_width * scale;
|
||||||
const float spacing_scaled = spacing * escala;
|
const float spacing_scaled = spacing * scale;
|
||||||
|
|
||||||
// Contar caracteres visuals (no bytes) - manejar UTF-8
|
// Contar caracteres visuals (no bytes) - manejar UTF-8
|
||||||
size_t visual_chars = 0;
|
size_t visual_chars = 0;
|
||||||
@@ -279,8 +279,8 @@ float VectorText::get_text_width(const std::string& text, float escala, float sp
|
|||||||
return (visual_chars * char_width_scaled) + ((visual_chars - 1) * spacing_scaled);
|
return (visual_chars * char_width_scaled) + ((visual_chars - 1) * spacing_scaled);
|
||||||
}
|
}
|
||||||
|
|
||||||
float VectorText::get_text_height(float escala) const {
|
float VectorText::get_text_height(float scale) const {
|
||||||
return char_height * escala;
|
return char_height * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Graphics
|
} // namespace Graphics
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// vector_text.hpp - Sistema de texto vectorial con display de 7-segmentos
|
// vector_text.hpp - Sistema de texto vectorial con display de 7-segmentos
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -16,36 +18,36 @@ namespace Graphics {
|
|||||||
|
|
||||||
class VectorText {
|
class VectorText {
|
||||||
public:
|
public:
|
||||||
VectorText(SDL_Renderer* renderer);
|
VectorText(Rendering::Renderer* renderer);
|
||||||
|
|
||||||
// Renderizar string completo
|
// Renderizar string completo
|
||||||
// - text: cadena a renderizar (soporta: A-Z, a-z, 0-9, '.', ',', '-', ':',
|
// - text: cadena a renderizar (soporta: A-Z, a-z, 0-9, '.', ',', '-', ':',
|
||||||
// '!', '?', ' ')
|
// '!', '?', ' ')
|
||||||
// - posicio: posición inicial (esquina superior izquierda)
|
// - position: posición inicial (esquina superior izquierda)
|
||||||
// - escala: factor de escala (1.0 = 20×40 px por carácter)
|
// - scale: factor de scale (1.0 = 20×40 px por carácter)
|
||||||
// - spacing: espacio entre caracteres en píxeles (a escala 1.0)
|
// - spacing: espacio entre caracteres en píxeles (a scale 1.0)
|
||||||
// - brightness: factor de brillantor (0.0-1.0, default 1.0 = màxima brillantor)
|
// - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness)
|
||||||
void render(const std::string& text, const Punt& posicio, float escala = 1.0F, float spacing = 2.0F, float brightness = 1.0F) const;
|
void render(const std::string& text, const Vec2& position, float scale = 1.0F, float spacing = 2.0F, float brightness = 1.0F) const;
|
||||||
|
|
||||||
// Renderizar string centrado en un punto
|
// Renderizar string centrado en un punto
|
||||||
// - text: cadena a renderizar
|
// - text: cadena a renderizar
|
||||||
// - centre_punt: punto central del texto (no esquina superior izquierda)
|
// - centre_punt: punto central del texto (no esquina superior izquierda)
|
||||||
// - escala: factor de escala (1.0 = 20×40 px por carácter)
|
// - scale: factor de scale (1.0 = 20×40 px por carácter)
|
||||||
// - spacing: espacio entre caracteres en píxeles (a escala 1.0)
|
// - spacing: espacio entre caracteres en píxeles (a scale 1.0)
|
||||||
// - brightness: factor de brillantor (0.0-1.0, default 1.0 = màxima brillantor)
|
// - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness)
|
||||||
void render_centered(const std::string& text, const Punt& centre_punt, float escala = 1.0F, float spacing = 2.0F, float brightness = 1.0F) const;
|
void renderCentered(const std::string& text, const Vec2& centre_punt, float scale = 1.0F, float spacing = 2.0F, float brightness = 1.0F) const;
|
||||||
|
|
||||||
// Calcular ancho total de un string (útil para centrado)
|
// Calcular ancho total de un string (útil para centrado)
|
||||||
[[nodiscard]] float get_text_width(const std::string& text, float escala = 1.0F, float spacing = 2.0F) const;
|
[[nodiscard]] float get_text_width(const std::string& text, float scale = 1.0F, float spacing = 2.0F) const;
|
||||||
|
|
||||||
// Calcular altura del texto (útil para centrado vertical)
|
// Calcular altura del texto (útil para centrado vertical)
|
||||||
[[nodiscard]] float get_text_height(float escala = 1.0F) const;
|
[[nodiscard]] float get_text_height(float scale = 1.0F) const;
|
||||||
|
|
||||||
// Verificar si un carácter está soportado
|
// Verificar si un carácter está soportado
|
||||||
[[nodiscard]] bool is_supported(char c) const;
|
[[nodiscard]] bool is_supported(char c) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_Renderer* renderer_;
|
Rendering::Renderer* renderer_;
|
||||||
std::unordered_map<char, std::shared_ptr<Shape>> chars_;
|
std::unordered_map<char, std::shared_ptr<Shape>> chars_;
|
||||||
|
|
||||||
void load_charset();
|
void load_charset();
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Input::Input(std::string game_controller_db_path)
|
|||||||
// Inicializar bindings del teclado (valores por defecto)
|
// Inicializar bindings del teclado (valores por defecto)
|
||||||
// Estos serán sobrescritos por applyPlayer1BindingsFromOptions()
|
// Estos serán sobrescritos por applyPlayer1BindingsFromOptions()
|
||||||
keyboard_.bindings = {
|
keyboard_.bindings = {
|
||||||
// Movimiento del jugador
|
// Movimiento del player
|
||||||
{Action::LEFT, KeyState{.scancode = SDL_SCANCODE_LEFT}},
|
{Action::LEFT, KeyState{.scancode = SDL_SCANCODE_LEFT}},
|
||||||
{Action::RIGHT, KeyState{.scancode = SDL_SCANCODE_RIGHT}},
|
{Action::RIGHT, KeyState{.scancode = SDL_SCANCODE_RIGHT}},
|
||||||
{Action::THRUST, KeyState{.scancode = SDL_SCANCODE_UP}},
|
{Action::THRUST, KeyState{.scancode = SDL_SCANCODE_UP}},
|
||||||
@@ -188,7 +188,7 @@ auto Input::checkAnyButton(bool repeat) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba si algún jugador (P1 o P2) presionó alguna acción de una lista
|
// Comprueba si algún player (P1 o P2) presionó alguna acción de una lista
|
||||||
auto Input::checkAnyPlayerAction(const std::span<const InputAction>& actions, bool repeat) -> bool {
|
auto Input::checkAnyPlayerAction(const std::span<const InputAction>& actions, bool repeat) -> bool {
|
||||||
for (const auto& action : actions) {
|
for (const auto& action : actions) {
|
||||||
if (checkActionPlayer1(action, repeat) || checkActionPlayer2(action, repeat)) {
|
if (checkActionPlayer1(action, repeat) || checkActionPlayer2(action, repeat)) {
|
||||||
@@ -388,14 +388,14 @@ void Input::update() {
|
|||||||
binding.second.is_held = key_is_down_now;
|
binding.second.is_held = key_is_down_now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar bindings de jugador 1
|
// Actualizar bindings de player 1
|
||||||
for (auto& binding : player1_keyboard_bindings_) {
|
for (auto& binding : player1_keyboard_bindings_) {
|
||||||
bool key_is_down_now = key_states[binding.second.scancode];
|
bool key_is_down_now = key_states[binding.second.scancode];
|
||||||
binding.second.just_pressed = key_is_down_now && !binding.second.is_held;
|
binding.second.just_pressed = key_is_down_now && !binding.second.is_held;
|
||||||
binding.second.is_held = key_is_down_now;
|
binding.second.is_held = key_is_down_now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar bindings de jugador 2
|
// Actualizar bindings de player 2
|
||||||
for (auto& binding : player2_keyboard_bindings_) {
|
for (auto& binding : player2_keyboard_bindings_) {
|
||||||
bool key_is_down_now = key_states[binding.second.scancode];
|
bool key_is_down_now = key_states[binding.second.scancode];
|
||||||
binding.second.just_pressed = key_is_down_now && !binding.second.is_held;
|
binding.second.just_pressed = key_is_down_now && !binding.second.is_held;
|
||||||
@@ -493,7 +493,7 @@ auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std::
|
|||||||
|
|
||||||
// ========== MÉTODOS ESPECÍFICOS POR JUGADOR (ORNI) ==========
|
// ========== MÉTODOS ESPECÍFICOS POR JUGADOR (ORNI) ==========
|
||||||
|
|
||||||
// Aplica configuración de controles del jugador 1
|
// Aplica configuración de controles del player 1
|
||||||
void Input::applyPlayer1BindingsFromOptions() {
|
void Input::applyPlayer1BindingsFromOptions() {
|
||||||
// 1. Aplicar bindings de teclado (NO usar bindKey, llenar mapa específico)
|
// 1. Aplicar bindings de teclado (NO usar bindKey, llenar mapa específico)
|
||||||
player1_keyboard_bindings_[Action::LEFT].scancode = Options::player1.keyboard.key_left;
|
player1_keyboard_bindings_[Action::LEFT].scancode = Options::player1.keyboard.key_left;
|
||||||
@@ -527,7 +527,7 @@ void Input::applyPlayer1BindingsFromOptions() {
|
|||||||
player1_gamepad_ = gamepad;
|
player1_gamepad_ = gamepad;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aplica configuración de controles del jugador 2
|
// Aplica configuración de controles del player 2
|
||||||
void Input::applyPlayer2BindingsFromOptions() {
|
void Input::applyPlayer2BindingsFromOptions() {
|
||||||
// 1. Aplicar bindings de teclado (mapa específico de P2, no sobrescribe P1)
|
// 1. Aplicar bindings de teclado (mapa específico de P2, no sobrescribe P1)
|
||||||
player2_keyboard_bindings_[Action::LEFT].scancode = Options::player2.keyboard.key_left;
|
player2_keyboard_bindings_[Action::LEFT].scancode = Options::player2.keyboard.key_left;
|
||||||
@@ -561,7 +561,7 @@ void Input::applyPlayer2BindingsFromOptions() {
|
|||||||
player2_gamepad_ = gamepad;
|
player2_gamepad_ = gamepad;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consulta de input para jugador 1
|
// Consulta de input para player 1
|
||||||
auto Input::checkActionPlayer1(Action action, bool repeat) -> bool {
|
auto Input::checkActionPlayer1(Action action, bool repeat) -> bool {
|
||||||
// Comprobar teclado con el mapa específico de P1
|
// Comprobar teclado con el mapa específico de P1
|
||||||
bool keyboard_active = false;
|
bool keyboard_active = false;
|
||||||
@@ -583,7 +583,7 @@ auto Input::checkActionPlayer1(Action action, bool repeat) -> bool {
|
|||||||
return keyboard_active || gamepad_active;
|
return keyboard_active || gamepad_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consulta de input para jugador 2
|
// Consulta de input para player 2
|
||||||
auto Input::checkActionPlayer2(Action action, bool repeat) -> bool {
|
auto Input::checkActionPlayer2(Action action, bool repeat) -> bool {
|
||||||
// Comprobar teclado con el mapa específico de P2
|
// Comprobar teclado con el mapa específico de P2
|
||||||
bool keyboard_active = false;
|
bool keyboard_active = false;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class Input {
|
|||||||
name(std::string(SDL_GetGamepadName(gamepad))),
|
name(std::string(SDL_GetGamepadName(gamepad))),
|
||||||
path(std::string(SDL_GetGamepadPath(pad))),
|
path(std::string(SDL_GetGamepadPath(pad))),
|
||||||
bindings{
|
bindings{
|
||||||
// Movimiento y acciones del jugador
|
// Movimiento y acciones del player
|
||||||
{Action::LEFT, ButtonState{.button = static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_LEFT)}},
|
{Action::LEFT, ButtonState{.button = static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_LEFT)}},
|
||||||
{Action::RIGHT, ButtonState{.button = static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_RIGHT)}},
|
{Action::RIGHT, ButtonState{.button = static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_RIGHT)}},
|
||||||
{Action::THRUST, ButtonState{.button = static_cast<int>(SDL_GAMEPAD_BUTTON_WEST)}},
|
{Action::THRUST, ButtonState{.button = static_cast<int>(SDL_GAMEPAD_BUTTON_WEST)}},
|
||||||
@@ -92,7 +92,7 @@ class Input {
|
|||||||
void applyKeyboardBindingsFromOptions();
|
void applyKeyboardBindingsFromOptions();
|
||||||
void applyGamepadBindingsFromOptions();
|
void applyGamepadBindingsFromOptions();
|
||||||
|
|
||||||
// Configuración por jugador (Orni - dos jugadores)
|
// Configuración por player (Orni - dos jugadores)
|
||||||
void applyPlayer1BindingsFromOptions();
|
void applyPlayer1BindingsFromOptions();
|
||||||
void applyPlayer2BindingsFromOptions();
|
void applyPlayer2BindingsFromOptions();
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class Input {
|
|||||||
auto checkAnyButton(bool repeat = DO_NOT_ALLOW_REPEAT) -> bool;
|
auto checkAnyButton(bool repeat = DO_NOT_ALLOW_REPEAT) -> bool;
|
||||||
void resetInputStates();
|
void resetInputStates();
|
||||||
|
|
||||||
// Consulta por jugador (Orni - dos jugadores)
|
// Consulta por player (Orni - dos jugadores)
|
||||||
auto checkActionPlayer1(Action action, bool repeat = true) -> bool;
|
auto checkActionPlayer1(Action action, bool repeat = true) -> bool;
|
||||||
auto checkActionPlayer2(Action action, bool repeat = true) -> bool;
|
auto checkActionPlayer2(Action action, bool repeat = true) -> bool;
|
||||||
|
|
||||||
@@ -152,11 +152,11 @@ class Input {
|
|||||||
Keyboard keyboard_{}; // Estado del teclado (solo acciones globales)
|
Keyboard keyboard_{}; // Estado del teclado (solo acciones globales)
|
||||||
std::string gamepad_mappings_file_; // Ruta al archivo de mappings
|
std::string gamepad_mappings_file_; // Ruta al archivo de mappings
|
||||||
|
|
||||||
// Referencias cacheadas a gamepads por jugador (Orni)
|
// Referencias cacheadas a gamepads por player (Orni)
|
||||||
std::shared_ptr<Gamepad> player1_gamepad_;
|
std::shared_ptr<Gamepad> player1_gamepad_;
|
||||||
std::shared_ptr<Gamepad> player2_gamepad_;
|
std::shared_ptr<Gamepad> player2_gamepad_;
|
||||||
|
|
||||||
// Mapas de bindings separados por jugador (Orni - dos jugadores)
|
// Mapas de bindings separados por player (Orni - dos jugadores)
|
||||||
std::unordered_map<Action, KeyState> player1_keyboard_bindings_;
|
std::unordered_map<Action, KeyState> player1_keyboard_bindings_;
|
||||||
std::unordered_map<Action, KeyState> player2_keyboard_bindings_;
|
std::unordered_map<Action, KeyState> player2_keyboard_bindings_;
|
||||||
};
|
};
|
||||||
@@ -13,7 +13,7 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
|
|||||||
RIGHT, // Rotar derecha
|
RIGHT, // Rotar derecha
|
||||||
THRUST, // Acelerar
|
THRUST, // Acelerar
|
||||||
SHOOT, // Disparar
|
SHOOT, // Disparar
|
||||||
START, // Empezar partida
|
START, // Empezar match
|
||||||
|
|
||||||
// Inputs de sistema (globales)
|
// Inputs de sistema (globales)
|
||||||
WINDOW_INC_ZOOM, // F2
|
WINDOW_INC_ZOOM, // F2
|
||||||
|
|||||||
@@ -12,19 +12,19 @@ bool cursor_visible = false; // Estado del cursor (inicia ocult)
|
|||||||
// SDLManager controla esto mediante llamadas a setForceHidden().
|
// SDLManager controla esto mediante llamadas a setForceHidden().
|
||||||
bool force_hidden = false;
|
bool force_hidden = false;
|
||||||
|
|
||||||
// Temps d'inicialització per ignorar esdeveniments fantasma de SDL
|
// Temps de inicialización per ignorar esdeveniments fantasma de SDL
|
||||||
Uint32 initialization_time = 0;
|
Uint32 initialization_time = 0;
|
||||||
constexpr Uint32 IGNORE_MOTION_DURATION = 1000; // Ignorar primers 1000ms
|
constexpr Uint32 IGNORE_MOTION_DURATION = 1000; // Ignorar primers 1000ms
|
||||||
|
|
||||||
void forceHide() {
|
void forceHide() {
|
||||||
// Forçar ocultació sincronitzant estat SDL i estat intern
|
// Forçar ocultació sincronitzant state SDL i state intern
|
||||||
std::cout << "[Mouse::forceHide] Ocultant cursor i sincronitzant estat. cursor_visible=" << cursor_visible
|
std::cout << "[Mouse::forceHide] Ocultant cursor i sincronitzant state. cursor_visible=" << cursor_visible
|
||||||
<< " -> false" << '\n';
|
<< " -> false" << '\n';
|
||||||
SDL_HideCursor();
|
SDL_HideCursor();
|
||||||
cursor_visible = false;
|
cursor_visible = false;
|
||||||
last_mouse_move_time = 0;
|
last_mouse_move_time = 0;
|
||||||
initialization_time = SDL_GetTicks(); // Marcar temps per ignorar esdeveniments inicials
|
initialization_time = SDL_GetTicks(); // Marcar time per ignorar esdeveniments inicials
|
||||||
std::cout << "[Mouse::forceHide] Ignorant moviments durant " << IGNORE_MOTION_DURATION << "ms" << '\n';
|
std::cout << "[Mouse::forceHide] Ignorant moviments durante " << IGNORE_MOTION_DURATION << "ms" << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
void setForceHidden(bool force) {
|
void setForceHidden(bool force) {
|
||||||
@@ -56,16 +56,16 @@ void handleEvent(const SDL_Event& event) {
|
|||||||
if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
||||||
Uint32 current_time = SDL_GetTicks();
|
Uint32 current_time = SDL_GetTicks();
|
||||||
|
|
||||||
// Ignorar esdeveniments fantasma de SDL durant el període inicial
|
// Ignorar esdeveniments fantasma de SDL durante el període inicial
|
||||||
if (initialization_time > 0 && (current_time - initialization_time < IGNORE_MOTION_DURATION)) {
|
if (initialization_time > 0 && (current_time - initialization_time < IGNORE_MOTION_DURATION)) {
|
||||||
std::cout << "[Mouse::handleEvent] Ignorant moviment fantasma de SDL. time=" << current_time
|
std::cout << "[Mouse::handleEvent] Ignorant movement fantasma de SDL. time=" << current_time
|
||||||
<< " (inicialització fa " << (current_time - initialization_time) << "ms)" << '\n';
|
<< " (inicialización hace " << (current_time - initialization_time) << "ms)" << '\n';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_mouse_move_time = current_time;
|
last_mouse_move_time = current_time;
|
||||||
if (!cursor_visible) {
|
if (!cursor_visible) {
|
||||||
std::cout << "[Mouse::handleEvent] Mostrant cursor per moviment REAL. time=" << last_mouse_move_time << '\n';
|
std::cout << "[Mouse::handleEvent] Mostrant cursor per movement REAL. time=" << last_mouse_move_time << '\n';
|
||||||
SDL_ShowCursor();
|
SDL_ShowCursor();
|
||||||
cursor_visible = true;
|
cursor_visible = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ extern Uint32 cursor_hide_time; // Tiempo en milisegundos para ocultar el c
|
|||||||
extern Uint32 last_mouse_move_time; // Última vez que el ratón se movió
|
extern Uint32 last_mouse_move_time; // Última vez que el ratón se movió
|
||||||
extern bool cursor_visible; // Estado del cursor
|
extern bool cursor_visible; // Estado del cursor
|
||||||
|
|
||||||
void forceHide(); // Forçar ocultació del cursor (sincronitza estat intern)
|
void forceHide(); // Forçar ocultació del cursor (sincronitza state intern)
|
||||||
void handleEvent(const SDL_Event& event);
|
void handleEvent(const SDL_Event& event);
|
||||||
void updateCursorVisibility();
|
void updateCursorVisibility();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// easing.hpp - Funcions d'interpolació i easing
|
// easing.hpp - Funciones de interpolació i easing
|
||||||
// © 2025 Orni Attack
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -7,21 +7,21 @@ namespace Easing {
|
|||||||
|
|
||||||
// Ease-out quadratic: empieza rápido, desacelera suavemente
|
// Ease-out quadratic: empieza rápido, desacelera suavemente
|
||||||
// t = progreso normalizado [0.0 - 1.0]
|
// t = progreso normalizado [0.0 - 1.0]
|
||||||
// retorna valor interpolado [0.0 - 1.0]
|
// retorna value interpolado [0.0 - 1.0]
|
||||||
inline float ease_out_quad(float t) {
|
inline float ease_out_quad(float t) {
|
||||||
return 1.0F - ((1.0F - t) * (1.0F - t));
|
return 1.0F - ((1.0F - t) * (1.0F - t));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ease-in quadratic: empieza lento, acelera
|
// Ease-in quadratic: empieza lento, acelera
|
||||||
// t = progreso normalizado [0.0 - 1.0]
|
// t = progreso normalizado [0.0 - 1.0]
|
||||||
// retorna valor interpolado [0.0 - 1.0]
|
// retorna value interpolado [0.0 - 1.0]
|
||||||
inline float ease_in_quad(float t) {
|
inline float ease_in_quad(float t) {
|
||||||
return t * t;
|
return t * t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ease-in-out quadratic: acelera al inicio, desacelera al final
|
// Ease-in-out quadratic: acelera al inicio, desacelera al final
|
||||||
// t = progreso normalizado [0.0 - 1.0]
|
// t = progreso normalizado [0.0 - 1.0]
|
||||||
// retorna valor interpolado [0.0 - 1.0]
|
// retorna value interpolado [0.0 - 1.0]
|
||||||
inline float ease_in_out_quad(float t) {
|
inline float ease_in_out_quad(float t) {
|
||||||
return (t < 0.5F)
|
return (t < 0.5F)
|
||||||
? 2.0F * t * t
|
? 2.0F * t * t
|
||||||
@@ -30,7 +30,7 @@ inline float ease_in_out_quad(float t) {
|
|||||||
|
|
||||||
// Ease-out cubic: desaceleración más suave que quadratic
|
// Ease-out cubic: desaceleración más suave que quadratic
|
||||||
// t = progreso normalizado [0.0 - 1.0]
|
// t = progreso normalizado [0.0 - 1.0]
|
||||||
// retorna valor interpolado [0.0 - 1.0]
|
// retorna value interpolado [0.0 - 1.0]
|
||||||
inline float ease_out_cubic(float t) {
|
inline float ease_out_cubic(float t) {
|
||||||
float t1 = 1.0F - t;
|
float t1 = 1.0F - t;
|
||||||
return 1.0F - (t1 * t1 * t1);
|
return 1.0F - (t1 * t1 * t1);
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
// collision.hpp - Utilitats de detecció de col·lisions
|
// collision.hpp - Utilitats de detecció de colisiones
|
||||||
// © 2025 Orni Attack - Sistema de física
|
// © 2025 Orni Attack - Sistema de física
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/entities/entitat.hpp"
|
#include "core/entities/entity.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
|
|
||||||
namespace Physics {
|
namespace Physics {
|
||||||
|
|
||||||
// Comprovació genèrica de col·lisió entre dues entitats
|
// Comprobación genèrica de colisión entre dues entidades
|
||||||
inline bool check_collision(const Entities::Entitat& a, const Entities::Entitat& b, float amplifier = 1.0F) {
|
inline bool check_collision(const Entities::Entity& a, const Entities::Entity& b, float amplifier = 1.0F) {
|
||||||
// Comprovar si ambdós són col·lisionables
|
// Comprovar si ambdós són col·lisionables
|
||||||
if (!a.es_collidable() || !b.es_collidable()) {
|
if (!a.isCollidable() || !b.isCollidable()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular radi combinat (amb amplificador per hitbox generós)
|
// Calcular radi combinat (con amplificador per hitbox generós)
|
||||||
float suma_radis = (a.get_collision_radius() + b.get_collision_radius()) * amplifier;
|
float suma_radis = (a.getCollisionRadius() + b.getCollisionRadius()) * amplifier;
|
||||||
float suma_radis_sq = suma_radis * suma_radis;
|
float suma_radis_sq = suma_radis * suma_radis;
|
||||||
|
|
||||||
// Comprovació distància al quadrat (sense sqrt)
|
// Comprobación distancia al cuadrado (sin sqrt)
|
||||||
const Punt& pos_a = a.get_centre();
|
const Vec2& pos_a = a.getCenter();
|
||||||
const Punt& pos_b = b.get_centre();
|
const Vec2& pos_b = b.getCenter();
|
||||||
float dx = pos_a.x - pos_b.x;
|
float dx = pos_a.x - pos_b.x;
|
||||||
float dy = pos_a.y - pos_b.y;
|
float dy = pos_a.y - pos_b.y;
|
||||||
float dist_sq = (dx * dx) + (dy * dy);
|
float dist_sq = (dx * dx) + (dy * dy);
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
// physics_world.cpp - Implementación del mundo físico
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
|
#include "core/physics/physics_world.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "core/physics/rigid_body.hpp"
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (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;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Dos cuerpos estáticos no necesitan resolución
|
||||||
|
if (a->isStatic() && b->isStatic()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// physics_world.hpp - Mundo físico 2D
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Gestiona un conjunto de RigidBody, integra sus movimientos y detecta
|
||||||
|
// colisiones por frame. Diseño minimalista para arcade: broadphase trivial
|
||||||
|
// O(n²) suficiente para <50 cuerpos (15 enemigos + balas + paredes).
|
||||||
|
//
|
||||||
|
// Los RigidBody viven en las entidades (las entidades poseen sus bodies);
|
||||||
|
// PhysicsWorld solo guarda punteros no-owning. La entidad es responsable
|
||||||
|
// de añadir/quitar su body del mundo en init/destroy.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Physics {
|
||||||
|
|
||||||
|
struct RigidBody;
|
||||||
|
|
||||||
|
class PhysicsWorld {
|
||||||
|
public:
|
||||||
|
PhysicsWorld() = default;
|
||||||
|
|
||||||
|
// Añade un cuerpo al mundo (no toma ownership).
|
||||||
|
void addBody(RigidBody* body);
|
||||||
|
|
||||||
|
// Elimina un cuerpo. No-op si no está registrado.
|
||||||
|
void removeBody(RigidBody* body);
|
||||||
|
|
||||||
|
// Vacía la lista (no destruye los cuerpos).
|
||||||
|
void clear() { bodies_.clear(); }
|
||||||
|
|
||||||
|
// Define los límites del mundo (paredes implícitas). Pasa un rect
|
||||||
|
// PLAYAREA para que los cuerpos reboten contra los bordes según su
|
||||||
|
// restitution. Vacío = sin paredes.
|
||||||
|
void setBounds(const SDL_FRect& bounds) {
|
||||||
|
bounds_ = bounds;
|
||||||
|
has_bounds_ = true;
|
||||||
|
}
|
||||||
|
void clearBounds() { has_bounds_ = false; }
|
||||||
|
|
||||||
|
// Avanza la simulación dt segundos:
|
||||||
|
// 1. Integra cada cuerpo (semi-implicit Euler + damping)
|
||||||
|
// 2. Resuelve colisiones contra los bounds (si configurados)
|
||||||
|
// 3. Resuelve colisiones cuerpo-cuerpo (impulsos elásticos)
|
||||||
|
void update(float dt);
|
||||||
|
|
||||||
|
// Consultas
|
||||||
|
[[nodiscard]] auto getBodyCount() const -> std::size_t { return bodies_.size(); }
|
||||||
|
[[nodiscard]] auto getBodies() const -> const std::vector<RigidBody*>& { return bodies_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<RigidBody*> bodies_;
|
||||||
|
SDL_FRect bounds_{0.0F, 0.0F, 0.0F, 0.0F};
|
||||||
|
bool has_bounds_{false};
|
||||||
|
|
||||||
|
void integrate(float dt);
|
||||||
|
void resolveBoundsCollisions();
|
||||||
|
void resolveBodyCollisions();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Physics
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// rigid_body.hpp - Cuerpo rígido 2D para el sistema de física
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Estructura POD-like que encapsula el estado físico de una entidad:
|
||||||
|
// posición, velocidad lineal/angular, masa, restitución y damping.
|
||||||
|
// El integrador es semi-implícito de Euler (estable para juegos arcade).
|
||||||
|
//
|
||||||
|
// Convenciones:
|
||||||
|
// - position: coordenadas lógicas (px), donde la entidad está en el mundo
|
||||||
|
// - angle: radianes; 0 apunta hacia arriba (eje Y negativo en SDL)
|
||||||
|
// - velocity: px/s en cartesianas (NO polares — adiós a cos/sin por entidad)
|
||||||
|
// - mass = 0 (inverse_mass = 0) representa un cuerpo estático (masa infinita)
|
||||||
|
// - restitution 0 = inelástico, 1 = elástico perfecto
|
||||||
|
// - linear_damping en s⁻¹ (fricción exponencial: v *= exp(-damping * dt))
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/types.hpp"
|
||||||
|
|
||||||
|
namespace Physics {
|
||||||
|
|
||||||
|
struct RigidBody {
|
||||||
|
// --- Estado cinemático ---
|
||||||
|
Vec2 position{}; // Posición del centro (px)
|
||||||
|
Vec2 velocity{}; // Velocidad lineal (px/s)
|
||||||
|
float angle{0.0F}; // Orientación (rad)
|
||||||
|
float angular_velocity{0.0F}; // Velocidad angular (rad/s)
|
||||||
|
|
||||||
|
// --- Propiedades físicas ---
|
||||||
|
float mass{1.0F}; // Masa (kg, escala libre)
|
||||||
|
float inverse_mass{1.0F}; // 1/mass cacheado (0 = estático)
|
||||||
|
float restitution{0.5F}; // Elasticidad (0..1)
|
||||||
|
float linear_damping{0.0F}; // Fricción lineal (s⁻¹)
|
||||||
|
float angular_damping{0.0F}; // Fricción angular (s⁻¹)
|
||||||
|
float radius{0.0F}; // Radio de colisión (círculo)
|
||||||
|
|
||||||
|
// --- Fuerzas acumuladas (reseteadas tras cada integrate) ---
|
||||||
|
Vec2 force_accumulator{};
|
||||||
|
float torque_accumulator{0.0F};
|
||||||
|
|
||||||
|
// Configura la masa y precalcula inverse_mass.
|
||||||
|
// mass <= 0 marca el cuerpo como estático (inmovible por impulsos).
|
||||||
|
void setMass(float new_mass) {
|
||||||
|
mass = new_mass;
|
||||||
|
inverse_mass = (new_mass > 0.0F) ? 1.0F / new_mass : 0.0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marca el cuerpo como estático (paredes, obstáculos fijos).
|
||||||
|
void setStatic() {
|
||||||
|
mass = 0.0F;
|
||||||
|
inverse_mass = 0.0F;
|
||||||
|
velocity = Vec2{};
|
||||||
|
angular_velocity = 0.0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto isStatic() const -> bool { return inverse_mass == 0.0F; }
|
||||||
|
|
||||||
|
// Aplica una fuerza instantánea (acumulada para el siguiente integrate).
|
||||||
|
void applyForce(const Vec2& force) { force_accumulator += force; }
|
||||||
|
|
||||||
|
// Aplica un impulso (cambio inmediato de velocidad: Δv = J / m).
|
||||||
|
void applyImpulse(const Vec2& impulse) {
|
||||||
|
if (!isStatic()) {
|
||||||
|
velocity += impulse * inverse_mass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resetea los acumuladores tras la integración.
|
||||||
|
void clearAccumulators() {
|
||||||
|
force_accumulator = Vec2{};
|
||||||
|
torque_accumulator = 0.0F;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Physics
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
// color_oscillator.cpp - Implementació d'oscil·lació de color
|
|
||||||
// © 2025 Port a C++20 amb SDL3
|
|
||||||
|
|
||||||
#include "core/rendering/color_oscillator.hpp"
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "core/defaults.hpp"
|
|
||||||
|
|
||||||
namespace Rendering {
|
|
||||||
|
|
||||||
ColorOscillator::ColorOscillator()
|
|
||||||
: accumulated_time_(0.0F) {
|
|
||||||
// Inicialitzar amb el color mínim
|
|
||||||
current_line_color_ = {.r = Defaults::Color::LINE_MIN_R,
|
|
||||||
.g = Defaults::Color::LINE_MIN_G,
|
|
||||||
.b = Defaults::Color::LINE_MIN_B,
|
|
||||||
.a = 255};
|
|
||||||
current_background_color_ = {.r = Defaults::Color::BACKGROUND_MIN_R,
|
|
||||||
.g = Defaults::Color::BACKGROUND_MIN_G,
|
|
||||||
.b = Defaults::Color::BACKGROUND_MIN_B,
|
|
||||||
.a = 255};
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorOscillator::update(float delta_time) {
|
|
||||||
accumulated_time_ += delta_time;
|
|
||||||
|
|
||||||
float factor =
|
|
||||||
calculateOscillationFactor(accumulated_time_, Defaults::Color::FREQUENCY);
|
|
||||||
|
|
||||||
// Interpolar colors de línies
|
|
||||||
SDL_Color line_min = {Defaults::Color::LINE_MIN_R,
|
|
||||||
Defaults::Color::LINE_MIN_G,
|
|
||||||
Defaults::Color::LINE_MIN_B,
|
|
||||||
255};
|
|
||||||
SDL_Color line_max = {Defaults::Color::LINE_MAX_R,
|
|
||||||
Defaults::Color::LINE_MAX_G,
|
|
||||||
Defaults::Color::LINE_MAX_B,
|
|
||||||
255};
|
|
||||||
current_line_color_ = interpolateColor(line_min, line_max, factor);
|
|
||||||
|
|
||||||
// Interpolar colors de fons
|
|
||||||
SDL_Color bg_min = {Defaults::Color::BACKGROUND_MIN_R,
|
|
||||||
Defaults::Color::BACKGROUND_MIN_G,
|
|
||||||
Defaults::Color::BACKGROUND_MIN_B,
|
|
||||||
255};
|
|
||||||
SDL_Color bg_max = {Defaults::Color::BACKGROUND_MAX_R,
|
|
||||||
Defaults::Color::BACKGROUND_MAX_G,
|
|
||||||
Defaults::Color::BACKGROUND_MAX_B,
|
|
||||||
255};
|
|
||||||
current_background_color_ = interpolateColor(bg_min, bg_max, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
float ColorOscillator::calculateOscillationFactor(float time, float frequency) {
|
|
||||||
// Oscil·lació senoïdal: sin(t * freq * 2π)
|
|
||||||
// Mapejar de [-1, 1] a [0, 1]
|
|
||||||
float radians = time * frequency * 2.0F * Defaults::Math::PI;
|
|
||||||
return (std::sin(radians) + 1.0F) / 2.0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Color ColorOscillator::interpolateColor(SDL_Color min, SDL_Color max, float factor) {
|
|
||||||
return {static_cast<uint8_t>(min.r + ((max.r - min.r) * factor)),
|
|
||||||
static_cast<uint8_t>(min.g + ((max.g - min.g) * factor)),
|
|
||||||
static_cast<uint8_t>(min.b + ((max.b - min.b) * factor)),
|
|
||||||
255};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Rendering
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// color_oscillator.hpp - Sistema d'oscil·lació de color per efecte CRT
|
|
||||||
// © 2025 Port a C++20 amb SDL3
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
namespace Rendering {
|
|
||||||
|
|
||||||
class ColorOscillator {
|
|
||||||
public:
|
|
||||||
ColorOscillator();
|
|
||||||
|
|
||||||
void update(float delta_time);
|
|
||||||
|
|
||||||
[[nodiscard]] SDL_Color getCurrentLineColor() const { return current_line_color_; }
|
|
||||||
[[nodiscard]] SDL_Color getCurrentBackgroundColor() const {
|
|
||||||
return current_background_color_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
float accumulated_time_;
|
|
||||||
SDL_Color current_line_color_;
|
|
||||||
SDL_Color current_background_color_;
|
|
||||||
|
|
||||||
static float calculateOscillationFactor(float time, float frequency);
|
|
||||||
static SDL_Color interpolateColor(SDL_Color min, SDL_Color max, float factor);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Rendering
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
// coordinate_transform.cpp - Inicialització de variables globals
|
// coordinate_transform.cpp - Inicialización de variables globals
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "core/rendering/coordinate_transform.hpp"
|
#include "core/rendering/coordinate_transform.hpp"
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
// Factor d'escala global (inicialitzat a 1.0 per defecte)
|
// Factor de scale global (inicialitzat a 1.0 per defecte)
|
||||||
float g_current_scale_factor = 1.0F;
|
float g_current_scale_factor = 1.0F;
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// coordinate_transform.hpp - Transformació de coordenades lògiques a físiques
|
// coordinate_transform.hpp - Transformació de coordenades lògiques a físiques
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
// Factor d'escala global (actualitzat cada frame per SDLManager)
|
// Factor de scale global (actualitzat cada frame per SDLManager)
|
||||||
extern float g_current_scale_factor;
|
extern float g_current_scale_factor;
|
||||||
|
|
||||||
// Transforma coordenada lògica a física amb arrodoniment
|
// Transforma coordenada lógica a física con arrodoniment
|
||||||
inline int transform_x(int logical_x, float scale) {
|
inline int transform_x(int logical_x, float scale) {
|
||||||
return static_cast<int>(std::round(logical_x * scale));
|
return static_cast<int>(std::round(logical_x * scale));
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ inline int transform_y(int logical_y, float scale) {
|
|||||||
return static_cast<int>(std::round(logical_y * scale));
|
return static_cast<int>(std::round(logical_y * scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variant que usa el factor d'escala global
|
// Variant que usa el factor de scale global
|
||||||
inline int transform_x(int logical_x) {
|
inline int transform_x(int logical_x) {
|
||||||
return transform_x(logical_x, g_current_scale_factor);
|
return transform_x(logical_x, g_current_scale_factor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// gpu_device.cpp - Implementación del wrapper de SDL_GPUDevice
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_device.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/utils/path_utils.hpp"
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
GpuDevice::~GpuDevice() { destroy(); }
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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_);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
const std::streamsize SIZE = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
std::vector<uint8_t> buffer(static_cast<size_t>(SIZE));
|
||||||
|
if (!file.read(reinterpret_cast<char*>(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
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// gpu_device.hpp - Wrapper de SDL_GPUDevice
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Ownership del SDL_GPUDevice y del claim del window. Backend preferido:
|
||||||
|
// Vulkan (Linux, Windows) y Metal (macOS). Sin DirectX.
|
||||||
|
//
|
||||||
|
// Uso:
|
||||||
|
// GpuDevice device;
|
||||||
|
// if (!device.init(window)) return -1; // claim del window
|
||||||
|
// ... renderer setup ...
|
||||||
|
// device.destroy(); // unclaim + destroy device
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
class GpuDevice {
|
||||||
|
public:
|
||||||
|
GpuDevice() = default;
|
||||||
|
~GpuDevice();
|
||||||
|
|
||||||
|
// No copia / move (RAII propietario 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.
|
||||||
|
[[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:
|
||||||
|
SDL_GPUDevice* device_{nullptr};
|
||||||
|
SDL_Window* window_{nullptr};
|
||||||
|
SDL_GPUTextureFormat swapchain_format_{SDL_GPU_TEXTUREFORMAT_INVALID};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,400 @@
|
|||||||
|
// gpu_frame_renderer.cpp - Implementación del FrameRenderer con offscreen + postpro
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_frame_renderer.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
GpuFrameRenderer::~GpuFrameRenderer() { destroy(); }
|
||||||
|
|
||||||
|
auto GpuFrameRenderer::init(SDL_Window* window, float logical_w, float logical_h) -> bool {
|
||||||
|
logical_w_ = logical_w;
|
||||||
|
logical_h_ = logical_h;
|
||||||
|
|
||||||
|
if (!device_.init(window)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Pipeline de líneas: escribe sobre el offscreen (formato fijo).
|
||||||
|
if (!line_pipeline_.init(device_, offscreen_format_)) {
|
||||||
|
device_.destroy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Pipeline de postpro: escribe sobre swapchain (formato del swapchain).
|
||||||
|
if (!postfx_pipeline_.init(device_, device_.swapchainFormat())) {
|
||||||
|
line_pipeline_.destroy();
|
||||||
|
device_.destroy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!createOffscreen()) {
|
||||||
|
postfx_pipeline_.destroy();
|
||||||
|
line_pipeline_.destroy();
|
||||||
|
device_.destroy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GpuFrameRenderer::createOffscreen() -> bool {
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
if (dev == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Textura offscreen del tamaño lógico del juego, COLOR_TARGET + SAMPLER.
|
||||||
|
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<uint32_t>(logical_w_);
|
||||||
|
tex_info.height = static_cast<uint32_t>(logical_h_);
|
||||||
|
tex_info.layer_count_or_depth = 1;
|
||||||
|
tex_info.num_levels = 1;
|
||||||
|
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||||
|
offscreen_texture_ = SDL_CreateGPUTexture(dev, &tex_info);
|
||||||
|
if (offscreen_texture_ == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] SDL_CreateGPUTexture (offscreen): "
|
||||||
|
<< SDL_GetError() << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sampler lineal con clamp-to-edge (evita sangrado en los bordes del bloom).
|
||||||
|
SDL_GPUSamplerCreateInfo sampler_info{};
|
||||||
|
sampler_info.min_filter = SDL_GPU_FILTER_LINEAR;
|
||||||
|
sampler_info.mag_filter = SDL_GPU_FILTER_LINEAR;
|
||||||
|
sampler_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
|
||||||
|
sampler_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||||
|
sampler_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||||
|
sampler_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||||
|
linear_sampler_ = SDL_CreateGPUSampler(dev, &sampler_info);
|
||||||
|
if (linear_sampler_ == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] SDL_CreateGPUSampler: "
|
||||||
|
<< SDL_GetError() << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::destroyOffscreen() {
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
if (dev == nullptr) {
|
||||||
|
offscreen_texture_ = nullptr;
|
||||||
|
linear_sampler_ = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (offscreen_texture_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUTexture(dev, offscreen_texture_);
|
||||||
|
offscreen_texture_ = nullptr;
|
||||||
|
}
|
||||||
|
if (linear_sampler_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUSampler(dev, linear_sampler_);
|
||||||
|
linear_sampler_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::destroy() {
|
||||||
|
destroyOffscreen();
|
||||||
|
postfx_pipeline_.destroy();
|
||||||
|
line_pipeline_.destroy();
|
||||||
|
device_.destroy();
|
||||||
|
vertices_.clear();
|
||||||
|
indices_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
(void)clear_r;
|
||||||
|
(void)clear_g;
|
||||||
|
(void)clear_b;
|
||||||
|
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
if (dev == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_buffer_ = SDL_AcquireGPUCommandBuffer(dev);
|
||||||
|
if (cmd_buffer_ == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] SDL_AcquireGPUCommandBuffer: " << SDL_GetError() << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SDL_WaitAndAcquireGPUSwapchainTexture(cmd_buffer_, device_.window(),
|
||||||
|
&swapchain_texture_, nullptr, nullptr)) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] WaitAndAcquire: " << SDL_GetError() << '\n';
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
|
cmd_buffer_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swapchain_texture_ == nullptr) {
|
||||||
|
// Ventana minimizada o swapchain no disponible: solo submit y salir.
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
|
cmd_buffer_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abrir render pass sobre OFFSCREEN con clear a negro.
|
||||||
|
SDL_GPUColorTargetInfo color_target{};
|
||||||
|
color_target.texture = offscreen_texture_;
|
||||||
|
color_target.clear_color = SDL_FColor{.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||||
|
color_target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||||
|
color_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||||
|
color_target.cycle = false;
|
||||||
|
|
||||||
|
render_pass_ = SDL_BeginGPURenderPass(cmd_buffer_, &color_target, 1, nullptr);
|
||||||
|
if (render_pass_ == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] SDL_BeginGPURenderPass (offscreen): "
|
||||||
|
<< SDL_GetError() << '\n';
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
|
cmd_buffer_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Sin SetGPUViewport: el offscreen se llena entero a tamaño lógico.
|
||||||
|
|
||||||
|
vertices_.clear();
|
||||||
|
indices_.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::setViewport(float x, float y, float w, float h) {
|
||||||
|
viewport_x_ = x;
|
||||||
|
viewport_y_ = y;
|
||||||
|
viewport_w_ = w;
|
||||||
|
viewport_h_ = h;
|
||||||
|
// El viewport solo se aplica en el pase final (composite). Si estamos
|
||||||
|
// ya dentro del composite, lo aplicaríamos inmediatamente, pero la API
|
||||||
|
// está pensada para llamarse antes de endFrame/al cambiar de ventana.
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::setVSync(bool enabled) {
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
if (dev == nullptr || device_.window() == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const SDL_GPUPresentMode MODE = enabled
|
||||||
|
? SDL_GPU_PRESENTMODE_VSYNC
|
||||||
|
: SDL_GPU_PRESENTMODE_IMMEDIATE;
|
||||||
|
if (!SDL_SetGPUSwapchainParameters(dev, device_.window(),
|
||||||
|
SDL_GPU_SWAPCHAINCOMPOSITION_SDR, MODE)) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] SDL_SetGPUSwapchainParameters: " << SDL_GetError() << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::applyFinalViewport() {
|
||||||
|
if (render_pass_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (viewport_w_ <= 0.0F || viewport_h_ <= 0.0F) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_GPUViewport vp{};
|
||||||
|
vp.x = viewport_x_;
|
||||||
|
vp.y = viewport_y_;
|
||||||
|
vp.w = viewport_w_;
|
||||||
|
vp.h = viewport_h_;
|
||||||
|
vp.min_depth = 0.0F;
|
||||||
|
vp.max_depth = 1.0F;
|
||||||
|
SDL_SetGPUViewport(render_pass_, &vp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::pushLine(float x1, float y1, float x2, float y2, float thickness,
|
||||||
|
float r, float g, float b, float a) {
|
||||||
|
const float DX = x2 - x1;
|
||||||
|
const float DY = y2 - y1;
|
||||||
|
const float LEN = std::sqrt((DX * DX) + (DY * DY));
|
||||||
|
if (LEN < 1e-6F) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float HALF = thickness * 0.5F;
|
||||||
|
const float NX = -DY / LEN * HALF;
|
||||||
|
const float NY = DX / LEN * HALF;
|
||||||
|
|
||||||
|
const auto BASE_INDEX = static_cast<uint16_t>(vertices_.size());
|
||||||
|
|
||||||
|
vertices_.push_back({x1 + NX, y1 + NY, r, g, b, a});
|
||||||
|
vertices_.push_back({x1 - NX, y1 - NY, r, g, b, a});
|
||||||
|
vertices_.push_back({x2 + NX, y2 + NY, r, g, b, a});
|
||||||
|
vertices_.push_back({x2 - NX, y2 - NY, r, g, b, a});
|
||||||
|
|
||||||
|
indices_.push_back(BASE_INDEX + 0);
|
||||||
|
indices_.push_back(BASE_INDEX + 1);
|
||||||
|
indices_.push_back(BASE_INDEX + 2);
|
||||||
|
indices_.push_back(BASE_INDEX + 1);
|
||||||
|
indices_.push_back(BASE_INDEX + 3);
|
||||||
|
indices_.push_back(BASE_INDEX + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::flushBatch() {
|
||||||
|
if (vertices_.empty() || indices_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
|
||||||
|
const uint32_t VBO_SIZE = static_cast<uint32_t>(vertices_.size() * sizeof(LineVertex));
|
||||||
|
const uint32_t IBO_SIZE = static_cast<uint32_t>(indices_.size() * sizeof(uint16_t));
|
||||||
|
|
||||||
|
SDL_GPUBufferCreateInfo vbo_info{};
|
||||||
|
vbo_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||||
|
vbo_info.size = VBO_SIZE;
|
||||||
|
SDL_GPUBuffer* vbo = SDL_CreateGPUBuffer(dev, &vbo_info);
|
||||||
|
|
||||||
|
SDL_GPUBufferCreateInfo ibo_info{};
|
||||||
|
ibo_info.usage = SDL_GPU_BUFFERUSAGE_INDEX;
|
||||||
|
ibo_info.size = IBO_SIZE;
|
||||||
|
SDL_GPUBuffer* ibo = SDL_CreateGPUBuffer(dev, &ibo_info);
|
||||||
|
|
||||||
|
SDL_GPUTransferBufferCreateInfo tbo_info{};
|
||||||
|
tbo_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||||
|
tbo_info.size = VBO_SIZE + IBO_SIZE;
|
||||||
|
SDL_GPUTransferBuffer* tbo = SDL_CreateGPUTransferBuffer(dev, &tbo_info);
|
||||||
|
|
||||||
|
auto* mapped = static_cast<uint8_t*>(SDL_MapGPUTransferBuffer(dev, tbo, false));
|
||||||
|
std::memcpy(mapped, vertices_.data(), VBO_SIZE);
|
||||||
|
std::memcpy(mapped + VBO_SIZE, indices_.data(), IBO_SIZE);
|
||||||
|
SDL_UnmapGPUTransferBuffer(dev, tbo);
|
||||||
|
|
||||||
|
// Copy pass FUERA del render pass.
|
||||||
|
SDL_EndGPURenderPass(render_pass_);
|
||||||
|
render_pass_ = nullptr;
|
||||||
|
|
||||||
|
SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd_buffer_);
|
||||||
|
SDL_GPUTransferBufferLocation vbo_src{.transfer_buffer = tbo, .offset = 0};
|
||||||
|
SDL_GPUBufferRegion vbo_dst{.buffer = vbo, .offset = 0, .size = VBO_SIZE};
|
||||||
|
SDL_UploadToGPUBuffer(copy_pass, &vbo_src, &vbo_dst, false);
|
||||||
|
SDL_GPUTransferBufferLocation ibo_src{.transfer_buffer = tbo, .offset = VBO_SIZE};
|
||||||
|
SDL_GPUBufferRegion ibo_dst{.buffer = ibo, .offset = 0, .size = IBO_SIZE};
|
||||||
|
SDL_UploadToGPUBuffer(copy_pass, &ibo_src, &ibo_dst, false);
|
||||||
|
SDL_EndGPUCopyPass(copy_pass);
|
||||||
|
|
||||||
|
// Reabrir render pass sobre OFFSCREEN (load_op=LOAD para preservar el clear).
|
||||||
|
SDL_GPUColorTargetInfo color_target{};
|
||||||
|
color_target.texture = offscreen_texture_;
|
||||||
|
color_target.load_op = SDL_GPU_LOADOP_LOAD;
|
||||||
|
color_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||||
|
render_pass_ = SDL_BeginGPURenderPass(cmd_buffer_, &color_target, 1, nullptr);
|
||||||
|
|
||||||
|
SDL_BindGPUGraphicsPipeline(render_pass_, line_pipeline_.get());
|
||||||
|
|
||||||
|
// UBO de líneas usa el tamaño lógico (también del offscreen).
|
||||||
|
LineUniforms ubo{.viewport_width = logical_w_,
|
||||||
|
.viewport_height = logical_h_,
|
||||||
|
.padding_0 = 0.0F,
|
||||||
|
.padding_1 = 0.0F};
|
||||||
|
SDL_PushGPUVertexUniformData(cmd_buffer_, 0, &ubo, sizeof(ubo));
|
||||||
|
|
||||||
|
SDL_GPUBufferBinding vbo_binding{.buffer = vbo, .offset = 0};
|
||||||
|
SDL_BindGPUVertexBuffers(render_pass_, 0, &vbo_binding, 1);
|
||||||
|
|
||||||
|
SDL_GPUBufferBinding ibo_binding{.buffer = ibo, .offset = 0};
|
||||||
|
SDL_BindGPUIndexBuffer(render_pass_, &ibo_binding, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
SDL_DrawGPUIndexedPrimitives(render_pass_,
|
||||||
|
static_cast<uint32_t>(indices_.size()),
|
||||||
|
1, 0, 0, 0);
|
||||||
|
|
||||||
|
SDL_ReleaseGPUBuffer(dev, vbo);
|
||||||
|
SDL_ReleaseGPUBuffer(dev, ibo);
|
||||||
|
SDL_ReleaseGPUTransferBuffer(dev, tbo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::compositePass() {
|
||||||
|
// Cierra el render pass actual (sobre offscreen).
|
||||||
|
if (render_pass_ != nullptr) {
|
||||||
|
SDL_EndGPURenderPass(render_pass_);
|
||||||
|
render_pass_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pase final: render pass sobre SWAPCHAIN con clear a negro (cubre el
|
||||||
|
// letterbox del viewport físico).
|
||||||
|
SDL_GPUColorTargetInfo target{};
|
||||||
|
target.texture = swapchain_texture_;
|
||||||
|
target.clear_color = SDL_FColor{.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||||
|
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||||
|
target.store_op = SDL_GPU_STOREOP_STORE;
|
||||||
|
target.cycle = false;
|
||||||
|
render_pass_ = SDL_BeginGPURenderPass(cmd_buffer_, &target, 1, nullptr);
|
||||||
|
if (render_pass_ == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] BeginRenderPass (composite): "
|
||||||
|
<< SDL_GetError() << '\n';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyFinalViewport();
|
||||||
|
|
||||||
|
SDL_BindGPUGraphicsPipeline(render_pass_, postfx_pipeline_.get());
|
||||||
|
|
||||||
|
// Bind del sampler (escena offscreen) en slot 0 del fragment shader.
|
||||||
|
SDL_GPUTextureSamplerBinding sampler_binding{};
|
||||||
|
sampler_binding.texture = offscreen_texture_;
|
||||||
|
sampler_binding.sampler = linear_sampler_;
|
||||||
|
SDL_BindGPUFragmentSamplers(render_pass_, 0, &sampler_binding, 1);
|
||||||
|
|
||||||
|
// Uniforms del postpro. Si una sección está desactivada, anulamos sus
|
||||||
|
// contribuciones (intensidad / amplitud / max=min) en lugar de tener
|
||||||
|
// un branch en el shader.
|
||||||
|
const float BLOOM_INTENSITY = postfx_params_.bloom_enabled
|
||||||
|
? postfx_params_.bloom_intensity : 0.0F;
|
||||||
|
const float FLICKER_AMPLITUDE = postfx_params_.flicker_enabled
|
||||||
|
? postfx_params_.flicker_amplitude : 0.0F;
|
||||||
|
const float BG_MIN_R = postfx_params_.background_enabled ? postfx_params_.background_min_r : 0.0F;
|
||||||
|
const float BG_MIN_G = postfx_params_.background_enabled ? postfx_params_.background_min_g : 0.0F;
|
||||||
|
const float BG_MIN_B = postfx_params_.background_enabled ? postfx_params_.background_min_b : 0.0F;
|
||||||
|
const float BG_MAX_R = postfx_params_.background_enabled ? postfx_params_.background_max_r : 0.0F;
|
||||||
|
const float BG_MAX_G = postfx_params_.background_enabled ? postfx_params_.background_max_g : 0.0F;
|
||||||
|
const float BG_MAX_B = postfx_params_.background_enabled ? postfx_params_.background_max_b : 0.0F;
|
||||||
|
|
||||||
|
// Tiempo en segundos desde el inicio de SDL (wall-clock real, robusto a FPS variables).
|
||||||
|
const float TIME_SECONDS = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||||
|
|
||||||
|
PostFxUniforms ubo{};
|
||||||
|
ubo.time = TIME_SECONDS;
|
||||||
|
ubo.bloom_intensity = BLOOM_INTENSITY;
|
||||||
|
ubo.bloom_threshold = postfx_params_.bloom_threshold;
|
||||||
|
ubo.bloom_radius_px = postfx_params_.bloom_radius_px;
|
||||||
|
ubo.flicker_amplitude = FLICKER_AMPLITUDE;
|
||||||
|
ubo.flicker_frequency_hz = postfx_params_.flicker_frequency_hz;
|
||||||
|
ubo.background_pulse_freq_hz = postfx_params_.background_pulse_freq_hz;
|
||||||
|
ubo.pad_a_ = 0.0F;
|
||||||
|
ubo.background_min_r = BG_MIN_R;
|
||||||
|
ubo.background_min_g = BG_MIN_G;
|
||||||
|
ubo.background_min_b = BG_MIN_B;
|
||||||
|
ubo.background_min_a = 1.0F;
|
||||||
|
ubo.background_max_r = BG_MAX_R;
|
||||||
|
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_;
|
||||||
|
ubo.pad_b_ = 0.0F;
|
||||||
|
ubo.pad_c_ = 0.0F;
|
||||||
|
|
||||||
|
SDL_PushGPUFragmentUniformData(cmd_buffer_, 0, &ubo, sizeof(ubo));
|
||||||
|
|
||||||
|
// Fullscreen triangle: 3 vértices generados en el shader, sin VBO.
|
||||||
|
SDL_DrawGPUPrimitives(render_pass_, 3, 1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::endFrame() {
|
||||||
|
if (cmd_buffer_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flushBatch();
|
||||||
|
compositePass();
|
||||||
|
if (render_pass_ != nullptr) {
|
||||||
|
SDL_EndGPURenderPass(render_pass_);
|
||||||
|
render_pass_ = nullptr;
|
||||||
|
}
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
|
cmd_buffer_ = nullptr;
|
||||||
|
swapchain_texture_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
// gpu_frame_renderer.hpp - Renderer de alto nivel basado en SDL_GPU
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Flujo por frame:
|
||||||
|
// 1. beginFrame(clear_color)
|
||||||
|
// → acquire swapchain + begin render pass sobre la textura OFFSCREEN
|
||||||
|
// (clear a black; la swapchain se pinta después con el postpro).
|
||||||
|
// 2. pushLine(...) — encola líneas (extrusión en CPU).
|
||||||
|
// 3. endFrame()
|
||||||
|
// → flush del batch en offscreen + pase de postpro (sample offscreen
|
||||||
|
// → swapchain con bloom/flicker/background) + submit + presenta.
|
||||||
|
//
|
||||||
|
// La oscilación de brillo y el fondo pulsante viven ahora en el shader de
|
||||||
|
// postpro; el CPU solo le pasa los uniformes del struct PostFxParams.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_device.hpp"
|
||||||
|
#include "core/rendering/gpu/gpu_line_pipeline.hpp"
|
||||||
|
#include "core/rendering/gpu/gpu_postfx_pipeline.hpp"
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
// Parámetros del postpro que el caller (SDLManager) pasa cada frame.
|
||||||
|
// Equivalente al lado "humano" del PostFxUniforms (sin paddings).
|
||||||
|
struct PostFxParams {
|
||||||
|
bool bloom_enabled{true};
|
||||||
|
float bloom_intensity{0.6F};
|
||||||
|
float bloom_threshold{0.4F};
|
||||||
|
float bloom_radius_px{2.0F};
|
||||||
|
|
||||||
|
bool flicker_enabled{true};
|
||||||
|
float flicker_amplitude{0.10F};
|
||||||
|
float flicker_frequency_hz{6.0F};
|
||||||
|
|
||||||
|
bool background_enabled{true};
|
||||||
|
float background_min_r{0.0F};
|
||||||
|
float background_min_g{0.02F};
|
||||||
|
float background_min_b{0.0F};
|
||||||
|
float background_max_r{0.0F};
|
||||||
|
float background_max_g{0.06F};
|
||||||
|
float background_max_b{0.0F};
|
||||||
|
float background_pulse_freq_hz{6.0F};
|
||||||
|
};
|
||||||
|
|
||||||
|
class GpuFrameRenderer {
|
||||||
|
public:
|
||||||
|
GpuFrameRenderer() = default;
|
||||||
|
~GpuFrameRenderer();
|
||||||
|
|
||||||
|
GpuFrameRenderer(const GpuFrameRenderer&) = delete;
|
||||||
|
auto operator=(const GpuFrameRenderer&) -> GpuFrameRenderer& = delete;
|
||||||
|
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;
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// fondo lo dibuja el postpro).
|
||||||
|
[[nodiscard]] auto beginFrame(float clear_r, float clear_g, float clear_b) -> bool;
|
||||||
|
|
||||||
|
// Encola una línea con grosor configurable (px). Color RGBA en [0..1].
|
||||||
|
void pushLine(float x1, float y1, float x2, float y2, float thickness,
|
||||||
|
float r, float g, float b, float a);
|
||||||
|
|
||||||
|
// endFrame: flush del batch de líneas → composite postpro → submit + presenta.
|
||||||
|
void endFrame();
|
||||||
|
|
||||||
|
// Viewport del PASE FINAL (postpro → swapchain) en píxeles físicos.
|
||||||
|
// Implementa el letterbox: cualquier área fuera del viewport queda
|
||||||
|
// como el clear color del pase de composite (negro). Si w<=0 o h<=0,
|
||||||
|
// se usa el tamaño completo de la ventana.
|
||||||
|
void setViewport(float x, float y, float w, float h);
|
||||||
|
|
||||||
|
// Activa/desactiva VSync. true = SDL_GPU_PRESENTMODE_VSYNC, false = IMMEDIATE.
|
||||||
|
void setVSync(bool enabled);
|
||||||
|
|
||||||
|
// Parámetros del postpro que se aplican en endFrame. Por defecto =
|
||||||
|
// valores de Defaults (bloom moderado, flicker suave, fondo verde tenue).
|
||||||
|
void setPostFx(const PostFxParams& params) { postfx_params_ = params; }
|
||||||
|
[[nodiscard]] auto postfx() const -> const PostFxParams& { return postfx_params_; }
|
||||||
|
|
||||||
|
// Acceso a internals.
|
||||||
|
[[nodiscard]] auto device() -> GpuDevice& { return device_; }
|
||||||
|
[[nodiscard]] auto isInsideFrame() const -> bool { return cmd_buffer_ != nullptr; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GpuDevice device_;
|
||||||
|
GpuLinePipeline line_pipeline_;
|
||||||
|
GpuPostFxPipeline postfx_pipeline_;
|
||||||
|
|
||||||
|
// Tamaño lógico del juego (= tamaño del offscreen).
|
||||||
|
float logical_w_{1280.0F};
|
||||||
|
float logical_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};
|
||||||
|
float viewport_w_{-1.0F};
|
||||||
|
float viewport_h_{-1.0F};
|
||||||
|
|
||||||
|
// Offscreen color target (formato fijo R8G8B8A8_UNORM para portabilidad).
|
||||||
|
SDL_GPUTexture* offscreen_texture_{nullptr};
|
||||||
|
SDL_GPUTextureFormat offscreen_format_{SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM};
|
||||||
|
SDL_GPUSampler* linear_sampler_{nullptr};
|
||||||
|
|
||||||
|
// Batch del frame en curso.
|
||||||
|
std::vector<LineVertex> vertices_;
|
||||||
|
std::vector<uint16_t> indices_;
|
||||||
|
|
||||||
|
// Estado del frame en curso.
|
||||||
|
SDL_GPUCommandBuffer* cmd_buffer_{nullptr};
|
||||||
|
SDL_GPUTexture* swapchain_texture_{nullptr};
|
||||||
|
SDL_GPURenderPass* render_pass_{nullptr};
|
||||||
|
|
||||||
|
// Parámetros del postpro (configurables vía YAML).
|
||||||
|
PostFxParams postfx_params_{};
|
||||||
|
|
||||||
|
// Helpers internos.
|
||||||
|
[[nodiscard]] auto createOffscreen() -> bool;
|
||||||
|
void destroyOffscreen();
|
||||||
|
void flushBatch();
|
||||||
|
void compositePass();
|
||||||
|
void applyFinalViewport();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
// gpu_line_pipeline.cpp - Implementación del pipeline de líneas
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_line_pipeline.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_device.hpp"
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
GpuLinePipeline::~GpuLinePipeline() { destroy(); }
|
||||||
|
|
||||||
|
auto GpuLinePipeline::init(const GpuDevice& device,
|
||||||
|
SDL_GPUTextureFormat target_format) -> bool {
|
||||||
|
owner_ = device.get();
|
||||||
|
if (owner_ == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUShader* vert = device.loadShader("line.vert.spv",
|
||||||
|
SDL_GPU_SHADERSTAGE_VERTEX,
|
||||||
|
/*num_uniform_buffers=*/1);
|
||||||
|
SDL_GPUShader* frag = device.loadShader("line.frag.spv",
|
||||||
|
SDL_GPU_SHADERSTAGE_FRAGMENT,
|
||||||
|
/*num_uniform_buffers=*/0);
|
||||||
|
if ((vert == nullptr) || (frag == nullptr)) {
|
||||||
|
if (vert != nullptr) {
|
||||||
|
SDL_ReleaseGPUShader(owner_, vert);
|
||||||
|
}
|
||||||
|
if (frag != nullptr) {
|
||||||
|
SDL_ReleaseGPUShader(owner_, frag);
|
||||||
|
}
|
||||||
|
std::cerr << "[GpuLinePipeline] Error cargando shaders\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertex layout: pos (vec2) + color (vec4) → 6 floats por vertex.
|
||||||
|
SDL_GPUVertexBufferDescription vbo_desc{};
|
||||||
|
vbo_desc.slot = 0;
|
||||||
|
vbo_desc.pitch = sizeof(LineVertex);
|
||||||
|
vbo_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||||
|
vbo_desc.instance_step_rate = 0;
|
||||||
|
|
||||||
|
SDL_GPUVertexAttribute attrs[2];
|
||||||
|
attrs[0].location = 0; // in_position
|
||||||
|
attrs[0].buffer_slot = 0;
|
||||||
|
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||||
|
attrs[0].offset = 0;
|
||||||
|
attrs[1].location = 1; // in_color
|
||||||
|
attrs[1].buffer_slot = 0;
|
||||||
|
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||||
|
attrs[1].offset = sizeof(float) * 2;
|
||||||
|
|
||||||
|
SDL_GPUVertexInputState vertex_input{};
|
||||||
|
vertex_input.vertex_buffer_descriptions = &vbo_desc;
|
||||||
|
vertex_input.num_vertex_buffers = 1;
|
||||||
|
vertex_input.vertex_attributes = attrs;
|
||||||
|
vertex_input.num_vertex_attributes = 2;
|
||||||
|
|
||||||
|
// Color target = formato pasado por el caller (offscreen u otro).
|
||||||
|
// Blending alpha estándar.
|
||||||
|
SDL_GPUColorTargetDescription color_target{};
|
||||||
|
color_target.format = target_format;
|
||||||
|
color_target.blend_state.enable_blend = true;
|
||||||
|
color_target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||||
|
color_target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||||
|
color_target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||||
|
color_target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||||
|
color_target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||||
|
color_target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||||
|
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; // No backface culling para 2D
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Los shaders se pueden liberar tras crear el pipeline (SDL los retiene
|
||||||
|
// internamente mientras el pipeline esté vivo).
|
||||||
|
SDL_ReleaseGPUShader(owner_, vert);
|
||||||
|
SDL_ReleaseGPUShader(owner_, frag);
|
||||||
|
|
||||||
|
if (pipeline_ == nullptr) {
|
||||||
|
std::cerr << "[GpuLinePipeline] SDL_CreateGPUGraphicsPipeline: " << SDL_GetError() << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuLinePipeline::destroy() {
|
||||||
|
if ((pipeline_ != nullptr) && (owner_ != nullptr)) {
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(owner_, pipeline_);
|
||||||
|
}
|
||||||
|
pipeline_ = nullptr;
|
||||||
|
owner_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// gpu_line_pipeline.hpp - Pipeline gráfico para dibujar líneas vectoriales
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Las líneas se renderizan como quads (2 triángulos = 6 índices) ya extrudidos
|
||||||
|
// en CPU según el grosor pedido por línea. Vertex layout: position (vec2) + color (vec4).
|
||||||
|
// Primitive type: TRIANGLELIST. Sin depth test (juego 2D), blending alpha estándar.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
class GpuDevice;
|
||||||
|
|
||||||
|
// Vertex layout (debe coincidir con shaders/line.vert.glsl).
|
||||||
|
struct LineVertex {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
float a;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Uniform buffer del vertex shader (debe coincidir con UBO en line.vert.glsl).
|
||||||
|
struct LineUniforms {
|
||||||
|
float viewport_width;
|
||||||
|
float viewport_height;
|
||||||
|
float padding_0;
|
||||||
|
float padding_1; // Alineamiento a 16 bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
class GpuLinePipeline {
|
||||||
|
public:
|
||||||
|
GpuLinePipeline() = default;
|
||||||
|
~GpuLinePipeline();
|
||||||
|
|
||||||
|
GpuLinePipeline(const GpuLinePipeline&) = delete;
|
||||||
|
auto operator=(const GpuLinePipeline&) -> GpuLinePipeline& = delete;
|
||||||
|
GpuLinePipeline(GpuLinePipeline&&) = delete;
|
||||||
|
auto operator=(GpuLinePipeline&&) -> GpuLinePipeline& = delete;
|
||||||
|
|
||||||
|
// target_format: formato del color target sobre el que renderizamos
|
||||||
|
// (swapchain o offscreen). Por defecto coincide con el del swapchain
|
||||||
|
// del device.
|
||||||
|
[[nodiscard]] auto init(const GpuDevice& device,
|
||||||
|
SDL_GPUTextureFormat target_format) -> bool;
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
[[nodiscard]] auto get() const -> SDL_GPUGraphicsPipeline* { return pipeline_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_GPUDevice* owner_{nullptr}; // No-owning; el GpuDevice es el dueño
|
||||||
|
SDL_GPUGraphicsPipeline* pipeline_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// gpu_postfx_pipeline.cpp - Implementación del pipeline de postprocesado.
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_postfx_pipeline.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_device.hpp"
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (frag != nullptr) {
|
||||||
|
SDL_ReleaseGPUShader(owner_, frag);
|
||||||
|
}
|
||||||
|
std::cerr << "[GpuPostFxPipeline] Error cargando shaders postfx\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuPostFxPipeline::destroy() {
|
||||||
|
if ((pipeline_ != nullptr) && (owner_ != nullptr)) {
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(owner_, pipeline_);
|
||||||
|
}
|
||||||
|
pipeline_ = nullptr;
|
||||||
|
owner_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// gpu_postfx_pipeline.hpp - Pipeline de postprocesado (fullscreen triangle)
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Pase final del frame: muestrea la escena renderizada en offscreen y aplica
|
||||||
|
// bloom + flicker + background pulse en el fragment shader. El vertex shader
|
||||||
|
// emite un único triángulo que cubre toda la pantalla, así que el draw no
|
||||||
|
// necesita vertex buffer (DrawPrimitives con vertex_count=3).
|
||||||
|
//
|
||||||
|
// Recursos del shader (SDL_gpu set bindings):
|
||||||
|
// fragment set=2, binding=0 → sampler2D (escena offscreen)
|
||||||
|
// fragment set=3, binding=0 → uniform buffer (parámetros del postpro)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
namespace Rendering::GPU {
|
||||||
|
|
||||||
|
class GpuDevice;
|
||||||
|
|
||||||
|
// Uniform buffer del postpro. Debe coincidir EXACTAMENTE con
|
||||||
|
// shaders/postfx.frag.glsl (layout std140 con vec4 alineadas a 16 bytes).
|
||||||
|
struct PostFxUniforms {
|
||||||
|
float time; // Tiempo acumulado en segundos
|
||||||
|
float bloom_intensity; // Mezcla bloom (0..2)
|
||||||
|
float bloom_threshold; // Luminancia mínima high-pass (0..1)
|
||||||
|
float bloom_radius_px; // Radio del kernel en píxeles lógicos
|
||||||
|
|
||||||
|
float flicker_amplitude; // Profundidad del flicker (0..1)
|
||||||
|
float flicker_frequency_hz; // Hz
|
||||||
|
float background_pulse_freq_hz; // Hz
|
||||||
|
float pad_a_;
|
||||||
|
|
||||||
|
float background_min_r; // Color min RGB en [0..1], A=1
|
||||||
|
float background_min_g;
|
||||||
|
float background_min_b;
|
||||||
|
float background_min_a;
|
||||||
|
|
||||||
|
float background_max_r;
|
||||||
|
float background_max_g;
|
||||||
|
float background_max_b;
|
||||||
|
float background_max_a;
|
||||||
|
|
||||||
|
float texel_size_x; // 1.0 / texture_width
|
||||||
|
float texel_size_y;
|
||||||
|
float pad_b_;
|
||||||
|
float pad_c_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GpuPostFxPipeline {
|
||||||
|
public:
|
||||||
|
GpuPostFxPipeline() = default;
|
||||||
|
~GpuPostFxPipeline();
|
||||||
|
|
||||||
|
GpuPostFxPipeline(const GpuPostFxPipeline&) = delete;
|
||||||
|
auto operator=(const GpuPostFxPipeline&) -> GpuPostFxPipeline& = delete;
|
||||||
|
GpuPostFxPipeline(GpuPostFxPipeline&&) = delete;
|
||||||
|
auto operator=(GpuPostFxPipeline&&) -> GpuPostFxPipeline& = delete;
|
||||||
|
|
||||||
|
// target_format: formato del color target del pase final (swapchain).
|
||||||
|
[[nodiscard]] auto init(const GpuDevice& device,
|
||||||
|
SDL_GPUTextureFormat target_format) -> bool;
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
[[nodiscard]] auto get() const -> SDL_GPUGraphicsPipeline* { return pipeline_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_GPUDevice* owner_{nullptr};
|
||||||
|
SDL_GPUGraphicsPipeline* pipeline_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering::GPU
|
||||||
@@ -1,102 +1,55 @@
|
|||||||
// line_renderer.cpp - Implementació de renderitzat de línies
|
// line_renderer.cpp - Implementación de renderizado de líneas (SDL3 GPU)
|
||||||
// © 1999 Visente i Sergi (versió Pascal)
|
// © 1999 Visente i Sergi (versión Pascal)
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "core/rendering/line_renderer.hpp"
|
#include "core/rendering/line_renderer.hpp"
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "core/rendering/coordinate_transform.hpp"
|
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
// [NUEVO] Color global compartit (actualitzat per ColorOscillator via
|
// Color global compartido para líneas sin paleta propia (HUD, debug, texto
|
||||||
// SDLManager)
|
// genérico). Equivale al "color máximo" de la antigua oscilación CPU: verde
|
||||||
SDL_Color g_current_line_color = {255, 255, 255, 255}; // Blanc inicial
|
// fósforo CRT. El pulso de brillo lo aplica ahora el shader de postpro.
|
||||||
|
SDL_Color g_current_line_color = {100, 255, 100, 255};
|
||||||
|
|
||||||
bool linea(SDL_Renderer* renderer, int x1, int y1, int x2, int y2, bool dibuixar, float brightness) {
|
// Grosor global por defecto. Configurable via setLineThickness.
|
||||||
// Algorisme de Bresenham per dibuixar línies
|
// 1.5 da una línea visible y crujiente; 1.0 se ve demasiado fino en pantallas grandes.
|
||||||
// Basat en el codi Pascal original
|
float g_current_line_thickness = 1.5F;
|
||||||
|
|
||||||
// Helper function: retorna el signe d'un nombre
|
void linea(Renderer* renderer,
|
||||||
auto sign = [](int x) -> int {
|
int x1, int y1, int x2, int y2,
|
||||||
if (x < 0) {
|
float brightness,
|
||||||
return -1;
|
float thickness,
|
||||||
}
|
SDL_Color color) {
|
||||||
if (x > 0) {
|
if (renderer == nullptr) {
|
||||||
return 1;
|
return;
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Variables per a l'algorisme (no utilitzades fins Fase 10 - detecció de
|
|
||||||
// col·lisions) int x = x1, y = y1; int xs = x2 - x1; int ys = y2 - y1; int
|
|
||||||
// xm = sign(xs); int ym = sign(ys); xs = std::abs(xs); ys = std::abs(ys);
|
|
||||||
|
|
||||||
// Suprimir warning de variable no usada
|
|
||||||
(void)sign;
|
|
||||||
|
|
||||||
// Detecció de col·lisió (TODO per Fase 10)
|
|
||||||
// El codi Pascal original llegia pixels del framebuffer bit-packed
|
|
||||||
// i comptava col·lisions. Per ara, usem SDL_RenderDrawLine i retornem false.
|
|
||||||
bool colisio = false;
|
|
||||||
|
|
||||||
// Dibuixar amb SDL3 (més eficient que Bresenham píxel a píxel)
|
|
||||||
if (dibuixar && (renderer != nullptr)) {
|
|
||||||
// Transformar coordenades lògiques (640x480) a físiques (resolució real)
|
|
||||||
float scale = g_current_scale_factor;
|
|
||||||
int px1 = transform_x(x1, scale);
|
|
||||||
int py1 = transform_y(y1, scale);
|
|
||||||
int px2 = transform_x(x2, scale);
|
|
||||||
int py2 = transform_y(y2, scale);
|
|
||||||
|
|
||||||
// Aplicar brightness al color oscil·lat global
|
|
||||||
SDL_Color color_final;
|
|
||||||
color_final.r = static_cast<uint8_t>(g_current_line_color.r * brightness);
|
|
||||||
color_final.g = static_cast<uint8_t>(g_current_line_color.g * brightness);
|
|
||||||
color_final.b = static_cast<uint8_t>(g_current_line_color.b * brightness);
|
|
||||||
color_final.a = 255;
|
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, color_final.r, color_final.g, color_final.b, 255);
|
|
||||||
|
|
||||||
// Renderitzar amb coordenades físiques
|
|
||||||
SDL_RenderLine(renderer, static_cast<float>(px1), static_cast<float>(py1), static_cast<float>(px2), static_cast<float>(py2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Algorisme de Bresenham original (conservat per a futura detecció de
|
// Coords lógicas (1280×720). El shader hace el mapeo a NDC; el viewport
|
||||||
// col·lisió)
|
// del SDLManager hace el letterbox a píxeles físicos.
|
||||||
/*
|
const float FX1 = static_cast<float>(x1);
|
||||||
if (xs > ys) {
|
const float FY1 = static_cast<float>(y1);
|
||||||
// Línia plana (<45 graus)
|
const float FX2 = static_cast<float>(x2);
|
||||||
int count = -(xs / 2);
|
const float FY2 = static_cast<float>(y2);
|
||||||
while (x != x2) {
|
|
||||||
count = count + ys;
|
|
||||||
x = x + xm;
|
|
||||||
if (count > 0) {
|
|
||||||
y = y + ym;
|
|
||||||
count = count - xs;
|
|
||||||
}
|
|
||||||
// Aquí aniria la detecció de col·lisió píxel a píxel
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Línia pronunciada (>=45 graus)
|
|
||||||
int count = -(ys / 2);
|
|
||||||
while (y != y2) {
|
|
||||||
count = count + xs;
|
|
||||||
y = y + ym;
|
|
||||||
if (count > 0) {
|
|
||||||
x = x + xm;
|
|
||||||
count = count - ys;
|
|
||||||
}
|
|
||||||
// Aquí aniria la detecció de col·lisió píxel a píxel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return colisio;
|
// 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<float>(SOURCE.r) * brightness) / 255.0F;
|
||||||
|
const float G = (static_cast<float>(SOURCE.g) * brightness) / 255.0F;
|
||||||
|
const float B = (static_cast<float>(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [NUEVO] Establir el color global de les línies
|
|
||||||
void setLineColor(SDL_Color color) { g_current_line_color = color; }
|
void setLineColor(SDL_Color color) { g_current_line_color = color; }
|
||||||
|
|
||||||
|
void setLineThickness(float thickness) {
|
||||||
|
if (thickness > 0.0F) {
|
||||||
|
g_current_line_thickness = thickness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getLineThickness() -> float { return g_current_line_thickness; }
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -1,16 +1,36 @@
|
|||||||
// line_renderer.hpp - Renderitzat de línies
|
// line_renderer.hpp - Renderizado de líneas vectoriales (SDL3 GPU)
|
||||||
// © 1999 Visente i Sergi (versió Pascal)
|
// © 1999 Visente i Sergi (versión Pascal)
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
//
|
||||||
|
// El dibujo de líneas pasa por el pipeline GPU. Las coordenadas (x1,y1,x2,y2)
|
||||||
|
// son lógicas (1280×720); el shader las mapea a NDC y el viewport del SDLManager
|
||||||
|
// hace el letterbox a píxeles físicos. El brillo modula el color global de
|
||||||
|
// línea (lo gestiona ColorOscillator). El grosor es configurable por línea
|
||||||
|
// (parámetro thickness>0) o global (g_current_line_thickness vía setLineThickness).
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
// Algorisme de Bresenham per dibuixar línies
|
|
||||||
// Retorna true si hi ha col·lisió (per Fase 10)
|
|
||||||
// brightness: factor de brillantor (0.0-1.0, default 1.0 = màxima brillantor)
|
|
||||||
bool linea(SDL_Renderer* renderer, int x1, int y1, int x2, int y2, bool dibuixar, float brightness = 1.0F);
|
|
||||||
|
|
||||||
// [NUEVO] Establir el color global de les línies (oscil·lació)
|
// Dibuja una línea entre dos puntos en coordenadas lógicas (1280×720).
|
||||||
|
// brightness: factor de brillo (0.0..1.0, default 1.0 = brillo máximo).
|
||||||
|
// thickness: grosor en píxeles lógicos. Si <= 0 usa g_current_line_thickness.
|
||||||
|
// color: si alpha==0, se usa el color global del oscilador; si alpha>0 se
|
||||||
|
// usa este color directo (paleta semántica por entidad).
|
||||||
|
void linea(Renderer* renderer,
|
||||||
|
int x1, int y1, int x2, int y2,
|
||||||
|
float brightness = 1.0F,
|
||||||
|
float thickness = 0.0F,
|
||||||
|
SDL_Color color = {0, 0, 0, 0});
|
||||||
|
|
||||||
|
// Color global de las líneas (lo actualiza ColorOscillator vía SDLManager).
|
||||||
void setLineColor(SDL_Color color);
|
void setLineColor(SDL_Color color);
|
||||||
|
|
||||||
|
// Grosor global por defecto (en píxeles lógicos). Default: 1.5.
|
||||||
|
void setLineThickness(float thickness);
|
||||||
|
[[nodiscard]] auto getLineThickness() -> float;
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
// polygon_renderer.cpp - Implementació de renderitzat de polígons
|
|
||||||
// © 1999 Visente i Sergi (versió Pascal)
|
|
||||||
// © 2025 Port a C++20 amb SDL3
|
|
||||||
//
|
|
||||||
// ==============================================================================
|
|
||||||
// DEPRECATED: Use core/rendering/shape_renderer.cpp instead
|
|
||||||
// ==============================================================================
|
|
||||||
|
|
||||||
#include "core/rendering/polygon_renderer.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "core/defaults.hpp"
|
|
||||||
#include "core/rendering/line_renderer.hpp"
|
|
||||||
|
|
||||||
namespace Rendering {
|
|
||||||
|
|
||||||
void rota_tri(SDL_Renderer* renderer, const Triangle& tri, float angul, float velocitat, bool dibuixar) {
|
|
||||||
// Rotar i dibuixar triangle (nau)
|
|
||||||
// Conversió de coordenades polars a cartesianes amb rotació
|
|
||||||
// Basat en el codi Pascal original: lines 271-284
|
|
||||||
|
|
||||||
// Convertir cada punt polar a cartesià
|
|
||||||
// x = (r + velocitat) * cos(angle_punt + angle_nau) + centre.x
|
|
||||||
// y = (r + velocitat) * sin(angle_punt + angle_nau) + centre.y
|
|
||||||
|
|
||||||
int x1 = static_cast<int>(std::round((tri.p1.r + velocitat) *
|
|
||||||
std::cos(tri.p1.angle + angul))) +
|
|
||||||
tri.centre.x;
|
|
||||||
|
|
||||||
int y1 = static_cast<int>(std::round((tri.p1.r + velocitat) *
|
|
||||||
std::sin(tri.p1.angle + angul))) +
|
|
||||||
tri.centre.y;
|
|
||||||
|
|
||||||
int x2 = static_cast<int>(std::round((tri.p2.r + velocitat) *
|
|
||||||
std::cos(tri.p2.angle + angul))) +
|
|
||||||
tri.centre.x;
|
|
||||||
|
|
||||||
int y2 = static_cast<int>(std::round((tri.p2.r + velocitat) *
|
|
||||||
std::sin(tri.p2.angle + angul))) +
|
|
||||||
tri.centre.y;
|
|
||||||
|
|
||||||
int x3 = static_cast<int>(std::round((tri.p3.r + velocitat) *
|
|
||||||
std::cos(tri.p3.angle + angul))) +
|
|
||||||
tri.centre.x;
|
|
||||||
|
|
||||||
int y3 = static_cast<int>(std::round((tri.p3.r + velocitat) *
|
|
||||||
std::sin(tri.p3.angle + angul))) +
|
|
||||||
tri.centre.y;
|
|
||||||
|
|
||||||
// Dibuixar les 3 línies que formen el triangle
|
|
||||||
linea(renderer, x1, y1, x2, y2, dibuixar);
|
|
||||||
linea(renderer, x1, y1, x3, y3, dibuixar);
|
|
||||||
linea(renderer, x3, y3, x2, y2, dibuixar);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rota_pol(SDL_Renderer* renderer, const Poligon& pol, float angul, bool dibuixar) {
|
|
||||||
// Rotar i dibuixar polígon (enemics i bales)
|
|
||||||
// Conversió de coordenades polars a cartesianes amb rotació
|
|
||||||
// Basat en el codi Pascal original: lines 286-296
|
|
||||||
|
|
||||||
// Array temporal per emmagatzemar punts convertits a cartesianes
|
|
||||||
std::array<Punt, Defaults::Entities::MAX_IPUNTS> xy;
|
|
||||||
|
|
||||||
// Convertir cada punt polar a cartesià
|
|
||||||
for (uint8_t i = 0; i < pol.n; i++) {
|
|
||||||
xy[i].x = static_cast<int>(std::round(
|
|
||||||
pol.ipuntx[i].r * std::cos(pol.ipuntx[i].angle + angul))) +
|
|
||||||
pol.centre.x;
|
|
||||||
|
|
||||||
xy[i].y = static_cast<int>(std::round(
|
|
||||||
pol.ipuntx[i].r * std::sin(pol.ipuntx[i].angle + angul))) +
|
|
||||||
pol.centre.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dibuixar línies entre punts consecutius
|
|
||||||
for (uint8_t i = 0; i < pol.n - 1; i++) {
|
|
||||||
linea(renderer, xy[i].x, xy[i].y, xy[i + 1].x, xy[i + 1].y, dibuixar);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tancar el polígon (últim punt → primer punt)
|
|
||||||
linea(renderer, xy[pol.n - 1].x, xy[pol.n - 1].y, xy[0].x, xy[0].y, dibuixar);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Rendering
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// polygon_renderer.hpp - Renderitzat de polígons polars
|
|
||||||
// © 1999 Visente i Sergi (versió Pascal)
|
|
||||||
// © 2025 Port a C++20 amb SDL3
|
|
||||||
//
|
|
||||||
// ==============================================================================
|
|
||||||
// DEPRECATED: Use core/rendering/shape_renderer.hpp instead
|
|
||||||
// ==============================================================================
|
|
||||||
// This file is kept temporarily for chatarra_cosmica_ (Phase 10: explosions)
|
|
||||||
// TODO Phase 10: Replace with particle system or remove completely
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include "core/types.hpp"
|
|
||||||
|
|
||||||
namespace Rendering {
|
|
||||||
// Rotar i dibuixar triangle (nau)
|
|
||||||
void rota_tri(SDL_Renderer* renderer, const Triangle& tri, float angul, float velocitat, bool dibuixar);
|
|
||||||
|
|
||||||
// Rotar i dibuixar polígon (enemics i bales)
|
|
||||||
void rota_pol(SDL_Renderer* renderer, const Poligon& pol, float angul, bool dibuixar);
|
|
||||||
} // namespace Rendering
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// primitives.cpp - Implementació de funcions geomètriques
|
|
||||||
// © 1999 Visente i Sergi (versió Pascal)
|
|
||||||
// © 2025 Port a C++20 amb SDL3
|
|
||||||
//
|
|
||||||
// ==============================================================================
|
|
||||||
// DEPRECATED: Use Shape system instead (.shp files + ShapeLoader)
|
|
||||||
// ==============================================================================
|
|
||||||
|
|
||||||
#include "primitives.hpp"
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "core/defaults.hpp"
|
|
||||||
|
|
||||||
float modul(const Punt& p) {
|
|
||||||
// Càlcul de la magnitud d'un vector: sqrt(x² + y²)
|
|
||||||
return std::sqrt((p.x * p.x) + (p.y * p.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
void diferencia(const Punt& o, const Punt& d, Punt& p) {
|
|
||||||
// Resta de vectors (origen - destí)
|
|
||||||
p.x = o.x - d.x;
|
|
||||||
p.y = o.y - d.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
int distancia(const Punt& o, const Punt& d) {
|
|
||||||
// Distància entre dos punts
|
|
||||||
Punt p;
|
|
||||||
diferencia(o, d, p);
|
|
||||||
return static_cast<int>(std::round(modul(p)));
|
|
||||||
}
|
|
||||||
|
|
||||||
float angle_punt(const Punt& p) {
|
|
||||||
// Càlcul de l'angle d'un punt (arctan)
|
|
||||||
if (p.y != 0) {
|
|
||||||
return std::atan(p.x / p.y);
|
|
||||||
}
|
|
||||||
return 0.0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
void crear_poligon_regular(Poligon& pol, uint8_t n, float r) {
|
|
||||||
// Crear un polígon regular amb n costats i radi r
|
|
||||||
// Distribueix els punts uniformement al voltant d'un cercle
|
|
||||||
|
|
||||||
float interval = 2.0F * Defaults::Math::PI / n;
|
|
||||||
float act = 0.0F;
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < n; i++) {
|
|
||||||
pol.ipuntx[i].r = r;
|
|
||||||
pol.ipuntx[i].angle = act;
|
|
||||||
act += interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inicialitzar propietats del polígon
|
|
||||||
pol.centre.x = 320.0F;
|
|
||||||
pol.centre.y = 200.0F;
|
|
||||||
pol.angle = 0.0F;
|
|
||||||
// Convertir velocitat de px/frame a px/s: 2 px/frame × 20 FPS = 40 px/s
|
|
||||||
pol.velocitat = Defaults::Physics::ENEMY_SPEED * 20.0F;
|
|
||||||
pol.n = n;
|
|
||||||
// Convertir rotació de rad/frame a rad/s: 0.0785 rad/frame × 20 FPS = 1.57
|
|
||||||
// rad/s (~90°/s)
|
|
||||||
pol.drotacio = 0.078539816F * 20.0F;
|
|
||||||
pol.rotacio = 0.0F;
|
|
||||||
pol.esta = true;
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// primitives.hpp - Funcions geomètriques bàsiques
|
|
||||||
// © 1999 Visente i Sergi (versió Pascal)
|
|
||||||
// © 2025 Port a C++20 amb SDL3
|
|
||||||
//
|
|
||||||
// ==============================================================================
|
|
||||||
// DEPRECATED: Use Shape system instead (.shp files + ShapeLoader)
|
|
||||||
// ==============================================================================
|
|
||||||
// This file is kept temporarily for chatarra_cosmica_ (Phase 10: explosions)
|
|
||||||
// TODO Phase 10: Replace with particle system or remove completely
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "core/types.hpp"
|
|
||||||
|
|
||||||
// Funcions matemàtiques geomètriques pures (sense dependències d'estat)
|
|
||||||
|
|
||||||
// Càlcul de la magnitud d'un vector
|
|
||||||
float modul(const Punt& p);
|
|
||||||
|
|
||||||
// Diferència entre dos punts (vector origen - destí)
|
|
||||||
void diferencia(const Punt& o, const Punt& d, Punt& p);
|
|
||||||
|
|
||||||
// Distància entre dos punts
|
|
||||||
int distancia(const Punt& o, const Punt& d);
|
|
||||||
|
|
||||||
// Càlcul de l'angle d'un punt
|
|
||||||
float angle_punt(const Punt& p);
|
|
||||||
|
|
||||||
// Creació de polígons regulars
|
|
||||||
void crear_poligon_regular(Poligon& pol, uint8_t n, float r);
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// render_context.hpp - Alias del contexto de rendering del juego
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Punto único de indireción entre el resto del código y el backend de
|
||||||
|
// rendering. El juego habla con un `Rendering::Renderer*` opaco; el alias
|
||||||
|
// apunta a la implementación concreta (SDL3 GPU vía GpuFrameRenderer).
|
||||||
|
//
|
||||||
|
// Antes (BETA 3.0): se propagaba un `SDL_Renderer*` por todas las firmas.
|
||||||
|
// Ahora: `Rendering::Renderer*`. El cambio de backend (Vulkan→Metal vía
|
||||||
|
// SDL_gpu) no toca el código del juego.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/rendering/gpu/gpu_frame_renderer.hpp"
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
// Tipo único del contexto de dibujo que se pasa a las entidades, scenes,
|
||||||
|
// effects, etc. Las llamadas concretas viven en GpuFrameRenderer.
|
||||||
|
using Renderer = GPU::GpuFrameRenderer;
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
// sdl_manager.cpp - Implementació del gestor SDL3
|
// sdl_manager.cpp - Implementació del gestor SDL3
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "sdl_manager.hpp"
|
#include "sdl_manager.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/config/postfx_config.hpp"
|
||||||
#include "core/defaults.hpp"
|
#include "core/defaults.hpp"
|
||||||
#include "core/input/mouse.hpp"
|
#include "core/input/mouse.hpp"
|
||||||
#include "core/rendering/coordinate_transform.hpp"
|
#include "core/rendering/coordinate_transform.hpp"
|
||||||
@@ -14,12 +17,53 @@
|
|||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
auto initWindowAndGpu(SDL_Window** out_window,
|
||||||
|
Rendering::Renderer& gpu_renderer,
|
||||||
|
int width, int height, bool fullscreen) -> 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",
|
||||||
|
Project::LONG_NAME);
|
||||||
|
|
||||||
|
SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE;
|
||||||
|
if (fullscreen) {
|
||||||
|
flags = static_cast<SDL_WindowFlags>(flags | SDL_WINDOW_FULLSCREEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window* window = SDL_CreateWindow(TITLE.c_str(), width, height, flags);
|
||||||
|
if (window == nullptr) {
|
||||||
|
std::cerr << "Error creant finestra: " << SDL_GetError() << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fullscreen) {
|
||||||
|
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializar el FrameRenderer (claim del window + pipeline de líneas).
|
||||||
|
if (!gpu_renderer.init(window,
|
||||||
|
static_cast<float>(Defaults::Game::WIDTH),
|
||||||
|
static_cast<float>(Defaults::Game::HEIGHT))) {
|
||||||
|
std::cerr << "Error inicialitzant GpuFrameRenderer\n";
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpu_renderer.setVSync(Options::rendering.vsync != 0);
|
||||||
|
|
||||||
|
// Cargar parámetros del postpro desde el resource pack. Si el YAML falta
|
||||||
|
// o falla, el loader devuelve los defaults built-in (bloom suave + flicker
|
||||||
|
// sutil + background verde tenue).
|
||||||
|
gpu_renderer.setPostFx(Config::PostFx::load("config/postfx.yaml"));
|
||||||
|
|
||||||
|
*out_window = window;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
SDLManager::SDLManager()
|
SDLManager::SDLManager()
|
||||||
: finestra_(nullptr),
|
: finestra_(nullptr),
|
||||||
renderer_(nullptr),
|
|
||||||
fps_accumulator_(0.0F),
|
|
||||||
fps_frame_count_(0),
|
|
||||||
fps_display_(0),
|
|
||||||
current_width_(Defaults::Window::WIDTH),
|
current_width_(Defaults::Window::WIDTH),
|
||||||
current_height_(Defaults::Window::HEIGHT),
|
current_height_(Defaults::Window::HEIGHT),
|
||||||
is_fullscreen_(false),
|
is_fullscreen_(false),
|
||||||
@@ -29,61 +73,27 @@ SDLManager::SDLManager()
|
|||||||
windowed_width_(Defaults::Window::WIDTH),
|
windowed_width_(Defaults::Window::WIDTH),
|
||||||
windowed_height_(Defaults::Window::HEIGHT),
|
windowed_height_(Defaults::Window::HEIGHT),
|
||||||
max_zoom_(1.0F) {
|
max_zoom_(1.0F) {
|
||||||
// Inicialitzar SDL3
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << '\n';
|
std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << '\n';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular mida màxima des del display
|
|
||||||
calculateMaxWindowSize();
|
calculateMaxWindowSize();
|
||||||
|
|
||||||
// Construir títol dinàmic
|
if (!initWindowAndGpu(&finestra_, gpu_renderer_, current_width_, current_height_, false)) {
|
||||||
std::string window_title = std::format("{} v{} ({})", Project::LONG_NAME, Project::VERSION, Project::COPYRIGHT);
|
|
||||||
|
|
||||||
// Crear finestra CENTRADA (SDL ho fa automàticament amb CENTERED)
|
|
||||||
finestra_ =
|
|
||||||
SDL_CreateWindow(window_title.c_str(), current_width_, current_height_,
|
|
||||||
SDL_WINDOW_RESIZABLE // Permetre resize manual també
|
|
||||||
);
|
|
||||||
|
|
||||||
if (finestra_ == nullptr) {
|
|
||||||
std::cerr << "Error creant finestra: " << SDL_GetError() << '\n';
|
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT: Centrar explícitament la finestra
|
updateViewport();
|
||||||
SDL_SetWindowPosition(finestra_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
||||||
|
|
||||||
// Crear renderer amb acceleració
|
|
||||||
renderer_ = SDL_CreateRenderer(finestra_, nullptr);
|
|
||||||
|
|
||||||
if (renderer_ == nullptr) {
|
|
||||||
std::cerr << "Error creant renderer: " << SDL_GetError() << '\n';
|
|
||||||
SDL_DestroyWindow(finestra_);
|
|
||||||
SDL_Quit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aplicar configuració de V-Sync
|
|
||||||
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
|
|
||||||
|
|
||||||
// CRÍTIC: Configurar viewport scaling
|
|
||||||
updateLogicalPresentation();
|
|
||||||
|
|
||||||
std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_
|
std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_
|
||||||
<< " (logic: " << Defaults::Game::WIDTH << "x"
|
<< " (logic: " << Defaults::Game::WIDTH << "x"
|
||||||
<< Defaults::Game::HEIGHT << ")" << '\n';
|
<< Defaults::Game::HEIGHT << ")" << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor amb configuració
|
|
||||||
SDLManager::SDLManager(int width, int height, bool fullscreen)
|
SDLManager::SDLManager(int width, int height, bool fullscreen)
|
||||||
: finestra_(nullptr),
|
: finestra_(nullptr),
|
||||||
renderer_(nullptr),
|
|
||||||
fps_accumulator_(0.0F),
|
|
||||||
fps_frame_count_(0),
|
|
||||||
fps_display_(0),
|
|
||||||
current_width_(width),
|
current_width_(width),
|
||||||
current_height_(height),
|
current_height_(height),
|
||||||
is_fullscreen_(fullscreen),
|
is_fullscreen_(fullscreen),
|
||||||
@@ -93,56 +103,21 @@ SDLManager::SDLManager(int width, int height, bool fullscreen)
|
|||||||
windowed_width_(width),
|
windowed_width_(width),
|
||||||
windowed_height_(height),
|
windowed_height_(height),
|
||||||
max_zoom_(1.0F) {
|
max_zoom_(1.0F) {
|
||||||
// Inicialitzar SDL3
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << '\n';
|
std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << '\n';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular mida màxima des del display
|
|
||||||
calculateMaxWindowSize();
|
calculateMaxWindowSize();
|
||||||
|
|
||||||
// Construir títol dinàmic
|
if (!initWindowAndGpu(&finestra_, gpu_renderer_, current_width_, current_height_, is_fullscreen_)) {
|
||||||
std::string window_title = std::format("{} v{} ({})", Project::LONG_NAME, Project::VERSION, Project::COPYRIGHT);
|
|
||||||
|
|
||||||
// Configurar flags de la finestra
|
|
||||||
SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE;
|
|
||||||
if (is_fullscreen_) {
|
|
||||||
flags = static_cast<SDL_WindowFlags>(flags | SDL_WINDOW_FULLSCREEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear finestra
|
|
||||||
finestra_ = SDL_CreateWindow(window_title.c_str(), current_width_, current_height_, flags);
|
|
||||||
|
|
||||||
if (finestra_ == nullptr) {
|
|
||||||
std::cerr << "Error creant finestra: " << SDL_GetError() << '\n';
|
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Centrar explícitament la finestra (si no és fullscreen)
|
updateViewport();
|
||||||
if (!is_fullscreen_) {
|
|
||||||
SDL_SetWindowPosition(finestra_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear renderer amb acceleració
|
// En fullscreen: forzar ocultació permanent del cursor.
|
||||||
renderer_ = SDL_CreateRenderer(finestra_, nullptr);
|
|
||||||
|
|
||||||
if (renderer_ == nullptr) {
|
|
||||||
std::cerr << "Error creant renderer: " << SDL_GetError() << '\n';
|
|
||||||
SDL_DestroyWindow(finestra_);
|
|
||||||
SDL_Quit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aplicar configuració de V-Sync
|
|
||||||
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
|
|
||||||
|
|
||||||
// Configurar viewport scaling
|
|
||||||
updateLogicalPresentation();
|
|
||||||
|
|
||||||
// Inicialitzar sistema de cursor
|
|
||||||
// En fullscreen: forzar ocultació permanent
|
|
||||||
if (is_fullscreen_) {
|
if (is_fullscreen_) {
|
||||||
Mouse::setForceHidden(true);
|
Mouse::setForceHidden(true);
|
||||||
}
|
}
|
||||||
@@ -157,10 +132,7 @@ SDLManager::SDLManager(int width, int height, bool fullscreen)
|
|||||||
}
|
}
|
||||||
|
|
||||||
SDLManager::~SDLManager() {
|
SDLManager::~SDLManager() {
|
||||||
if (renderer_ != nullptr) {
|
gpu_renderer_.destroy();
|
||||||
SDL_DestroyRenderer(renderer_);
|
|
||||||
renderer_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finestra_ != nullptr) {
|
if (finestra_ != nullptr) {
|
||||||
SDL_DestroyWindow(finestra_);
|
SDL_DestroyWindow(finestra_);
|
||||||
@@ -176,36 +148,27 @@ void SDLManager::calculateMaxWindowSize() {
|
|||||||
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
|
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
|
||||||
|
|
||||||
if (mode != nullptr) {
|
if (mode != nullptr) {
|
||||||
// Deixar marge de 100px per a decoracions de l'OS
|
// Deixar marge de 100px para decoracions de l'OS
|
||||||
max_width_ = mode->w - 100;
|
max_width_ = mode->w - 100;
|
||||||
max_height_ = mode->h - 100;
|
max_height_ = mode->h - 100;
|
||||||
std::cout << "Display detectat: " << mode->w << "x" << mode->h
|
std::cout << "Display detectat: " << mode->w << "x" << mode->h
|
||||||
<< " (max finestra: " << max_width_ << "x" << max_height_ << ")"
|
<< " (max finestra: " << max_width_ << "x" << max_height_ << ")"
|
||||||
<< '\n';
|
<< '\n';
|
||||||
} else {
|
} else {
|
||||||
// Fallback conservador
|
|
||||||
max_width_ = 1920;
|
max_width_ = 1920;
|
||||||
max_height_ = 1080;
|
max_height_ = 1080;
|
||||||
std::cerr << "No s'ha pogut detectar el display, usant fallback: "
|
std::cerr << "No s'ha pogut detectar el display, usant fallback: "
|
||||||
<< max_width_ << "x" << max_height_ << '\n';
|
<< max_width_ << "x" << max_height_ << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate max zoom immediately after determining max size
|
|
||||||
calculateMaxZoom();
|
calculateMaxZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLManager::calculateMaxZoom() {
|
void SDLManager::calculateMaxZoom() {
|
||||||
// Maximum zoom limited by BOTH width and height (preserves 4:3)
|
|
||||||
float max_zoom_width = static_cast<float>(max_width_) / Defaults::Window::WIDTH;
|
float max_zoom_width = static_cast<float>(max_width_) / Defaults::Window::WIDTH;
|
||||||
float max_zoom_height = static_cast<float>(max_height_) / Defaults::Window::HEIGHT;
|
float max_zoom_height = static_cast<float>(max_height_) / Defaults::Window::HEIGHT;
|
||||||
|
|
||||||
// Take smaller constraint
|
|
||||||
float max_zoom_unrounded = std::min(max_zoom_width, max_zoom_height);
|
float max_zoom_unrounded = std::min(max_zoom_width, max_zoom_height);
|
||||||
|
|
||||||
// Round DOWN to nearest 0.1 increment (user preference)
|
|
||||||
max_zoom_ = std::floor(max_zoom_unrounded / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
|
max_zoom_ = std::floor(max_zoom_unrounded / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
|
||||||
|
|
||||||
// Safety clamp
|
|
||||||
max_zoom_ = std::max(max_zoom_, Defaults::Window::MIN_ZOOM);
|
max_zoom_ = std::max(max_zoom_, Defaults::Window::MIN_ZOOM);
|
||||||
|
|
||||||
std::cout << "Max zoom: " << max_zoom_ << "x (display: "
|
std::cout << "Max zoom: " << max_zoom_ << "x (display: "
|
||||||
@@ -213,37 +176,25 @@ void SDLManager::calculateMaxZoom() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SDLManager::applyZoom(float new_zoom) {
|
void SDLManager::applyZoom(float new_zoom) {
|
||||||
// Clamp to valid range
|
|
||||||
new_zoom = std::max(Defaults::Window::MIN_ZOOM,
|
new_zoom = std::max(Defaults::Window::MIN_ZOOM,
|
||||||
std::min(new_zoom, max_zoom_));
|
std::min(new_zoom, max_zoom_));
|
||||||
|
|
||||||
// Round to nearest 0.1 increment
|
|
||||||
new_zoom = std::round(new_zoom / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
|
new_zoom = std::round(new_zoom / Defaults::Window::ZOOM_INCREMENT) * Defaults::Window::ZOOM_INCREMENT;
|
||||||
|
|
||||||
// No change?
|
|
||||||
if (std::abs(new_zoom - zoom_factor_) < 0.01F) {
|
if (std::abs(new_zoom - zoom_factor_) < 0.01F) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
zoom_factor_ = new_zoom;
|
zoom_factor_ = new_zoom;
|
||||||
|
|
||||||
// Calculate physical dimensions (4:3 maintained automatically)
|
int new_width = static_cast<int>(std::round(Defaults::Window::WIDTH * zoom_factor_));
|
||||||
int new_width = static_cast<int>(std::round(
|
int new_height = static_cast<int>(std::round(Defaults::Window::HEIGHT * zoom_factor_));
|
||||||
Defaults::Window::WIDTH * zoom_factor_));
|
|
||||||
int new_height = static_cast<int>(std::round(
|
|
||||||
Defaults::Window::HEIGHT * zoom_factor_));
|
|
||||||
|
|
||||||
// Apply to window (centers via applyWindowSize)
|
|
||||||
applyWindowSize(new_width, new_height);
|
applyWindowSize(new_width, new_height);
|
||||||
|
|
||||||
// Update viewport for new zoom
|
|
||||||
updateViewport();
|
updateViewport();
|
||||||
|
|
||||||
// Update windowed size cache
|
|
||||||
windowed_width_ = new_width;
|
windowed_width_ = new_width;
|
||||||
windowed_height_ = new_height;
|
windowed_height_ = new_height;
|
||||||
|
|
||||||
// Persist
|
|
||||||
Options::window.width = new_width;
|
Options::window.width = new_width;
|
||||||
Options::window.height = new_height;
|
Options::window.height = new_height;
|
||||||
Options::window.zoom_factor = zoom_factor_;
|
Options::window.zoom_factor = zoom_factor_;
|
||||||
@@ -252,30 +203,23 @@ void SDLManager::applyZoom(float new_zoom) {
|
|||||||
<< new_width << "x" << new_height << ")" << '\n';
|
<< new_width << "x" << new_height << ")" << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLManager::updateLogicalPresentation() {
|
|
||||||
// CANVIAT: Ja no usem SDL_SetRenderLogicalPresentation
|
|
||||||
// Ara renderitzem directament a resolució física per evitar pixelació irregular
|
|
||||||
// El viewport amb letterbox es configura a updateViewport()
|
|
||||||
updateViewport();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLManager::updateViewport() {
|
void SDLManager::updateViewport() {
|
||||||
// Calcular dimensions físiques basades en el zoom
|
// Cálculo de letterbox: el juego se renderiza a 1280×720 lógicos, pero
|
||||||
|
// la swapchain tiene el tamaño físico de la ventana. Aplicamos un viewport
|
||||||
|
// centrado con la proporción 16:9 para preservar aspect ratio.
|
||||||
float scale = zoom_factor_;
|
float scale = zoom_factor_;
|
||||||
int scaled_width = static_cast<int>(std::round(Defaults::Game::WIDTH * scale));
|
int scaled_width = static_cast<int>(std::round(Defaults::Game::WIDTH * scale));
|
||||||
int scaled_height = static_cast<int>(std::round(Defaults::Game::HEIGHT * scale));
|
int scaled_height = static_cast<int>(std::round(Defaults::Game::HEIGHT * scale));
|
||||||
|
|
||||||
// Càlcul de letterbox (centrar l'àrea escalada)
|
|
||||||
int offset_x = (current_width_ - scaled_width) / 2;
|
int offset_x = (current_width_ - scaled_width) / 2;
|
||||||
int offset_y = (current_height_ - scaled_height) / 2;
|
int offset_y = (current_height_ - scaled_height) / 2;
|
||||||
|
|
||||||
// Evitar offsets negatius
|
|
||||||
offset_x = std::max(offset_x, 0);
|
offset_x = std::max(offset_x, 0);
|
||||||
offset_y = std::max(offset_y, 0);
|
offset_y = std::max(offset_y, 0);
|
||||||
|
|
||||||
// Configurar viewport per al renderitzat
|
gpu_renderer_.setViewport(static_cast<float>(offset_x),
|
||||||
SDL_Rect viewport = {offset_x, offset_y, scaled_width, scaled_height};
|
static_cast<float>(offset_y),
|
||||||
SDL_SetRenderViewport(renderer_, &viewport);
|
static_cast<float>(scaled_width),
|
||||||
|
static_cast<float>(scaled_height));
|
||||||
|
|
||||||
std::cout << "Viewport: " << scaled_width << "x" << scaled_height
|
std::cout << "Viewport: " << scaled_width << "x" << scaled_height
|
||||||
<< " @ (" << offset_x << "," << offset_y << ") [scale=" << scale << "]"
|
<< " @ (" << offset_x << "," << offset_y << ") [scale=" << scale << "]"
|
||||||
@@ -283,7 +227,6 @@ void SDLManager::updateViewport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SDLManager::updateRenderingContext() const {
|
void SDLManager::updateRenderingContext() const {
|
||||||
// Actualitzar el factor d'escala global per a totes les funcions de renderitzat
|
|
||||||
Rendering::g_current_scale_factor = zoom_factor_;
|
Rendering::g_current_scale_factor = zoom_factor_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,10 +234,8 @@ void SDLManager::increaseWindowSize() {
|
|||||||
if (is_fullscreen_) {
|
if (is_fullscreen_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float new_zoom = zoom_factor_ + Defaults::Window::ZOOM_INCREMENT;
|
float new_zoom = zoom_factor_ + Defaults::Window::ZOOM_INCREMENT;
|
||||||
applyZoom(new_zoom);
|
applyZoom(new_zoom);
|
||||||
|
|
||||||
std::cout << "F2: Zoom aumentat a " << zoom_factor_ << "x" << '\n';
|
std::cout << "F2: Zoom aumentat a " << zoom_factor_ << "x" << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,15 +243,12 @@ void SDLManager::decreaseWindowSize() {
|
|||||||
if (is_fullscreen_) {
|
if (is_fullscreen_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float new_zoom = zoom_factor_ - Defaults::Window::ZOOM_INCREMENT;
|
float new_zoom = zoom_factor_ - Defaults::Window::ZOOM_INCREMENT;
|
||||||
applyZoom(new_zoom);
|
applyZoom(new_zoom);
|
||||||
|
|
||||||
std::cout << "F1: Zoom reduït a " << zoom_factor_ << "x" << '\n';
|
std::cout << "F1: Zoom reduït a " << zoom_factor_ << "x" << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLManager::applyWindowSize(int new_width, int new_height) {
|
void SDLManager::applyWindowSize(int new_width, int new_height) {
|
||||||
// Obtenir posició actual ABANS del resize
|
|
||||||
int old_x;
|
int old_x;
|
||||||
int old_y;
|
int old_y;
|
||||||
SDL_GetWindowPosition(finestra_, &old_x, &old_y);
|
SDL_GetWindowPosition(finestra_, &old_x, &old_y);
|
||||||
@@ -318,76 +256,56 @@ void SDLManager::applyWindowSize(int new_width, int new_height) {
|
|||||||
int old_width = current_width_;
|
int old_width = current_width_;
|
||||||
int old_height = current_height_;
|
int old_height = current_height_;
|
||||||
|
|
||||||
// Actualitzar mida
|
|
||||||
SDL_SetWindowSize(finestra_, new_width, new_height);
|
SDL_SetWindowSize(finestra_, new_width, new_height);
|
||||||
current_width_ = new_width;
|
current_width_ = new_width;
|
||||||
current_height_ = new_height;
|
current_height_ = new_height;
|
||||||
|
|
||||||
// CENTRADO INTEL·LIGENT (algoritme de pollo)
|
|
||||||
// Calcular nova posició per mantenir la finestra centrada sobre si mateixa
|
|
||||||
int delta_width = old_width - new_width;
|
int delta_width = old_width - new_width;
|
||||||
int delta_height = old_height - new_height;
|
int delta_height = old_height - new_height;
|
||||||
|
|
||||||
int new_x = old_x + (delta_width / 2);
|
int new_x = old_x + (delta_width / 2);
|
||||||
int new_y = old_y + (delta_height / 2);
|
int new_y = old_y + (delta_height / 2);
|
||||||
|
|
||||||
// Evitar que la finestra surti de la pantalla
|
constexpr int TITLEBAR_HEIGHT = 35;
|
||||||
constexpr int TITLEBAR_HEIGHT = 35; // Alçada aproximada de la barra de títol
|
|
||||||
new_x = std::max(new_x, 0);
|
new_x = std::max(new_x, 0);
|
||||||
new_y = std::max(new_y, TITLEBAR_HEIGHT);
|
new_y = std::max(new_y, TITLEBAR_HEIGHT);
|
||||||
|
|
||||||
SDL_SetWindowPosition(finestra_, new_x, new_y);
|
SDL_SetWindowPosition(finestra_, new_x, new_y);
|
||||||
|
|
||||||
// Actualitzar viewport després del resize
|
|
||||||
updateViewport();
|
updateViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLManager::toggleFullscreen() {
|
void SDLManager::toggleFullscreen() {
|
||||||
if (!is_fullscreen_) {
|
if (!is_fullscreen_) {
|
||||||
// ENTERING FULLSCREEN
|
|
||||||
windowed_width_ = current_width_;
|
windowed_width_ = current_width_;
|
||||||
windowed_height_ = current_height_;
|
windowed_height_ = current_height_;
|
||||||
|
|
||||||
is_fullscreen_ = true;
|
is_fullscreen_ = true;
|
||||||
SDL_SetWindowFullscreen(finestra_, true);
|
SDL_SetWindowFullscreen(finestra_, true);
|
||||||
|
|
||||||
std::cout << "F3: Fullscreen activat (guardada: "
|
std::cout << "F3: Fullscreen activat (guardada: "
|
||||||
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
|
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
|
||||||
} else {
|
} else {
|
||||||
// EXITING FULLSCREEN
|
|
||||||
is_fullscreen_ = false;
|
is_fullscreen_ = false;
|
||||||
SDL_SetWindowFullscreen(finestra_, false);
|
SDL_SetWindowFullscreen(finestra_, false);
|
||||||
|
|
||||||
// CRITICAL: Explicitly restore windowed size
|
|
||||||
applyWindowSize(windowed_width_, windowed_height_);
|
applyWindowSize(windowed_width_, windowed_height_);
|
||||||
|
|
||||||
std::cout << "F3: Fullscreen desactivat (restaurada: "
|
std::cout << "F3: Fullscreen desactivat (restaurada: "
|
||||||
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
|
<< windowed_width_ << "x" << windowed_height_ << ")" << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
Options::window.fullscreen = is_fullscreen_;
|
Options::window.fullscreen = is_fullscreen_;
|
||||||
|
|
||||||
// Notificar al mòdul Mouse: Fullscreen requereix ocultació permanent del cursor.
|
|
||||||
// Quan es surt de fullscreen, restaurar el comportament normal d'auto-ocultació.
|
|
||||||
Mouse::setForceHidden(is_fullscreen_);
|
Mouse::setForceHidden(is_fullscreen_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDLManager::handleWindowEvent(const SDL_Event& event) {
|
auto SDLManager::handleWindowEvent(const SDL_Event& event) -> bool {
|
||||||
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
|
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
|
||||||
SDL_GetWindowSize(finestra_, ¤t_width_, ¤t_height_);
|
SDL_GetWindowSize(finestra_, ¤t_width_, ¤t_height_);
|
||||||
|
|
||||||
// Calculate zoom from actual size (may not align to 0.1 increments)
|
|
||||||
float new_zoom = static_cast<float>(current_width_) / Defaults::Window::WIDTH;
|
float new_zoom = static_cast<float>(current_width_) / Defaults::Window::WIDTH;
|
||||||
zoom_factor_ = std::max(Defaults::Window::MIN_ZOOM,
|
zoom_factor_ = std::max(Defaults::Window::MIN_ZOOM,
|
||||||
std::min(new_zoom, max_zoom_));
|
std::min(new_zoom, max_zoom_));
|
||||||
|
|
||||||
// Update windowed cache (if not in fullscreen)
|
|
||||||
if (!is_fullscreen_) {
|
if (!is_fullscreen_) {
|
||||||
windowed_width_ = current_width_;
|
windowed_width_ = current_width_;
|
||||||
windowed_height_ = current_height_;
|
windowed_height_ = current_height_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualitzar viewport després del resize manual
|
|
||||||
updateViewport();
|
updateViewport();
|
||||||
|
|
||||||
std::cout << "Finestra redimensionada: " << current_width_
|
std::cout << "Finestra redimensionada: " << current_width_
|
||||||
@@ -398,84 +316,22 @@ bool SDLManager::handleWindowEvent(const SDL_Event& event) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLManager::neteja(uint8_t r, uint8_t g, uint8_t b) {
|
void SDLManager::clear(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
if (renderer_ == nullptr) {
|
// El fondo lo dibuja ahora el shader de postpro (background pulse). El
|
||||||
return;
|
// offscreen se limpia en negro dentro de beginFrame. Los argumentos r/g/b
|
||||||
}
|
// se mantienen por compatibilidad de API.
|
||||||
|
|
||||||
// [MODIFICAT] Usar color oscil·lat del fons en lloc dels paràmetres
|
|
||||||
(void)r;
|
(void)r;
|
||||||
(void)g;
|
(void)g;
|
||||||
(void)b; // Suprimir warnings
|
(void)b;
|
||||||
SDL_Color bg = color_oscillator_.getCurrentBackgroundColor();
|
gpu_renderer_.beginFrame(0.0F, 0.0F, 0.0F);
|
||||||
SDL_SetRenderDrawColor(renderer_, bg.r, bg.g, bg.b, 255);
|
|
||||||
SDL_RenderClear(renderer_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLManager::presenta() {
|
void SDLManager::present() {
|
||||||
if (renderer_ == nullptr) {
|
gpu_renderer_.endFrame();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_RenderPresent(renderer_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// [NUEVO] Actualitzar colors amb oscil·lació
|
|
||||||
void SDLManager::updateColors(float delta_time) {
|
|
||||||
color_oscillator_.update(delta_time);
|
|
||||||
|
|
||||||
// Actualitzar color global de línies
|
|
||||||
Rendering::setLineColor(color_oscillator_.getCurrentLineColor());
|
|
||||||
}
|
|
||||||
|
|
||||||
// [NUEVO] Actualitzar comptador de FPS
|
|
||||||
void SDLManager::updateFPS(float delta_time) {
|
|
||||||
// Acumular temps i frames
|
|
||||||
fps_accumulator_ += delta_time;
|
|
||||||
fps_frame_count_++;
|
|
||||||
|
|
||||||
// Actualitzar display cada 0.5 segons
|
|
||||||
if (fps_accumulator_ >= 0.5F) {
|
|
||||||
fps_display_ = static_cast<int>(fps_frame_count_ / fps_accumulator_);
|
|
||||||
fps_frame_count_ = 0;
|
|
||||||
fps_accumulator_ = 0.0F;
|
|
||||||
|
|
||||||
// Actualitzar títol de la finestra
|
|
||||||
std::string vsync_state = (Options::rendering.vsync == 1) ? "ON" : "OFF";
|
|
||||||
std::string title = std::format("{} v{} ({}) - {} FPS - VSync: {}",
|
|
||||||
Project::LONG_NAME,
|
|
||||||
Project::VERSION,
|
|
||||||
Project::COPYRIGHT,
|
|
||||||
fps_display_,
|
|
||||||
vsync_state);
|
|
||||||
|
|
||||||
if (finestra_ != nullptr) {
|
|
||||||
SDL_SetWindowTitle(finestra_, title.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [NUEVO] Actualitzar títol de la finestra
|
|
||||||
void SDLManager::setWindowTitle(const std::string& title) {
|
|
||||||
if (finestra_ != nullptr) {
|
|
||||||
SDL_SetWindowTitle(finestra_, title.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [NUEVO] Toggle V-Sync (F4)
|
|
||||||
void SDLManager::toggleVSync() {
|
void SDLManager::toggleVSync() {
|
||||||
// Toggle: 1 → 0 → 1
|
|
||||||
Options::rendering.vsync = (Options::rendering.vsync == 1) ? 0 : 1;
|
Options::rendering.vsync = (Options::rendering.vsync == 1) ? 0 : 1;
|
||||||
|
gpu_renderer_.setVSync(Options::rendering.vsync != 0);
|
||||||
// Aplicar a SDL
|
|
||||||
if (renderer_ != nullptr) {
|
|
||||||
SDL_SetRenderVSync(renderer_, Options::rendering.vsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset FPS counter para evitar valores mixtos entre regímenes
|
|
||||||
fps_accumulator_ = 0.0F;
|
|
||||||
fps_frame_count_ = 0;
|
|
||||||
|
|
||||||
// Guardar configuració
|
|
||||||
Options::saveToFile();
|
Options::saveToFile();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +1,50 @@
|
|||||||
// sdl_manager.hpp - Gestor d'inicialització de SDL3
|
// sdl_manager.hpp - Gestor de inicialización de SDL3
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
//
|
||||||
|
// Tras la Fase 7 de la migración, el rendering ya no usa SDL_Renderer:
|
||||||
|
// SDLManager posee un GpuFrameRenderer (SDL3 GPU) que es el contexto único
|
||||||
|
// de dibujo del juego. El resto del código accede vía getRenderer() →
|
||||||
|
// Rendering::Renderer* (alias del GpuFrameRenderer).
|
||||||
|
|
||||||
#ifndef SDL_MANAGER_HPP
|
#pragma once
|
||||||
#define SDL_MANAGER_HPP
|
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "core/rendering/color_oscillator.hpp"
|
#include "core/rendering/render_context.hpp"
|
||||||
|
|
||||||
class SDLManager {
|
class SDLManager {
|
||||||
public:
|
public:
|
||||||
SDLManager(); // Constructor per defecte (usa Defaults::)
|
SDLManager(); // Constructor per defecte (usa Defaults::)
|
||||||
SDLManager(int width, int height, bool fullscreen); // Constructor amb configuració
|
SDLManager(int width, int height, bool fullscreen); // Constructor con configuración
|
||||||
~SDLManager();
|
~SDLManager();
|
||||||
|
|
||||||
// No permetre còpia ni assignació
|
// No permetre còpia ni assignació
|
||||||
SDLManager(const SDLManager&) = delete;
|
SDLManager(const SDLManager&) = delete;
|
||||||
SDLManager& operator=(const SDLManager&) = delete;
|
auto operator=(const SDLManager&) -> SDLManager& = delete;
|
||||||
|
|
||||||
// [NUEVO] Gestió de finestra dinàmica
|
// [NUEVO] Gestió de finestra dinàmica
|
||||||
void increaseWindowSize(); // F2: +100px
|
void increaseWindowSize(); // F2: +100px
|
||||||
void decreaseWindowSize(); // F1: -100px
|
void decreaseWindowSize(); // F1: -100px
|
||||||
void toggleFullscreen(); // F3
|
void toggleFullscreen(); // F3
|
||||||
void toggleVSync(); // F4
|
void toggleVSync(); // F4
|
||||||
bool handleWindowEvent(const SDL_Event& event); // Per a SDL_EVENT_WINDOW_RESIZED
|
auto handleWindowEvent(const SDL_Event& event) -> bool; // Per a SDL_EVENT_WINDOW_RESIZED
|
||||||
|
|
||||||
// Funcions principals (renderitzat)
|
// Funciones principals (renderizado)
|
||||||
void neteja(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0);
|
void clear(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0);
|
||||||
void presenta();
|
void present();
|
||||||
|
|
||||||
// [NUEVO] Actualització de colors (oscil·lació)
|
|
||||||
void updateColors(float delta_time);
|
|
||||||
|
|
||||||
// [NUEVO] Actualitzar comptador de FPS
|
|
||||||
void updateFPS(float delta_time);
|
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
SDL_Renderer* obte_renderer() { return renderer_; }
|
auto getRenderer() -> Rendering::Renderer* { return &gpu_renderer_; }
|
||||||
[[nodiscard]] float getScaleFactor() const { return zoom_factor_; }
|
[[nodiscard]] auto getScaleFactor() const -> float { return zoom_factor_; }
|
||||||
|
|
||||||
// [NUEVO] Actualitzar títol de la finestra
|
// [NUEVO] Actualitzar context de renderizado (factor de scale global)
|
||||||
void setWindowTitle(const std::string& title);
|
|
||||||
|
|
||||||
// [NUEVO] Actualitzar context de renderitzat (factor d'escala global)
|
|
||||||
void updateRenderingContext() const;
|
void updateRenderingContext() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_Window* finestra_;
|
SDL_Window* finestra_;
|
||||||
SDL_Renderer* renderer_;
|
Rendering::Renderer gpu_renderer_; // GpuFrameRenderer (SDL3 GPU)
|
||||||
|
|
||||||
// [NUEVO] Variables FPS
|
|
||||||
float fps_accumulator_;
|
|
||||||
int fps_frame_count_;
|
|
||||||
int fps_display_;
|
|
||||||
|
|
||||||
// [NUEVO] Estat de la finestra
|
// [NUEVO] Estat de la finestra
|
||||||
int current_width_; // Mida física actual
|
int current_width_; // Mida física actual
|
||||||
@@ -70,16 +59,11 @@ class SDLManager {
|
|||||||
int windowed_height_; // Saved size before fullscreen
|
int windowed_height_; // Saved size before fullscreen
|
||||||
float max_zoom_; // Maximum zoom (calculated from display)
|
float max_zoom_; // Maximum zoom (calculated from display)
|
||||||
|
|
||||||
// [NUEVO] Funcions internes
|
// [NUEVO] Funciones internes
|
||||||
void calculateMaxWindowSize(); // Llegir resolució del display
|
void calculateMaxWindowSize(); // Llegir resolució del display
|
||||||
void calculateMaxZoom(); // Calculate max zoom from display
|
void calculateMaxZoom(); // Calculate max zoom from display
|
||||||
void applyZoom(float new_zoom); // Apply zoom and resize window
|
void applyZoom(float new_zoom); // Apply zoom and resize window
|
||||||
void applyWindowSize(int width, int height); // Canviar mida + centrar
|
void applyWindowSize(int width, int height); // Canviar mida + centrar
|
||||||
void updateLogicalPresentation(); // Actualitzar viewport
|
void updateViewport(); // Configurar viewport con letterbox
|
||||||
void updateViewport(); // Configurar viewport amb letterbox
|
|
||||||
|
|
||||||
// [NUEVO] Oscil·lador de colors
|
|
||||||
Rendering::ColorOscillator color_oscillator_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SDL_MANAGER_HPP
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// shape_renderer.cpp - Implementació del renderitzat de formes
|
// shape_renderer.cpp - Implementació del renderizado de formes
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "core/rendering/shape_renderer.hpp"
|
#include "core/rendering/shape_renderer.hpp"
|
||||||
|
|
||||||
@@ -10,108 +10,100 @@
|
|||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
// Helper: aplicar rotació 3D a un punt 2D (assumeix Z=0)
|
// Helper: aplicar rotación 3D a un point 2D (assumeix Z=0)
|
||||||
static Punt apply_3d_rotation(float x, float y, const Rotation3D& rot) {
|
static Vec2 apply_3d_rotation(float x, float y, const Rotation3D& rot) {
|
||||||
float z = 0.0F; // Tots els punts 2D comencen a Z=0
|
float z = 0.0F; // Todos los points 2D comencen a Z=0
|
||||||
|
|
||||||
// Pitch (rotació eix X): cabeceo arriba/baix
|
// Pitch (rotación eix X): cabeceo arriba/baix
|
||||||
float cos_pitch = std::cos(rot.pitch);
|
float cos_pitch = std::cos(rot.pitch);
|
||||||
float sin_pitch = std::sin(rot.pitch);
|
float sin_pitch = std::sin(rot.pitch);
|
||||||
float y1 = (y * cos_pitch) - (z * sin_pitch);
|
float y1 = (y * cos_pitch) - (z * sin_pitch);
|
||||||
float z1 = (y * sin_pitch) + (z * cos_pitch);
|
float z1 = (y * sin_pitch) + (z * cos_pitch);
|
||||||
|
|
||||||
// Yaw (rotació eix Y): guiñada esquerra/dreta
|
// Yaw (rotación eix Y): guiñada izquierda/derecha
|
||||||
float cos_yaw = std::cos(rot.yaw);
|
float cos_yaw = std::cos(rot.yaw);
|
||||||
float sin_yaw = std::sin(rot.yaw);
|
float sin_yaw = std::sin(rot.yaw);
|
||||||
float x2 = (x * cos_yaw) + (z1 * sin_yaw);
|
float x2 = (x * cos_yaw) + (z1 * sin_yaw);
|
||||||
float z2 = (-x * sin_yaw) + (z1 * cos_yaw);
|
float z2 = (-x * sin_yaw) + (z1 * cos_yaw);
|
||||||
|
|
||||||
// Roll (rotació eix Z): alabeo lateral
|
// Roll (rotación eix Z): alabeo lateral
|
||||||
float cos_roll = std::cos(rot.roll);
|
float cos_roll = std::cos(rot.roll);
|
||||||
float sin_roll = std::sin(rot.roll);
|
float sin_roll = std::sin(rot.roll);
|
||||||
float x3 = (x2 * cos_roll) - (y1 * sin_roll);
|
float x3 = (x2 * cos_roll) - (y1 * sin_roll);
|
||||||
float y3 = (x2 * sin_roll) + (y1 * cos_roll);
|
float y3 = (x2 * sin_roll) + (y1 * cos_roll);
|
||||||
|
|
||||||
// Proyecció perspectiva (Z-divide simple)
|
// Proyecció perspectiva (Z-divide simple)
|
||||||
// Naus volen cap al punt de fuga (320, 240) a "infinit" (Z → +∞)
|
// Naves quieren hacia el point de fuga (320, 240) a "infinit" (Z → +∞)
|
||||||
// Z més gran = més lluny = més petit a pantalla
|
// Z més grande = més lluny = més pequeño a pantalla
|
||||||
constexpr float perspective_factor = 500.0F;
|
constexpr float perspective_factor = 500.0F;
|
||||||
float scale_factor = perspective_factor / (perspective_factor + z2);
|
float scale_factor = perspective_factor / (perspective_factor + z2);
|
||||||
|
|
||||||
return {.x = x3 * scale_factor, .y = y3 * scale_factor};
|
return {.x = x3 * scale_factor, .y = y3 * scale_factor};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: transformar un punt amb rotació, escala i trasllació
|
// Helper: transformar un point con rotación, scale i traslación
|
||||||
static Punt transform_point(const Punt& point, const Punt& shape_centre, const Punt& posicio, float angle, float escala, const Rotation3D* rotation_3d) {
|
static Vec2 transform_point(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale, const Rotation3D* rotation_3d) {
|
||||||
// 1. Centrar el punt respecte al centre de la forma
|
// 1. Centrar el point respecte al centro de la shape
|
||||||
float centered_x = point.x - shape_centre.x;
|
float centered_x = point.x - shape_centre.x;
|
||||||
float centered_y = point.y - shape_centre.y;
|
float centered_y = point.y - shape_centre.y;
|
||||||
|
|
||||||
// 2. Aplicar rotació 3D (si es proporciona)
|
// 2. Aplicar rotación 3D (si es proporciona)
|
||||||
if ((rotation_3d != nullptr) && rotation_3d->has_rotation()) {
|
if ((rotation_3d != nullptr) && rotation_3d->has_rotation()) {
|
||||||
Punt rotated_3d = apply_3d_rotation(centered_x, centered_y, *rotation_3d);
|
Vec2 rotated_3d = apply_3d_rotation(centered_x, centered_y, *rotation_3d);
|
||||||
centered_x = rotated_3d.x;
|
centered_x = rotated_3d.x;
|
||||||
centered_y = rotated_3d.y;
|
centered_y = rotated_3d.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Aplicar escala al punt (després de rotació 3D)
|
// 3. Aplicar scale al point (después de rotación 3D)
|
||||||
float scaled_x = centered_x * escala;
|
float scaled_x = centered_x * scale;
|
||||||
float scaled_y = centered_y * escala;
|
float scaled_y = centered_y * scale;
|
||||||
|
|
||||||
// 4. Aplicar rotació 2D (Z-axis, tradicional)
|
// 4. Aplicar rotación 2D (Z-axis, tradicional)
|
||||||
// IMPORTANT: En el sistema original, angle=0 apunta AMUNT (no dreta)
|
// IMPORTANT: En el sistema original, angle=0 apunta AMUNT (no derecha)
|
||||||
// Per això usem (angle - PI/2) per compensar
|
// Per això usem (angle - PI/2) per compensar
|
||||||
// Però aquí angle ja ve en el sistema correcte del joc
|
// Pero aquí angle ya ve en el sistema correcte del juego
|
||||||
float cos_a = std::cos(angle);
|
float cos_a = std::cos(angle);
|
||||||
float sin_a = std::sin(angle);
|
float sin_a = std::sin(angle);
|
||||||
|
|
||||||
float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a);
|
float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a);
|
||||||
float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a);
|
float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a);
|
||||||
|
|
||||||
// 5. Aplicar trasllació a posició mundial
|
// 5. Aplicar traslación a posición mundial
|
||||||
return {.x = rotated_x + posicio.x, .y = rotated_y + posicio.y};
|
return {.x = rotated_x + position.x, .y = rotated_y + position.y};
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_shape(SDL_Renderer* renderer,
|
void render_shape(Rendering::Renderer* renderer,
|
||||||
const std::shared_ptr<Graphics::Shape>& shape,
|
const std::shared_ptr<Graphics::Shape>& shape,
|
||||||
const Punt& posicio,
|
const Vec2& position,
|
||||||
float angle,
|
float angle,
|
||||||
float escala,
|
float scale,
|
||||||
bool dibuixar,
|
|
||||||
float progress,
|
float progress,
|
||||||
float brightness,
|
float brightness,
|
||||||
const Rotation3D* rotation_3d) {
|
const Rotation3D* rotation_3d,
|
||||||
// Verificar que la forma és vàlida
|
SDL_Color color) {
|
||||||
if (!shape || !shape->es_valida()) {
|
if (!shape || !shape->isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si progress < 1.0, no dibuixar (tot o res)
|
|
||||||
if (progress < 1.0F) {
|
if (progress < 1.0F) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtenir el centre de la forma per a transformacions
|
const Vec2& SHAPE_CENTRE = shape->getCenter();
|
||||||
const Punt& shape_centre = shape->get_centre();
|
|
||||||
|
|
||||||
// Iterar sobre totes les primitives
|
|
||||||
for (const auto& primitive : shape->get_primitives()) {
|
for (const auto& primitive : shape->get_primitives()) {
|
||||||
if (primitive.type == Graphics::PrimitiveType::POLYLINE) {
|
if (primitive.type == Graphics::PrimitiveType::POLYLINE) {
|
||||||
// POLYLINE: connectar punts consecutius
|
// POLYLINE: conectar puntos consecutivos.
|
||||||
for (size_t i = 0; i < primitive.points.size() - 1; i++) {
|
for (size_t i = 0; i < primitive.points.size() - 1; i++) {
|
||||||
Punt p1 = transform_point(primitive.points[i], shape_centre, posicio, angle, escala, rotation_3d);
|
const Vec2 P1 = transform_point(primitive.points[i], SHAPE_CENTRE, position, angle, scale, rotation_3d);
|
||||||
Punt p2 = transform_point(primitive.points[i + 1], shape_centre, posicio, angle, escala, rotation_3d);
|
const Vec2 P2 = transform_point(primitive.points[i + 1], SHAPE_CENTRE, position, angle, scale, rotation_3d);
|
||||||
|
linea(renderer, static_cast<int>(P1.x), static_cast<int>(P1.y),
|
||||||
linea(renderer, static_cast<int>(p1.x), static_cast<int>(p1.y), static_cast<int>(p2.x), static_cast<int>(p2.y), dibuixar, brightness);
|
static_cast<int>(P2.x), static_cast<int>(P2.y), brightness, 0.0F, color);
|
||||||
}
|
|
||||||
} else { // PrimitiveType::LINE
|
|
||||||
// LINE: exactament 2 punts
|
|
||||||
if (primitive.points.size() >= 2) {
|
|
||||||
Punt p1 = transform_point(primitive.points[0], shape_centre, posicio, angle, escala, rotation_3d);
|
|
||||||
Punt p2 = transform_point(primitive.points[1], shape_centre, posicio, angle, escala, rotation_3d);
|
|
||||||
|
|
||||||
linea(renderer, static_cast<int>(p1.x), static_cast<int>(p1.y), static_cast<int>(p2.x), static_cast<int>(p2.y), dibuixar, brightness);
|
|
||||||
}
|
}
|
||||||
|
} else if (primitive.points.size() >= 2) { // LINE
|
||||||
|
const Vec2 P1 = transform_point(primitive.points[0], SHAPE_CENTRE, position, angle, scale, rotation_3d);
|
||||||
|
const Vec2 P2 = transform_point(primitive.points[1], SHAPE_CENTRE, position, angle, scale, rotation_3d);
|
||||||
|
linea(renderer, static_cast<int>(P1.x), static_cast<int>(P1.y),
|
||||||
|
static_cast<int>(P2.x), static_cast<int>(P2.y), brightness, 0.0F, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// shape_renderer.hpp - Renderitzat de formes vectorials
|
// shape_renderer.hpp - Renderizado de formes vectorials
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -14,9 +16,9 @@ namespace Rendering {
|
|||||||
|
|
||||||
// Estructura per rotacions 3D (pitch, yaw, roll)
|
// Estructura per rotacions 3D (pitch, yaw, roll)
|
||||||
struct Rotation3D {
|
struct Rotation3D {
|
||||||
float pitch; // Rotació eix X (cabeceo arriba/baix)
|
float pitch; // Rotación eix X (cabeceo arriba/baix)
|
||||||
float yaw; // Rotació eix Y (guiñada esquerra/dreta)
|
float yaw; // Rotación eix Y (guiñada izquierda/derecha)
|
||||||
float roll; // Rotació eix Z (alabeo lateral)
|
float roll; // Rotación eix Z (alabeo lateral)
|
||||||
|
|
||||||
Rotation3D()
|
Rotation3D()
|
||||||
: pitch(0.0F),
|
: pitch(0.0F),
|
||||||
@@ -32,23 +34,22 @@ struct Rotation3D {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Renderitzar forma amb transformacions
|
// Renderizar shape con transformacions
|
||||||
// - renderer: SDL renderer
|
// - renderer: SDL renderer
|
||||||
// - shape: forma vectorial a dibuixar
|
// - shape: shape vectorial a draw
|
||||||
// - posicio: posició del centre en coordenades mundials
|
// - position: posición del centro en coordenades mundials
|
||||||
// - angle: rotació en radians (0 = amunt, sentit horari)
|
// - angle: rotación en radians (0 = amunt, sentit horari)
|
||||||
// - escala: factor d'escala (1.0 = mida original)
|
// - scale: factor de scale (1.0 = mida original)
|
||||||
// - dibuixar: flag per dibuixar (false per col·lisions futures)
|
// - progress: progrés de l'animación (0.0-1.0, default 1.0 = tot visible)
|
||||||
// - progress: progrés de l'animació (0.0-1.0, default 1.0 = tot visible)
|
// - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness)
|
||||||
// - brightness: factor de brillantor (0.0-1.0, default 1.0 = màxima brillantor)
|
void render_shape(Rendering::Renderer* renderer,
|
||||||
void render_shape(SDL_Renderer* renderer,
|
|
||||||
const std::shared_ptr<Graphics::Shape>& shape,
|
const std::shared_ptr<Graphics::Shape>& shape,
|
||||||
const Punt& posicio,
|
const Vec2& position,
|
||||||
float angle,
|
float angle,
|
||||||
float escala = 1.0F,
|
float scale = 1.0F,
|
||||||
bool dibuixar = true,
|
|
||||||
float progress = 1.0F,
|
float progress = 1.0F,
|
||||||
float brightness = 1.0F,
|
float brightness = 1.0F,
|
||||||
const Rotation3D* rotation_3d = nullptr);
|
const Rotation3D* rotation_3d = nullptr,
|
||||||
|
SDL_Color color = {0, 0, 0, 0}); // alpha==0 → usa global oscilador
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// resource_helper.cpp - Implementació de funcions d'ajuda
|
// resource_helper.cpp - Implementació de funciones de ajuda
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "resource_helper.hpp"
|
#include "resource_helper.hpp"
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ bool initializeResourceSystem(const std::string& pack_file, bool fallback) {
|
|||||||
return Loader::get().initialize(pack_file, fallback);
|
return Loader::get().initialize(pack_file, fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carregar un fitxer
|
// Carregar un file
|
||||||
std::vector<uint8_t> loadFile(const std::string& filepath) {
|
std::vector<uint8_t> loadFile(const std::string& filepath) {
|
||||||
// Normalitzar la ruta
|
// Normalitzar la ruta
|
||||||
std::string normalized = normalizePath(filepath);
|
std::string normalized = normalizePath(filepath);
|
||||||
@@ -24,7 +24,7 @@ std::vector<uint8_t> loadFile(const std::string& filepath) {
|
|||||||
return Loader::get().loadResource(normalized);
|
return Loader::get().loadResource(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprovar si existeix un fitxer
|
// Comprovar si existeix un file
|
||||||
bool fileExists(const std::string& filepath) {
|
bool fileExists(const std::string& filepath) {
|
||||||
std::string normalized = normalizePath(filepath);
|
std::string normalized = normalizePath(filepath);
|
||||||
return Loader::get().resourceExists(normalized);
|
return Loader::get().resourceExists(normalized);
|
||||||
@@ -37,7 +37,7 @@ std::string getPackPath(const std::string& asset_path) {
|
|||||||
|
|
||||||
// Eliminar rutes absolutes (detectar / o C:\ al principi)
|
// Eliminar rutes absolutes (detectar / o C:\ al principi)
|
||||||
if (!path.empty() && path[0] == '/') {
|
if (!path.empty() && path[0] == '/') {
|
||||||
// Buscar "data/" i agafar el que ve després
|
// Buscar "data/" i agafar el que ve después
|
||||||
size_t data_pos = path.find("/data/");
|
size_t data_pos = path.find("/data/");
|
||||||
if (data_pos != std::string::npos) {
|
if (data_pos != std::string::npos) {
|
||||||
path = path.substr(data_pos + 6); // Saltar "/data/"
|
path = path.substr(data_pos + 6); // Saltar "/data/"
|
||||||
@@ -73,7 +73,7 @@ std::string normalizePath(const std::string& path) {
|
|||||||
return getPackPath(path);
|
return getPackPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprovar si hi ha paquet carregat
|
// Comprovar si hay paquet carregat
|
||||||
bool isPackLoaded() {
|
bool isPackLoaded() {
|
||||||
return Loader::get().isPackLoaded();
|
return Loader::get().isPackLoaded();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// resource_helper.hpp - Funcions d'ajuda per gestió de recursos
|
// resource_helper.hpp - Funciones de ajuda per gestió de recursos
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
// API simplificada i normalització de rutes
|
// API simplificada i normalització de rutes
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
namespace Resource::Helper {
|
namespace Resource::Helper {
|
||||||
|
|
||||||
// Inicialització del sistema
|
// Inicialización del sistema
|
||||||
bool initializeResourceSystem(const std::string& pack_file, bool fallback);
|
bool initializeResourceSystem(const std::string& pack_file, bool fallback);
|
||||||
|
|
||||||
// Càrrega de fitxers
|
// Càrrega de archivos
|
||||||
std::vector<uint8_t> loadFile(const std::string& filepath);
|
std::vector<uint8_t> loadFile(const std::string& filepath);
|
||||||
bool fileExists(const std::string& filepath);
|
bool fileExists(const std::string& filepath);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// resource_loader.cpp - Implementació del carregador de recursos
|
// resource_loader.cpp - Implementació del carregador de recursos
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "resource_loader.hpp"
|
#include "resource_loader.hpp"
|
||||||
|
|
||||||
@@ -19,18 +19,18 @@ Loader& Loader::get() {
|
|||||||
bool Loader::initialize(const std::string& pack_file, bool enable_fallback) {
|
bool Loader::initialize(const std::string& pack_file, bool enable_fallback) {
|
||||||
fallback_enabled_ = enable_fallback;
|
fallback_enabled_ = enable_fallback;
|
||||||
|
|
||||||
// Intentar carregar el paquet
|
// Intentar load el paquet
|
||||||
pack_ = std::make_unique<Pack>();
|
pack_ = std::make_unique<Pack>();
|
||||||
|
|
||||||
if (!pack_->loadPack(pack_file)) {
|
if (!pack_->loadPack(pack_file)) {
|
||||||
if (!fallback_enabled_) {
|
if (!fallback_enabled_) {
|
||||||
std::cerr << "[ResourceLoader] ERROR FATAL: No es pot carregar " << pack_file
|
std::cerr << "[ResourceLoader] ERROR FATAL: No es pot load " << pack_file
|
||||||
<< " i el fallback està desactivat\n";
|
<< " y el fallback está desactivat\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "[ResourceLoader] Paquet no trobat, usant fallback al sistema de fitxers\n";
|
std::cout << "[ResourceLoader] Paquet no trobat, usant fallback al sistema de archivos\n";
|
||||||
pack_.reset(); // No hi ha paquet
|
pack_.reset(); // No hay paquet
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ bool Loader::initialize(const std::string& pack_file, bool enable_fallback) {
|
|||||||
|
|
||||||
// Carregar un recurs
|
// Carregar un recurs
|
||||||
std::vector<uint8_t> Loader::loadResource(const std::string& filename) {
|
std::vector<uint8_t> Loader::loadResource(const std::string& filename) {
|
||||||
// Intentar carregar del paquet primer
|
// Intentar load del paquet primer
|
||||||
if (pack_) {
|
if (pack_) {
|
||||||
if (pack_->hasResource(filename)) {
|
if (pack_->hasResource(filename)) {
|
||||||
auto data = pack_->getResource(filename);
|
auto data = pack_->getResource(filename);
|
||||||
@@ -51,7 +51,7 @@ std::vector<uint8_t> Loader::loadResource(const std::string& filename) {
|
|||||||
<< "\n";
|
<< "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si no està al paquet i no hi ha fallback, falla
|
// Si no está al paquet y no hay fallback, falla
|
||||||
if (!fallback_enabled_) {
|
if (!fallback_enabled_) {
|
||||||
std::cerr << "[ResourceLoader] ERROR: Recurs no trobat al paquet i fallback desactivat: "
|
std::cerr << "[ResourceLoader] ERROR: Recurs no trobat al paquet i fallback desactivat: "
|
||||||
<< filename << "\n";
|
<< filename << "\n";
|
||||||
@@ -59,7 +59,7 @@ std::vector<uint8_t> Loader::loadResource(const std::string& filename) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback al sistema de fitxers
|
// Fallback al sistema de archivos
|
||||||
if (fallback_enabled_) {
|
if (fallback_enabled_) {
|
||||||
return loadFromFilesystem(filename);
|
return loadFromFilesystem(filename);
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ bool Loader::resourceExists(const std::string& filename) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprovar al sistema de fitxers si està activat el fallback
|
// Comprovar al sistema de archivos si está activat el fallback
|
||||||
if (fallback_enabled_) {
|
if (fallback_enabled_) {
|
||||||
std::string fullpath = base_path_.empty() ? "data/" + filename : base_path_ + "/data/" + filename;
|
std::string fullpath = base_path_.empty() ? "data/" + filename : base_path_ + "/data/" + filename;
|
||||||
return std::filesystem::exists(fullpath);
|
return std::filesystem::exists(fullpath);
|
||||||
@@ -86,14 +86,14 @@ bool Loader::resourceExists(const std::string& filename) {
|
|||||||
// Validar el paquet
|
// Validar el paquet
|
||||||
bool Loader::validatePack() {
|
bool Loader::validatePack() {
|
||||||
if (!pack_) {
|
if (!pack_) {
|
||||||
std::cerr << "[ResourceLoader] Advertència: no hi ha paquet carregat per validar\n";
|
std::cerr << "[ResourceLoader] Advertència: no hay paquet carregat per validar\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pack_->validatePack();
|
return pack_->validatePack();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprovar si hi ha paquet carregat
|
// Comprovar si hay paquet carregat
|
||||||
bool Loader::isPackLoaded() const {
|
bool Loader::isPackLoaded() const {
|
||||||
return pack_ != nullptr;
|
return pack_ != nullptr;
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ std::string Loader::getBasePath() const {
|
|||||||
return base_path_;
|
return base_path_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carregar des del sistema de fitxers (fallback)
|
// Carregar des del sistema de archivos (fallback)
|
||||||
std::vector<uint8_t> Loader::loadFromFilesystem(const std::string& filename) {
|
std::vector<uint8_t> Loader::loadFromFilesystem(const std::string& filename) {
|
||||||
// The filename is already normalized (e.g., "shapes/logo/letra_j.shp")
|
// The filename is already normalized (e.g., "shapes/logo/letra_j.shp")
|
||||||
// We need to prepend base_path + "data/"
|
// We need to prepend base_path + "data/"
|
||||||
@@ -136,7 +136,7 @@ std::vector<uint8_t> Loader::loadFromFilesystem(const std::string& filename) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "[ResourceLoader] Carregat des del sistema de fitxers: " << fullpath << "\n";
|
std::cout << "[ResourceLoader] Carregat des del sistema de archivos: " << fullpath << "\n";
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// resource_loader.hpp - Carregador de recursos (Singleton)
|
// resource_loader.hpp - Carregador de recursos (Singleton)
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
// Coordina càrrega des del paquet i/o sistema de fitxers
|
// Coordina càrrega des del paquet i/o sistema de archivos
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ class Loader {
|
|||||||
// Singleton
|
// Singleton
|
||||||
static Loader& get();
|
static Loader& get();
|
||||||
|
|
||||||
// Inicialització
|
// Inicialización
|
||||||
bool initialize(const std::string& pack_file, bool enable_fallback);
|
bool initialize(const std::string& pack_file, bool enable_fallback);
|
||||||
|
|
||||||
// Càrrega de recursos
|
// Càrrega de recursos
|
||||||
@@ -46,7 +46,7 @@ class Loader {
|
|||||||
bool fallback_enabled_ = false;
|
bool fallback_enabled_ = false;
|
||||||
std::string base_path_;
|
std::string base_path_;
|
||||||
|
|
||||||
// Funcions auxiliars
|
// Funciones auxiliars
|
||||||
std::vector<uint8_t> loadFromFilesystem(const std::string& filename);
|
std::vector<uint8_t> loadFromFilesystem(const std::string& filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// resource_pack.cpp - Implementació del sistema d'empaquetament
|
// resource_pack.cpp - Implementació del sistema de empaquetament
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "resource_pack.hpp"
|
#include "resource_pack.hpp"
|
||||||
|
|
||||||
@@ -30,11 +30,11 @@ void Pack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Pack::decryptData(std::vector<uint8_t>& data, const std::string& key) {
|
void Pack::decryptData(std::vector<uint8_t>& data, const std::string& key) {
|
||||||
// XOR és simètric
|
// XOR es simètric
|
||||||
encryptData(data, key);
|
encryptData(data, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Llegir fitxer complet a memòria
|
// Llegir file complet a memòria
|
||||||
std::vector<uint8_t> Pack::readFile(const std::string& filepath) {
|
std::vector<uint8_t> Pack::readFile(const std::string& filepath) {
|
||||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@@ -54,7 +54,7 @@ std::vector<uint8_t> Pack::readFile(const std::string& filepath) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Afegir un fitxer individual al paquet
|
// Añadir un file individual al paquet
|
||||||
bool Pack::addFile(const std::string& filepath, const std::string& pack_name) {
|
bool Pack::addFile(const std::string& filepath, const std::string& pack_name) {
|
||||||
auto file_data = readFile(filepath);
|
auto file_data = readFile(filepath);
|
||||||
if (file_data.empty()) {
|
if (file_data.empty()) {
|
||||||
@@ -67,17 +67,17 @@ bool Pack::addFile(const std::string& filepath, const std::string& pack_name) {
|
|||||||
.size = file_data.size(),
|
.size = file_data.size(),
|
||||||
.checksum = calculateChecksum(file_data)};
|
.checksum = calculateChecksum(file_data)};
|
||||||
|
|
||||||
// Afegir dades al bloc de dades
|
// Añadir dades al bloc de dades
|
||||||
data_.insert(data_.end(), file_data.begin(), file_data.end());
|
data_.insert(data_.end(), file_data.begin(), file_data.end());
|
||||||
|
|
||||||
resources_[pack_name] = entry;
|
resources_[pack_name] = entry;
|
||||||
|
|
||||||
std::cout << "[ResourcePack] Afegit: " << pack_name << " (" << file_data.size()
|
std::cout << "[ResourcePack] Añadido: " << pack_name << " (" << file_data.size()
|
||||||
<< " bytes)\n";
|
<< " bytes)\n";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Afegir tots els fitxers d'un directori recursivament
|
// Añadir todos los archivos de un directori recursivament
|
||||||
bool Pack::addDirectory(const std::string& dir_path,
|
bool Pack::addDirectory(const std::string& dir_path,
|
||||||
const std::string& base_path) {
|
const std::string& base_path) {
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
@@ -100,7 +100,7 @@ bool Pack::addDirectory(const std::string& dir_path,
|
|||||||
// Convertir barres invertides a normals (Windows)
|
// Convertir barres invertides a normals (Windows)
|
||||||
std::ranges::replace(relative_path, '\\', '/');
|
std::ranges::replace(relative_path, '\\', '/');
|
||||||
|
|
||||||
// Saltar fitxers de desenvolupament
|
// Saltar archivos de desenvolupament
|
||||||
if (relative_path.find(".world") != std::string::npos ||
|
if (relative_path.find(".world") != std::string::npos ||
|
||||||
relative_path.find(".tsx") != std::string::npos ||
|
relative_path.find(".tsx") != std::string::npos ||
|
||||||
relative_path.find(".DS_Store") != std::string::npos ||
|
relative_path.find(".DS_Store") != std::string::npos ||
|
||||||
@@ -134,7 +134,7 @@ bool Pack::savePack(const std::string& pack_file) {
|
|||||||
|
|
||||||
// Escriure metadades de recursos
|
// Escriure metadades de recursos
|
||||||
for (const auto& [name, entry] : resources_) {
|
for (const auto& [name, entry] : resources_) {
|
||||||
// Nom del fitxer
|
// Nom del file
|
||||||
auto name_len = static_cast<uint32_t>(entry.filename.length());
|
auto name_len = static_cast<uint32_t>(entry.filename.length());
|
||||||
file.write(reinterpret_cast<const char*>(&name_len), sizeof(name_len));
|
file.write(reinterpret_cast<const char*>(&name_len), sizeof(name_len));
|
||||||
file.write(entry.filename.c_str(), name_len);
|
file.write(entry.filename.c_str(), name_len);
|
||||||
@@ -149,7 +149,7 @@ bool Pack::savePack(const std::string& pack_file) {
|
|||||||
std::vector<uint8_t> encrypted_data = data_;
|
std::vector<uint8_t> encrypted_data = data_;
|
||||||
encryptData(encrypted_data, DEFAULT_ENCRYPT_KEY);
|
encryptData(encrypted_data, DEFAULT_ENCRYPT_KEY);
|
||||||
|
|
||||||
// Escriure mida de dades i dades encriptades
|
// Escriure mida de dades y dades encriptades
|
||||||
auto data_size = static_cast<uint64_t>(encrypted_data.size());
|
auto data_size = static_cast<uint64_t>(encrypted_data.size());
|
||||||
file.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
|
file.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
|
||||||
file.write(reinterpret_cast<const char*>(encrypted_data.data()), encrypted_data.size());
|
file.write(reinterpret_cast<const char*>(encrypted_data.data()), encrypted_data.size());
|
||||||
@@ -160,7 +160,7 @@ bool Pack::savePack(const std::string& pack_file) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carregar paquet des de disc
|
// Carregar paquet desde disc
|
||||||
bool Pack::loadPack(const std::string& pack_file) {
|
bool Pack::loadPack(const std::string& pack_file) {
|
||||||
std::ifstream file(pack_file, std::ios::binary);
|
std::ifstream file(pack_file, std::ios::binary);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@@ -180,7 +180,7 @@ bool Pack::loadPack(const std::string& pack_file) {
|
|||||||
uint32_t version;
|
uint32_t version;
|
||||||
file.read(reinterpret_cast<char*>(&version), sizeof(version));
|
file.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||||
if (version != VERSION) {
|
if (version != VERSION) {
|
||||||
std::cerr << "[ResourcePack] Error: versió incompatible (esperava " << VERSION
|
std::cerr << "[ResourcePack] Error: versión incompatible (esperava " << VERSION
|
||||||
<< ", trobat " << version << ")\n";
|
<< ", trobat " << version << ")\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,7 @@ bool Pack::loadPack(const std::string& pack_file) {
|
|||||||
// Llegir metadades de recursos
|
// Llegir metadades de recursos
|
||||||
resources_.clear();
|
resources_.clear();
|
||||||
for (uint32_t i = 0; i < resource_count; ++i) {
|
for (uint32_t i = 0; i < resource_count; ++i) {
|
||||||
// Nom del fitxer
|
// Nom del file
|
||||||
uint32_t name_len;
|
uint32_t name_len;
|
||||||
file.read(reinterpret_cast<char*>(&name_len), sizeof(name_len));
|
file.read(reinterpret_cast<char*>(&name_len), sizeof(name_len));
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ std::vector<uint8_t> Pack::getResource(const std::string& filename) {
|
|||||||
std::cerr << "[ResourcePack] ADVERTÈNCIA: checksum invàlid per " << filename
|
std::cerr << "[ResourcePack] ADVERTÈNCIA: checksum invàlid per " << filename
|
||||||
<< " (esperat " << entry.checksum << ", calculat " << computed_checksum
|
<< " (esperat " << entry.checksum << ", calculat " << computed_checksum
|
||||||
<< ")\n";
|
<< ")\n";
|
||||||
// No falla, però adverteix
|
// No falla, pero adverteix
|
||||||
}
|
}
|
||||||
|
|
||||||
return resource_data;
|
return resource_data;
|
||||||
@@ -261,7 +261,7 @@ bool Pack::hasResource(const std::string& filename) const {
|
|||||||
return resources_.contains(filename);
|
return resources_.contains(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtenir llista de tots els recursos
|
// Obtenir list de todos los recursos
|
||||||
std::vector<std::string> Pack::getResourceList() const {
|
std::vector<std::string> Pack::getResourceList() const {
|
||||||
std::vector<std::string> list;
|
std::vector<std::string> list;
|
||||||
list.reserve(resources_.size());
|
list.reserve(resources_.size());
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// resource_pack.hpp - Sistema d'empaquetament de recursos
|
// resource_pack.hpp - Sistema de empaquetament de recursos
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
// Basat en el sistema de "pollo" amb adaptacions per Orni Attack
|
// Basat en el sistema de "pollo" con adaptacions per Orni Attack
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -11,31 +11,31 @@
|
|||||||
|
|
||||||
namespace Resource {
|
namespace Resource {
|
||||||
|
|
||||||
// Capçalera del fitxer de paquet
|
// Capçalera del file de paquet
|
||||||
struct PackHeader {
|
struct PackHeader {
|
||||||
char magic[4]; // "ORNI"
|
char magic[4]; // "ORNI"
|
||||||
uint32_t version; // Versió del format (1)
|
uint32_t version; // Versión del format (1)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Entrada de recurs dins el paquet
|
// Entrada de recurs dins el paquet
|
||||||
struct ResourceEntry {
|
struct ResourceEntry {
|
||||||
std::string filename; // Nom del recurs (amb barres normals)
|
std::string filename; // Nom del recurs (con barres normals)
|
||||||
uint64_t offset; // Posició dins el bloc de dades
|
uint64_t offset; // Posición dins el bloc de dades
|
||||||
uint64_t size; // Mida en bytes
|
uint64_t size; // Mida en bytes
|
||||||
uint32_t checksum; // Checksum CRC32 per verificació
|
uint32_t checksum; // Checksum CRC32 per verificació
|
||||||
};
|
};
|
||||||
|
|
||||||
// Classe principal per gestionar paquets de recursos
|
// Clase principal per gestionar paquets de recursos
|
||||||
class Pack {
|
class Pack {
|
||||||
public:
|
public:
|
||||||
Pack() = default;
|
Pack() = default;
|
||||||
~Pack() = default;
|
~Pack() = default;
|
||||||
|
|
||||||
// Afegir fitxers al paquet
|
// Añadir archivos al paquet
|
||||||
bool addFile(const std::string& filepath, const std::string& pack_name);
|
bool addFile(const std::string& filepath, const std::string& pack_name);
|
||||||
bool addDirectory(const std::string& dir_path, const std::string& base_path = "");
|
bool addDirectory(const std::string& dir_path, const std::string& base_path = "");
|
||||||
|
|
||||||
// Guardar i carregar paquets
|
// Guardar i load paquets
|
||||||
bool savePack(const std::string& pack_file);
|
bool savePack(const std::string& pack_file);
|
||||||
bool loadPack(const std::string& pack_file);
|
bool loadPack(const std::string& pack_file);
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ class Pack {
|
|||||||
std::unordered_map<std::string, ResourceEntry> resources_;
|
std::unordered_map<std::string, ResourceEntry> resources_;
|
||||||
std::vector<uint8_t> data_;
|
std::vector<uint8_t> data_;
|
||||||
|
|
||||||
// Funcions auxiliars
|
// Funciones auxiliars
|
||||||
std::vector<uint8_t> readFile(const std::string& filepath);
|
std::vector<uint8_t> readFile(const std::string& filepath);
|
||||||
[[nodiscard]] uint32_t calculateChecksum(const std::vector<uint8_t>& data) const;
|
[[nodiscard]] uint32_t calculateChecksum(const std::vector<uint8_t>& data) const;
|
||||||
void encryptData(std::vector<uint8_t>& data, const std::string& key);
|
void encryptData(std::vector<uint8_t>& data, const std::string& key);
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
// context_escenes.hpp - Sistema de gestió d'escenes i context de transicions
|
|
||||||
// © 2025 Port a C++20
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "core/system/game_config.hpp"
|
|
||||||
|
|
||||||
namespace GestorEscenes {
|
|
||||||
|
|
||||||
// Context de transició entre escenes
|
|
||||||
// Conté l'escena destinació i opcions específiques per aquella escena
|
|
||||||
class ContextEscenes {
|
|
||||||
public:
|
|
||||||
// Tipus d'escena del joc
|
|
||||||
enum class Escena {
|
|
||||||
LOGO, // Pantalla d'inici (logo JAILGAMES)
|
|
||||||
TITOL, // Pantalla de títol amb menú
|
|
||||||
JOC, // Joc principal (Asteroids)
|
|
||||||
EIXIR // Sortir del programa
|
|
||||||
};
|
|
||||||
|
|
||||||
// Opcions específiques per a cada escena
|
|
||||||
enum class Opcio {
|
|
||||||
NONE, // Sense opcions especials (comportament per defecte)
|
|
||||||
JUMP_TO_TITLE_MAIN, // TITOL: Saltar directament a MAIN (starfield instantani)
|
|
||||||
// MODE_DEMO, // JOC: Mode demostració amb IA (futur)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constructor inicial amb escena LOGO i sense opcions
|
|
||||||
ContextEscenes() = default;
|
|
||||||
|
|
||||||
// Canviar escena amb opció específica
|
|
||||||
void canviar_escena(Escena nova_escena, Opcio opcio = Opcio::NONE) {
|
|
||||||
escena_desti_ = nova_escena;
|
|
||||||
opcio_ = opcio;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consultar escena destinació
|
|
||||||
[[nodiscard]] auto escena_desti() const -> Escena {
|
|
||||||
return escena_desti_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consultar opció actual
|
|
||||||
[[nodiscard]] auto opcio() const -> Opcio {
|
|
||||||
return opcio_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumir opció (retorna valor i reseteja a NONE)
|
|
||||||
// Utilitzar quan l'escena processa l'opció
|
|
||||||
[[nodiscard]] auto consumir_opcio() -> Opcio {
|
|
||||||
Opcio valor = opcio_;
|
|
||||||
opcio_ = Opcio::NONE;
|
|
||||||
return valor;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset opció a NONE (sense retornar valor)
|
|
||||||
void reset_opcio() {
|
|
||||||
opcio_ = Opcio::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configurar partida abans de transicionar a JOC
|
|
||||||
void set_config_partida(const GameConfig::ConfigPartida& config) {
|
|
||||||
config_partida_ = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtenir configuració de partida (consumit per EscenaJoc)
|
|
||||||
[[nodiscard]] const GameConfig::ConfigPartida& get_config_partida() const {
|
|
||||||
return config_partida_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Escena escena_desti_{Escena::LOGO}; // Escena a la qual transicionar
|
|
||||||
Opcio opcio_{Opcio::NONE}; // Opció específica per l'escena
|
|
||||||
GameConfig::ConfigPartida config_partida_; // Configuració de partida (jugadors actius, mode)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Variable global inline per gestionar l'escena actual (backward compatibility)
|
|
||||||
// Sincronitzada amb context.escena_desti() pel Director
|
|
||||||
inline ContextEscenes::Escena actual = ContextEscenes::Escena::LOGO;
|
|
||||||
|
|
||||||
} // namespace GestorEscenes
|
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// debug_overlay.cpp - Implementación del overlay de debug.
|
||||||
|
|
||||||
|
#include "core/system/debug_overlay.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/types.hpp"
|
||||||
|
#include "game/options.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
|
||||||
|
|
||||||
|
DebugOverlay::DebugOverlay(Rendering::Renderer* renderer)
|
||||||
|
: text_(renderer),
|
||||||
|
#ifdef _DEBUG
|
||||||
|
visible_(true),
|
||||||
|
#else
|
||||||
|
visible_(false),
|
||||||
|
#endif
|
||||||
|
fps_accumulator_(0.0F),
|
||||||
|
fps_frame_count_(0),
|
||||||
|
fps_display_(0) {}
|
||||||
|
|
||||||
|
void DebugOverlay::update(float delta_time) {
|
||||||
|
fps_accumulator_ += delta_time;
|
||||||
|
fps_frame_count_++;
|
||||||
|
|
||||||
|
if (fps_accumulator_ >= FPS_UPDATE_INTERVAL) {
|
||||||
|
fps_display_ = static_cast<int>(fps_frame_count_ / fps_accumulator_);
|
||||||
|
fps_frame_count_ = 0;
|
||||||
|
fps_accumulator_ = 0.0F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::draw() const {
|
||||||
|
if (!visible_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string FPS_TEXT = "FPS: " + std::to_string(fps_display_);
|
||||||
|
const std::string VSYNC_TEXT = std::string("VSYNC: ")
|
||||||
|
+ (Options::rendering.vsync == 1 ? "ON" : "OFF");
|
||||||
|
|
||||||
|
text_.render(FPS_TEXT,
|
||||||
|
Vec2{.x = OVERLAY_X, .y = OVERLAY_Y_FPS},
|
||||||
|
OVERLAY_SCALE, OVERLAY_SPACING, OVERLAY_BRIGHTNESS);
|
||||||
|
text_.render(VSYNC_TEXT,
|
||||||
|
Vec2{.x = OVERLAY_X, .y = OVERLAY_Y_FPS + OVERLAY_LINE_HEIGHT},
|
||||||
|
OVERLAY_SCALE, OVERLAY_SPACING, OVERLAY_BRIGHTNESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace System
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// debug_overlay.hpp - Overlay de debug (FPS + VSync) toggleable con F11
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Sistema global propiedad del Director. Se actualiza y dibuja cada frame
|
||||||
|
// después de la escena (queda on top). En builds debug arranca visible,
|
||||||
|
// en release oculto. F11 alterna visibilidad.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/graphics/vector_text.hpp"
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
|
||||||
|
namespace System {
|
||||||
|
|
||||||
|
class DebugOverlay {
|
||||||
|
public:
|
||||||
|
explicit DebugOverlay(Rendering::Renderer* renderer);
|
||||||
|
|
||||||
|
// Acumula FPS. Llamar una vez por frame con el delta del Director.
|
||||||
|
void update(float delta_time);
|
||||||
|
|
||||||
|
// Pinta el overlay si está visible. Posición esquina sup-izq.
|
||||||
|
void draw() const;
|
||||||
|
|
||||||
|
void toggle() { visible_ = !visible_; }
|
||||||
|
[[nodiscard]] auto isVisible() const -> bool { return visible_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Graphics::VectorText text_;
|
||||||
|
bool visible_;
|
||||||
|
|
||||||
|
// FPS counter — se actualiza cada FPS_UPDATE_INTERVAL segundos.
|
||||||
|
float fps_accumulator_;
|
||||||
|
int fps_frame_count_;
|
||||||
|
int fps_display_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace System
|
||||||
+120
-68
@@ -3,13 +3,18 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "context_escenes.hpp"
|
#include "debug_overlay.hpp"
|
||||||
|
#include "scene.hpp"
|
||||||
|
#include "scene_context.hpp"
|
||||||
|
#include "global_events.hpp"
|
||||||
#include "core/audio/audio.hpp"
|
#include "core/audio/audio.hpp"
|
||||||
#include "core/audio/audio_cache.hpp"
|
#include "core/audio/audio_adapter.hpp"
|
||||||
#include "core/defaults.hpp"
|
#include "core/defaults.hpp"
|
||||||
#include "core/input/input.hpp"
|
#include "core/input/input.hpp"
|
||||||
#include "core/input/mouse.hpp"
|
#include "core/input/mouse.hpp"
|
||||||
@@ -17,9 +22,9 @@
|
|||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "core/resources/resource_loader.hpp"
|
#include "core/resources/resource_loader.hpp"
|
||||||
#include "core/utils/path_utils.hpp"
|
#include "core/utils/path_utils.hpp"
|
||||||
#include "game/escenes/escena_joc.hpp"
|
#include "game/scenes/game_scene.hpp"
|
||||||
#include "game/escenes/escena_logo.hpp"
|
#include "game/scenes/logo_scene.hpp"
|
||||||
#include "game/escenes/escena_titol.hpp"
|
#include "game/scenes/title_scene.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
|
||||||
@@ -29,14 +34,14 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Using declarations per simplificar el codi
|
// Using declarations per simplificar el codi
|
||||||
using GestorEscenes::ContextEscenes;
|
using SceneManager::SceneContext;
|
||||||
using Escena = ContextEscenes::Escena;
|
using SceneType = SceneContext::SceneType;
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Director::Director(std::vector<std::string> const& args) {
|
Director::Director(std::vector<std::string> const& args) {
|
||||||
std::cout << "Orni Attack - Inici\n";
|
std::cout << "Orni Attack - Inici\n";
|
||||||
|
|
||||||
// Inicialitzar opcions amb valors per defecte
|
// Inicialitzar opciones con valors per defecte
|
||||||
Options::init();
|
Options::init();
|
||||||
|
|
||||||
// Comprovar arguments del programa
|
// Comprovar arguments del programa
|
||||||
@@ -50,28 +55,28 @@ Director::Director(std::vector<std::string> const& args) {
|
|||||||
|
|
||||||
// Inicialitzar sistema de recursos
|
// Inicialitzar sistema de recursos
|
||||||
#ifdef RELEASE_BUILD
|
#ifdef RELEASE_BUILD
|
||||||
// Mode release: paquet obligatori, sense fallback
|
// Mode release: paquet obligatori, sin fallback
|
||||||
std::string pack_path = resource_base + "/resources.pack";
|
std::string pack_path = resource_base + "/resources.pack";
|
||||||
if (!Resource::Helper::initializeResourceSystem(pack_path, false)) {
|
if (!Resource::Helper::initializeResourceSystem(pack_path, false)) {
|
||||||
std::cerr << "ERROR FATAL: No es pot carregar " << pack_path << "\n";
|
std::cerr << "ERROR FATAL: No es pot load " << pack_path << "\n";
|
||||||
std::cerr << "El joc no pot continuar sense els recursos.\n";
|
std::cerr << "El juego no pot continuar sin los recursos.\n";
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validar integritat del paquet
|
// Validar integritat del paquet
|
||||||
if (!Resource::Loader::get().validatePack()) {
|
if (!Resource::Loader::get().validatePack()) {
|
||||||
std::cerr << "ERROR FATAL: El paquet de recursos està corromput\n";
|
std::cerr << "ERROR FATAL: El paquet de recursos está corromput\n";
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Sistema de recursos inicialitzat (mode release)\n";
|
std::cout << "Sistema de recursos inicialitzat (mode release)\n";
|
||||||
#else
|
#else
|
||||||
// Mode desenvolupament: intentar paquet amb fallback a data/
|
// Mode desenvolupament: intentar paquet con fallback a data/
|
||||||
std::string pack_path = resource_base + "/resources.pack";
|
std::string pack_path = resource_base + "/resources.pack";
|
||||||
Resource::Helper::initializeResourceSystem(pack_path, true);
|
Resource::Helper::initializeResourceSystem(pack_path, true);
|
||||||
|
|
||||||
if (Resource::Helper::isPackLoaded()) {
|
if (Resource::Helper::isPackLoaded()) {
|
||||||
std::cout << "Sistema de recursos inicialitzat (mode dev amb paquet)\n";
|
std::cout << "Sistema de recursos inicialitzat (mode dev con paquet)\n";
|
||||||
} else {
|
} else {
|
||||||
std::cout << "Sistema de recursos inicialitzat (mode dev, fallback a data/)\n";
|
std::cout << "Sistema de recursos inicialitzat (mode dev, fallback a data/)\n";
|
||||||
}
|
}
|
||||||
@@ -84,21 +89,21 @@ Director::Director(std::vector<std::string> const& args) {
|
|||||||
createSystemFolder("jailgames");
|
createSystemFolder("jailgames");
|
||||||
createSystemFolder(std::string("jailgames/") + Project::NAME);
|
createSystemFolder(std::string("jailgames/") + Project::NAME);
|
||||||
|
|
||||||
// Establir ruta del fitxer de configuració
|
// Establir ruta del file de configuración
|
||||||
Options::setConfigFile(system_folder_ + "/config.yaml");
|
Options::setConfigFile(system_folder_ + "/config.yaml");
|
||||||
|
|
||||||
// Carregar o crear configuració
|
// Carregar o crear configuración
|
||||||
Options::loadFromFile();
|
Options::loadFromFile();
|
||||||
|
|
||||||
// Inicialitzar sistema d'input
|
// Inicialitzar sistema de input
|
||||||
Input::init("data/gamecontrollerdb.txt");
|
Input::init("data/gamecontrollerdb.txt");
|
||||||
|
|
||||||
// Aplicar configuració de controls dels jugadors
|
// Aplicar configuración de controls dels jugadors
|
||||||
Input::get()->applyPlayer1BindingsFromOptions();
|
Input::get()->applyPlayer1BindingsFromOptions();
|
||||||
Input::get()->applyPlayer2BindingsFromOptions();
|
Input::get()->applyPlayer2BindingsFromOptions();
|
||||||
|
|
||||||
if (Options::console) {
|
if (Options::console) {
|
||||||
std::cout << "Configuració carregada\n";
|
std::cout << "Configuración carregada\n";
|
||||||
std::cout << " Finestra: " << Options::window.width << "×"
|
std::cout << " Finestra: " << Options::window.width << "×"
|
||||||
<< Options::window.height << '\n';
|
<< Options::window.height << '\n';
|
||||||
std::cout << " Física: rotation=" << Options::physics.rotation_speed
|
std::cout << " Física: rotation=" << Options::physics.rotation_speed
|
||||||
@@ -111,7 +116,7 @@ Director::Director(std::vector<std::string> const& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Director::~Director() {
|
Director::~Director() {
|
||||||
// Guardar opcions
|
// Guardar opciones
|
||||||
Options::saveToFile();
|
Options::saveToFile();
|
||||||
|
|
||||||
// Cleanup input
|
// Cleanup input
|
||||||
@@ -138,7 +143,7 @@ auto Director::checkProgramArguments(std::vector<std::string> const& args)
|
|||||||
} else if (argument == "--reset-config") {
|
} else if (argument == "--reset-config") {
|
||||||
Options::init();
|
Options::init();
|
||||||
Options::saveToFile();
|
Options::saveToFile();
|
||||||
std::cout << "Configuració restablida als valors per defecte\n";
|
std::cout << "Configuración restablida als valors per defecte\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +193,7 @@ void Director::createSystemFolder(const std::string& folder) {
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
case EEXIST:
|
case EEXIST:
|
||||||
// La carpeta ja existeix (race condition), continuar
|
// La carpeta ya existeix (race condition), continuar
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ENAMETOOLONG:
|
case ENAMETOOLONG:
|
||||||
@@ -207,7 +212,7 @@ void Director::createSystemFolder(const std::string& folder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucle principal del joc
|
// Bucle principal del juego
|
||||||
auto Director::run() -> int {
|
auto Director::run() -> int {
|
||||||
// Calculate initial size from saved zoom_factor
|
// Calculate initial size from saved zoom_factor
|
||||||
int initial_width = static_cast<int>(std::round(
|
int initial_width = static_cast<int>(std::round(
|
||||||
@@ -215,68 +220,115 @@ auto Director::run() -> int {
|
|||||||
int initial_height = static_cast<int>(std::round(
|
int initial_height = static_cast<int>(std::round(
|
||||||
Defaults::Window::HEIGHT * Options::window.zoom_factor));
|
Defaults::Window::HEIGHT * Options::window.zoom_factor));
|
||||||
|
|
||||||
// Crear gestor SDL amb configuració de Options
|
// Crear gestor SDL con configuración de Options
|
||||||
SDLManager sdl(initial_width, initial_height, Options::window.fullscreen);
|
SDLManager sdl(initial_width, initial_height, Options::window.fullscreen);
|
||||||
|
|
||||||
// CRÍTIC: Forçar ocultació del cursor DESPRÉS de tota la inicialització SDL
|
// CRÍTIC: Forçar ocultació del cursor DESPRÉS de toda la inicialización SDL
|
||||||
// Això evita que SDL mostre el cursor automàticament durant la creació de la finestra
|
// Això evita que SDL mostre el cursor automàticament durante la creació de la finestra
|
||||||
if (!Options::window.fullscreen) {
|
if (!Options::window.fullscreen) {
|
||||||
Mouse::forceHide();
|
Mouse::forceHide();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicialitzar sistema d'audio
|
// Inicializar sistema de audio (config inyectada desde Defaults)
|
||||||
Audio::init();
|
const Audio::Config AUDIO_CONFIG{
|
||||||
Audio::get()->setMusicVolume(1.0);
|
.enabled = Defaults::Audio::ENABLED,
|
||||||
Audio::get()->setSoundVolume(0.4);
|
.volume = Defaults::Audio::VOLUME,
|
||||||
|
.music_enabled = Defaults::Audio::MUSIC_ENABLED,
|
||||||
|
.music_volume = Defaults::Audio::MUSIC_VOLUME,
|
||||||
|
.sound_enabled = Defaults::Audio::SOUND_ENABLED,
|
||||||
|
.sound_volume = Defaults::Audio::SOUND_VOLUME,
|
||||||
|
};
|
||||||
|
Audio::init(AUDIO_CONFIG);
|
||||||
|
Audio::get()->applySettings(AUDIO_CONFIG); // Aplicar volúmenes iniciales al motor
|
||||||
|
|
||||||
// Precachejar música per evitar lag al començar
|
// Precachear música para evitar lag al empezar
|
||||||
AudioCache::getMusic("title.ogg");
|
AudioResource::getMusic("title.ogg");
|
||||||
AudioCache::getMusic("game.ogg");
|
AudioResource::getMusic("game.ogg");
|
||||||
if (Options::console) {
|
if (Options::console) {
|
||||||
std::cout << "Música precachejada: "
|
std::cout << "Música precacheada\n";
|
||||||
<< AudioCache::getMusicCacheSize() << " fitxers\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear context d'escenes
|
// Crear context de escenes
|
||||||
ContextEscenes context;
|
SceneContext context;
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
context.canviar_escena(Escena::TITOL);
|
context.setNextScene(SceneType::TITLE);
|
||||||
#else
|
#else
|
||||||
context.canviar_escena(Escena::LOGO);
|
context.setNextScene(SceneType::LOGO);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Bucle principal de gestió d'escenes
|
// Overlay de debug (FPS + VSync). Vive en el Director porque es global
|
||||||
while (context.escena_desti() != Escena::EIXIR) {
|
// a todas las escenas. Toggle con F11 (visible por defecto en _DEBUG).
|
||||||
// Sincronitzar GestorEscenes::actual amb context
|
System::DebugOverlay debug_overlay(sdl.getRenderer());
|
||||||
// (altres sistemes encara poden llegir GestorEscenes::actual)
|
|
||||||
GestorEscenes::actual = context.escena_desti();
|
|
||||||
|
|
||||||
switch (context.escena_desti()) {
|
// Bucle principal: construir escena → frame loop → destruir → siguiente.
|
||||||
case Escena::LOGO: {
|
while (context.nextScene() != SceneType::EXIT) {
|
||||||
EscenaLogo logo(sdl, context);
|
SceneManager::actual = context.nextScene();
|
||||||
logo.executar();
|
std::unique_ptr<Scene> scene = buildScene(context.nextScene(), sdl, context);
|
||||||
break;
|
if (!scene) {
|
||||||
}
|
break;
|
||||||
|
|
||||||
case Escena::TITOL: {
|
|
||||||
EscenaTitol titol(sdl, context);
|
|
||||||
titol.executar();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Escena::JOC: {
|
|
||||||
EscenaJoc joc(sdl, context);
|
|
||||||
joc.executar();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
runFrameLoop(*scene, sdl, context, debug_overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sincronitzar final amb GestorEscenes::actual
|
SceneManager::actual = SceneType::EXIT;
|
||||||
GestorEscenes::actual = Escena::EIXIR;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Director::buildScene(SceneType type, SDLManager& sdl, SceneContext& context)
|
||||||
|
-> std::unique_ptr<Scene> {
|
||||||
|
switch (type) {
|
||||||
|
case SceneType::LOGO:
|
||||||
|
return std::make_unique<LogoScene>(sdl, context);
|
||||||
|
case SceneType::TITLE:
|
||||||
|
return std::make_unique<TitleScene>(sdl, context);
|
||||||
|
case SceneType::GAME:
|
||||||
|
return std::make_unique<GameScene>(sdl, context);
|
||||||
|
case SceneType::EXIT:
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Director::runFrameLoop(Scene& scene, SDLManager& sdl, SceneContext& context,
|
||||||
|
System::DebugOverlay& debug_overlay) {
|
||||||
|
SDL_Event event;
|
||||||
|
Uint64 last_time = SDL_GetTicks();
|
||||||
|
|
||||||
|
while (!scene.isFinished()) {
|
||||||
|
// Delta time real, capeado a 50ms para evitar grandes saltos.
|
||||||
|
const Uint64 NOW = SDL_GetTicks();
|
||||||
|
float delta_time = static_cast<float>(NOW - last_time) / 1000.0F;
|
||||||
|
last_time = NOW;
|
||||||
|
delta_time = std::min(delta_time, 0.05F);
|
||||||
|
|
||||||
|
Mouse::updateCursorVisibility();
|
||||||
|
Input::get()->update();
|
||||||
|
|
||||||
|
// Event loop: primero ventana, después globales, después F11
|
||||||
|
// (toggle del overlay), después escena.
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (sdl.handleWindowEvent(event)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (GlobalEvents::handle(event, sdl, context)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event.type == SDL_EVENT_KEY_DOWN
|
||||||
|
&& event.key.scancode == SDL_SCANCODE_F11) {
|
||||||
|
debug_overlay.toggle();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
scene.handleEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.update(delta_time);
|
||||||
|
debug_overlay.update(delta_time);
|
||||||
|
Audio::update();
|
||||||
|
|
||||||
|
sdl.clear(0, 0, 0);
|
||||||
|
sdl.updateRenderingContext();
|
||||||
|
scene.draw();
|
||||||
|
debug_overlay.draw(); // siempre on top de la escena
|
||||||
|
sdl.present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "scene_context.hpp"
|
||||||
|
|
||||||
|
class Scene;
|
||||||
|
class SDLManager;
|
||||||
|
namespace System { class DebugOverlay; }
|
||||||
|
|
||||||
class Director {
|
class Director {
|
||||||
public:
|
public:
|
||||||
explicit Director(std::vector<std::string> const& args);
|
explicit Director(std::vector<std::string> const& args);
|
||||||
@@ -17,4 +24,18 @@ class Director {
|
|||||||
static auto checkProgramArguments(std::vector<std::string> const& args)
|
static auto checkProgramArguments(std::vector<std::string> const& args)
|
||||||
-> std::string;
|
-> std::string;
|
||||||
void createSystemFolder(const std::string& folder);
|
void createSystemFolder(const std::string& folder);
|
||||||
|
|
||||||
|
// Construye la escena correspondiente al tipo solicitado. Retorna
|
||||||
|
// nullptr para EXIT u otros valores no constructibles.
|
||||||
|
static auto buildScene(SceneManager::SceneContext::SceneType type,
|
||||||
|
SDLManager& sdl,
|
||||||
|
SceneManager::SceneContext& context)
|
||||||
|
-> std::unique_ptr<Scene>;
|
||||||
|
|
||||||
|
// Ejecuta el bucle de frames de UNA escena hasta que scene.isFinished()
|
||||||
|
// sea true. Maneja delta_time, eventos (globales + escena), update y draw.
|
||||||
|
// El debug_overlay es global a todas las escenas; el Director lo posee.
|
||||||
|
static void runFrameLoop(Scene& scene, SDLManager& sdl,
|
||||||
|
SceneManager::SceneContext& context,
|
||||||
|
System::DebugOverlay& debug_overlay);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,43 +4,43 @@
|
|||||||
|
|
||||||
namespace GameConfig {
|
namespace GameConfig {
|
||||||
|
|
||||||
// Mode de joc
|
// Mode de juego
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
NORMAL, // Partida normal
|
NORMAL, // Partida normal
|
||||||
DEMO // Mode demostració (futur)
|
DEMO // Mode demostració (futur)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Configuració d'una partida
|
// Configuración de una match
|
||||||
struct ConfigPartida {
|
struct MatchConfig {
|
||||||
bool jugador1_actiu{false}; // És actiu el jugador 1?
|
bool jugador1_actiu{false}; // Es active el player 1?
|
||||||
bool jugador2_actiu{false}; // És actiu el jugador 2?
|
bool jugador2_actiu{false}; // Es active el player 2?
|
||||||
Mode mode{Mode::NORMAL}; // Mode de joc
|
Mode mode{Mode::NORMAL}; // Mode de juego
|
||||||
|
|
||||||
// Mètodes auxiliars
|
// Métodos auxiliars
|
||||||
|
|
||||||
// Retorna true si només hi ha un jugador actiu
|
// Retorna true si solo hay un player active
|
||||||
[[nodiscard]] bool es_un_jugador() const {
|
[[nodiscard]] bool es_un_jugador() const {
|
||||||
return (jugador1_actiu && !jugador2_actiu) ||
|
return (jugador1_actiu && !jugador2_actiu) ||
|
||||||
(!jugador1_actiu && jugador2_actiu);
|
(!jugador1_actiu && jugador2_actiu);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retorna true si hi ha dos jugadors actius
|
// Retorna true si hay dos jugadors active
|
||||||
[[nodiscard]] bool son_dos_jugadors() const {
|
[[nodiscard]] bool son_dos_jugadors() const {
|
||||||
return jugador1_actiu && jugador2_actiu;
|
return jugador1_actiu && jugador2_actiu;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retorna true si no hi ha cap jugador actiu
|
// Retorna true si no hay sin player active
|
||||||
[[nodiscard]] bool cap_jugador() const {
|
[[nodiscard]] bool cap_jugador() const {
|
||||||
return !jugador1_actiu && !jugador2_actiu;
|
return !jugador1_actiu && !jugador2_actiu;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compte de jugadors actius (0, 1 o 2)
|
// Compte de jugadors active (0, 1 o 2)
|
||||||
[[nodiscard]] uint8_t compte_jugadors() const {
|
[[nodiscard]] uint8_t compte_jugadors() const {
|
||||||
return (jugador1_actiu ? 1 : 0) + (jugador2_actiu ? 1 : 0);
|
return (jugador1_actiu ? 1 : 0) + (jugador2_actiu ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retorna l'ID de l'únic jugador actiu (0 o 1)
|
// Retorna l'ID de l'únic player active (0 o 1)
|
||||||
// Només vàlid si es_un_jugador() retorna true
|
// Solo vàlid si es_un_jugador() retorna true
|
||||||
[[nodiscard]] uint8_t id_unic_jugador() const {
|
[[nodiscard]] uint8_t id_unic_jugador() const {
|
||||||
if (jugador1_actiu && !jugador2_actiu) {
|
if (jugador1_actiu && !jugador2_actiu) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -48,7 +48,7 @@ struct ConfigPartida {
|
|||||||
if (!jugador1_actiu && jugador2_actiu) {
|
if (!jugador1_actiu && jugador2_actiu) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0; // Fallback (cal comprovar es_un_jugador() primer)
|
return 0; // Fallback (necesario comprovar es_un_jugador() primer)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,18 +5,18 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "context_escenes.hpp"
|
#include "scene_context.hpp"
|
||||||
#include "core/input/input.hpp"
|
#include "core/input/input.hpp"
|
||||||
#include "core/input/mouse.hpp"
|
#include "core/input/mouse.hpp"
|
||||||
#include "core/rendering/sdl_manager.hpp"
|
#include "core/rendering/sdl_manager.hpp"
|
||||||
|
|
||||||
// Using declarations per simplificar el codi
|
// Using declarations per simplificar el codi
|
||||||
using GestorEscenes::ContextEscenes;
|
using SceneManager::SceneContext;
|
||||||
using Escena = ContextEscenes::Escena;
|
using SceneType = SceneContext::SceneType;
|
||||||
|
|
||||||
namespace GlobalEvents {
|
namespace GlobalEvents {
|
||||||
|
|
||||||
bool handle(const SDL_Event& event, SDLManager& sdl, ContextEscenes& context) {
|
bool handle(const SDL_Event& event, SDLManager& sdl, SceneContext& context) {
|
||||||
// 1. Permitir que Input procese el evento (para hotplug de gamepads)
|
// 1. Permitir que Input procese el evento (para hotplug de gamepads)
|
||||||
auto event_msg = Input::get()->handleEvent(event);
|
auto event_msg = Input::get()->handleEvent(event);
|
||||||
if (!event_msg.empty()) {
|
if (!event_msg.empty()) {
|
||||||
@@ -25,8 +25,8 @@ bool handle(const SDL_Event& event, SDLManager& sdl, ContextEscenes& context) {
|
|||||||
|
|
||||||
// 2. Procesar SDL_EVENT_QUIT directamente (no es input de juego)
|
// 2. Procesar SDL_EVENT_QUIT directamente (no es input de juego)
|
||||||
if (event.type == SDL_EVENT_QUIT) {
|
if (event.type == SDL_EVENT_QUIT) {
|
||||||
context.canviar_escena(Escena::EIXIR);
|
context.setNextScene(SceneType::EXIT);
|
||||||
GestorEscenes::actual = Escena::EIXIR;
|
SceneManager::actual = SceneType::EXIT;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@ bool handle(const SDL_Event& event, SDLManager& sdl, ContextEscenes& context) {
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case SDL_SCANCODE_ESCAPE:
|
case SDL_SCANCODE_ESCAPE:
|
||||||
context.canviar_escena(Escena::EIXIR);
|
context.setNextScene(SceneType::EXIT);
|
||||||
GestorEscenes::actual = Escena::EIXIR;
|
SceneManager::actual = SceneType::EXIT;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// global_events.hpp - Events globals del joc
|
// global_events.hpp - Events globals del juego
|
||||||
// Basat en el patró del projecte "pollo"
|
// Basat en el patró del projecte "pollo"
|
||||||
// © 2025 Port a C++20
|
// © 2025 Port a C++20
|
||||||
|
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
class SDLManager;
|
class SDLManager;
|
||||||
namespace GestorEscenes {
|
namespace SceneManager {
|
||||||
class ContextEscenes;
|
class SceneContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace GlobalEvents {
|
namespace GlobalEvents {
|
||||||
// Processa events globals (F1/F2/F3/ESC/QUIT)
|
// Processa events globals (F1/F2/F3/ESC/QUIT)
|
||||||
// Retorna true si l'event ha estat processat i no cal seguir processant-lo
|
// Retorna true si l'event ha state processat y no necesario seguir processant-lo
|
||||||
bool handle(const SDL_Event& event, SDLManager& sdl, GestorEscenes::ContextEscenes& context);
|
bool handle(const SDL_Event& event, SDLManager& sdl, SceneManager::SceneContext& context);
|
||||||
} // namespace GlobalEvents
|
} // namespace GlobalEvents
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// scene.hpp - Interfaz base para escenas del juego
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// El frame loop vive en Director, no en cada escena. Cada escena implementa
|
||||||
|
// estos cuatro métodos y el Director los llama en orden por frame:
|
||||||
|
// handleEvent(ev) por cada evento SDL (tras los eventos globales)
|
||||||
|
// update(dt) lógica
|
||||||
|
// draw() pintado (entre clear y present del Director)
|
||||||
|
// isFinished() consultar transición pendiente
|
||||||
|
//
|
||||||
|
// Cuando una escena pide transición (vía context_.setNextScene(...)),
|
||||||
|
// isFinished() debe pasar a true y el Director destruye la escena para
|
||||||
|
// construir la siguiente.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
class Scene {
|
||||||
|
public:
|
||||||
|
Scene() = default;
|
||||||
|
virtual ~Scene() = default;
|
||||||
|
|
||||||
|
Scene(const Scene&) = delete;
|
||||||
|
auto operator=(const Scene&) -> Scene& = delete;
|
||||||
|
Scene(Scene&&) = delete;
|
||||||
|
auto operator=(Scene&&) -> Scene& = delete;
|
||||||
|
|
||||||
|
// Eventos específicos de la escena. Los globales (window resize,
|
||||||
|
// F1-F4, ESC, QUIT) los procesa GlobalEvents::handle antes.
|
||||||
|
virtual void handleEvent(const SDL_Event& event) = 0;
|
||||||
|
|
||||||
|
// Lógica de la escena. delta_time en segundos, ya capeado por el Director.
|
||||||
|
virtual void update(float delta_time) = 0;
|
||||||
|
|
||||||
|
// Pintar la escena (entre clear y present del Director).
|
||||||
|
virtual void draw() = 0;
|
||||||
|
|
||||||
|
// True cuando la escena ha pedido transición.
|
||||||
|
[[nodiscard]] virtual auto isFinished() const -> bool = 0;
|
||||||
|
};
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// scene_context.hpp - Sistema de gestió de escenes i context de transiciones
|
||||||
|
// © 2025 Port a C++20
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/system/game_config.hpp"
|
||||||
|
|
||||||
|
namespace SceneManager {
|
||||||
|
|
||||||
|
// Context de transición entre escenes
|
||||||
|
// Conté l'escena destinació i opciones específiques per aquella escena
|
||||||
|
class SceneContext {
|
||||||
|
public:
|
||||||
|
// Tipo de escena del juego
|
||||||
|
enum class SceneType {
|
||||||
|
LOGO, // Pantalla de start (logo JAILGAMES)
|
||||||
|
TITLE, // Pantalla de título con menú
|
||||||
|
GAME, // Juego principal (Asteroids)
|
||||||
|
EXIT // Salir del programa
|
||||||
|
};
|
||||||
|
|
||||||
|
// Opciones específiques para cada escena
|
||||||
|
enum class Option {
|
||||||
|
NONE, // Sin opciones especials (comportament per defecte)
|
||||||
|
JUMP_TO_TITLE_MAIN, // TITLE: Saltar directament a MAIN (starfield instantani)
|
||||||
|
// MODE_DEMO, // GAME: Mode demostració con IA (futur)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constructor inicial con escena LOGO i sin opciones
|
||||||
|
SceneContext() = default;
|
||||||
|
|
||||||
|
// Canviar escena con opción específica
|
||||||
|
void setNextScene(SceneType next_scene, Option option = Option::NONE) {
|
||||||
|
next_scene_ = next_scene;
|
||||||
|
option_ = option;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consultar escena destinació
|
||||||
|
[[nodiscard]] auto nextScene() const -> SceneType {
|
||||||
|
return next_scene_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consultar opción actual
|
||||||
|
[[nodiscard]] auto option() const -> Option {
|
||||||
|
return option_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumir opción (retorna value i reseteja a NONE)
|
||||||
|
// Utilitzar cuando l'escena processa l'opción
|
||||||
|
[[nodiscard]] auto consumeOption() -> Option {
|
||||||
|
Option value = option_;
|
||||||
|
option_ = Option::NONE;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset opción a NONE (sin retornar value)
|
||||||
|
void resetOption() {
|
||||||
|
option_ = Option::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurar match antes de transicionar a GAME
|
||||||
|
void setMatchConfig(const GameConfig::MatchConfig& config) {
|
||||||
|
match_config_ = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtenir configuración de match (consumit per GameScene)
|
||||||
|
[[nodiscard]] const GameConfig::MatchConfig& getMatchConfig() const {
|
||||||
|
return match_config_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SceneType next_scene_{SceneType::LOGO}; // SceneType a la qual transicionar
|
||||||
|
Option option_{Option::NONE}; // Opción específica per l'escena
|
||||||
|
GameConfig::MatchConfig match_config_; // Configuración de match (jugadors active, mode)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Variable global inline per gestionar l'escena actual (backward compatibility)
|
||||||
|
// Sincronitzada con context.nextScene() por el Director
|
||||||
|
inline SceneContext::SceneType actual = SceneContext::SceneType::LOGO;
|
||||||
|
|
||||||
|
} // namespace SceneManager
|
||||||
+66
-37
@@ -1,43 +1,72 @@
|
|||||||
|
// types.hpp - Tipos básicos compartidos
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <cmath>
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "core/defaults.hpp"
|
// Vector 2D cartesiano - única estructura de coordenadas del juego.
|
||||||
|
// Aggregate (sin constructores definidos) para soportar designated initializers:
|
||||||
|
// Vec2{.x = 1.0F, .y = 2.0F}
|
||||||
|
// y aggregate initialization clásica:
|
||||||
|
// Vec2{1.0F, 2.0F}
|
||||||
|
struct Vec2 {
|
||||||
|
float x{0.0F};
|
||||||
|
float y{0.0F};
|
||||||
|
|
||||||
// Punt polar (coordenades polars)
|
constexpr auto operator+=(const Vec2& o) -> Vec2& {
|
||||||
struct IPunt {
|
x += o.x;
|
||||||
float r; // Radi
|
y += o.y;
|
||||||
float angle; // Angle en radians
|
return *this;
|
||||||
|
}
|
||||||
|
constexpr auto operator-=(const Vec2& o) -> Vec2& {
|
||||||
|
x -= o.x;
|
||||||
|
y -= o.y;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
constexpr auto operator*=(float s) -> Vec2& {
|
||||||
|
x *= s;
|
||||||
|
y *= s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
constexpr auto operator/=(float s) -> Vec2& {
|
||||||
|
x /= s;
|
||||||
|
y /= s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto lengthSquared() const -> float { return (x * x) + (y * y); }
|
||||||
|
[[nodiscard]] auto length() const -> float { return std::sqrt(lengthSquared()); }
|
||||||
|
[[nodiscard]] auto dot(const Vec2& o) const -> float { return (x * o.x) + (y * o.y); }
|
||||||
|
|
||||||
|
// Devuelve el vector normalizado; si la magnitud es 0 devuelve {0,0}.
|
||||||
|
[[nodiscard]] auto normalized() const -> Vec2 {
|
||||||
|
const float L = length();
|
||||||
|
return L > 0.0F ? Vec2{.x = x / L, .y = y / L} : Vec2{};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Punt cartesià
|
constexpr auto operator+(Vec2 a, const Vec2& b) -> Vec2 {
|
||||||
struct Punt {
|
a += b;
|
||||||
float x, y;
|
return a;
|
||||||
};
|
}
|
||||||
|
constexpr auto operator-(Vec2 a, const Vec2& b) -> Vec2 {
|
||||||
// ==============================================================================
|
a -= b;
|
||||||
// DEPRECATED: Legacy types (replaced by Shape system)
|
return a;
|
||||||
// ==============================================================================
|
}
|
||||||
// These types are kept temporarily for chatarra_cosmica_ (Phase 10: explosions)
|
constexpr auto operator*(Vec2 v, float s) -> Vec2 {
|
||||||
// TODO Phase 10: Replace with particle system or remove completely
|
v *= s;
|
||||||
|
return v;
|
||||||
// Nau (triangle) - DEPRECATED: Now using Shape system (ship.shp)
|
}
|
||||||
struct Triangle {
|
constexpr auto operator*(float s, Vec2 v) -> Vec2 {
|
||||||
IPunt p1, p2, p3;
|
v *= s;
|
||||||
Punt centre;
|
return v;
|
||||||
float angle;
|
}
|
||||||
float velocitat;
|
constexpr auto operator/(Vec2 v, float s) -> Vec2 {
|
||||||
};
|
v /= s;
|
||||||
|
return v;
|
||||||
// Polígon (enemics i bales) - DEPRECATED: Now using Shape system (.shp files)
|
}
|
||||||
struct Poligon {
|
constexpr auto operator-(const Vec2& v) -> Vec2 { return {.x = -v.x, .y = -v.y}; }
|
||||||
std::array<IPunt, Defaults::Entities::MAX_IPUNTS> ipuntx;
|
constexpr auto operator==(const Vec2& a, const Vec2& b) -> bool {
|
||||||
Punt centre;
|
return a.x == b.x && a.y == b.y;
|
||||||
float angle;
|
}
|
||||||
float velocitat;
|
|
||||||
uint8_t n;
|
|
||||||
float drotacio;
|
|
||||||
float rotacio;
|
|
||||||
bool esta;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// path_utils.cpp - Implementació de utilitats de rutes
|
// path_utils.cpp - Implementació de utilitats de rutes
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "path_utils.hpp"
|
#include "path_utils.hpp"
|
||||||
|
|
||||||
@@ -13,10 +13,10 @@ namespace Utils {
|
|||||||
static std::string executable_path_;
|
static std::string executable_path_;
|
||||||
static std::string executable_directory_;
|
static std::string executable_directory_;
|
||||||
|
|
||||||
// Inicialitzar el sistema de rutes amb argv[0]
|
// Inicialitzar el sistema de rutes con argv[0]
|
||||||
void initializePathSystem(const char* argv0) {
|
void initializePathSystem(const char* argv0) {
|
||||||
if (argv0 == nullptr) {
|
if (argv0 == nullptr) {
|
||||||
std::cerr << "[PathUtils] ADVERTÈNCIA: argv[0] és nullptr\n";
|
std::cerr << "[PathUtils] ADVERTÈNCIA: argv[0] es nullptr\n";
|
||||||
executable_path_ = "";
|
executable_path_ = "";
|
||||||
executable_directory_ = ".";
|
executable_directory_ = ".";
|
||||||
return;
|
return;
|
||||||
@@ -50,7 +50,7 @@ bool isMacOSBundle() {
|
|||||||
#ifdef MACOS_BUNDLE
|
#ifdef MACOS_BUNDLE
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
// Detecció en temps d'execució
|
// Detecció en time de execució
|
||||||
// Cercar ".app/Contents/MacOS" a la ruta de l'executable
|
// Cercar ".app/Contents/MacOS" a la ruta de l'executable
|
||||||
std::string exe_dir = getExecutableDirectory();
|
std::string exe_dir = getExecutableDirectory();
|
||||||
return exe_dir.find(".app/Contents/MacOS") != std::string::npos;
|
return exe_dir.find(".app/Contents/MacOS") != std::string::npos;
|
||||||
@@ -62,10 +62,10 @@ std::string getResourceBasePath() {
|
|||||||
std::string exe_dir = getExecutableDirectory();
|
std::string exe_dir = getExecutableDirectory();
|
||||||
|
|
||||||
if (isMacOSBundle()) {
|
if (isMacOSBundle()) {
|
||||||
// Bundle de macOS: recursos a ../Resources des de MacOS/
|
// Bundle de macOS: recursos a ../Resources desde MacOS/
|
||||||
std::cout << "[PathUtils] Detectat bundle de macOS\n";
|
std::cout << "[PathUtils] Detectat bundle de macOS\n";
|
||||||
return exe_dir + "/../Resources";
|
return exe_dir + "/../Resources";
|
||||||
} // Executable normal: recursos al mateix directori
|
} // Executable normal: recursos al mismo directori
|
||||||
return exe_dir;
|
return exe_dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ std::string normalizePath(const std::string& path) {
|
|||||||
// Convertir barres invertides a normals
|
// Convertir barres invertides a normals
|
||||||
std::ranges::replace(normalized, '\\', '/');
|
std::ranges::replace(normalized, '\\', '/');
|
||||||
|
|
||||||
// Simplificar rutes amb filesystem
|
// Simplificar rutes con filesystem
|
||||||
try {
|
try {
|
||||||
std::filesystem::path fs_path(normalized);
|
std::filesystem::path fs_path(normalized);
|
||||||
normalized = fs_path.lexically_normal().string();
|
normalized = fs_path.lexically_normal().string();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// path_utils.hpp - Utilitats de gestió de rutes
|
// path_utils.hpp - Utilitats de gestió de rutes
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
// Detecció de directoris i bundles multiplataforma
|
// Detecció de directoris i bundles multiplataforma
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|
||||||
// Inicialització amb argv[0]
|
// Inicialización con argv[0]
|
||||||
void initializePathSystem(const char* argv0);
|
void initializePathSystem(const char* argv0);
|
||||||
|
|
||||||
// Obtenció de rutes
|
// Obtenció de rutes
|
||||||
|
|||||||
Vendored
+116
-116
@@ -2,9 +2,9 @@
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_NODE_HPP
|
#ifndef FK_YAML_NODE_HPP
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP
|
#ifndef FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// Check version definitions if already defined.
|
// Check version definitions if already defined.
|
||||||
@@ -94,9 +94,9 @@
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP
|
#ifndef FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
// With the MSVC compilers, the value of __cplusplus is by default always
|
// With the MSVC compilers, the value of __cplusplus is by default always
|
||||||
// "199611L"(C++98). To avoid that, the library instead references _MSVC_LANG
|
// "199611L"(C++98). To avoid that, the library instead references _MSVC_LANG
|
||||||
// which is always set a correct value. See
|
// which is always set a correct value. See
|
||||||
// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
|
// https://devblogs.microsoft.como/cppblog/msvc-now-correctly-reports-__cplusplus/
|
||||||
// for more details.
|
// for more details.
|
||||||
#if defined(_MSVC_LANG) && !defined(__clang__)
|
#if defined(_MSVC_LANG) && !defined(__clang__)
|
||||||
#define FK_YAML_CPLUSPLUS _MSVC_LANG
|
#define FK_YAML_CPLUSPLUS _MSVC_LANG
|
||||||
@@ -265,9 +265,9 @@
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_ASSERT_HPP
|
#ifndef FK_YAML_DETAIL_ASSERT_HPP
|
||||||
@@ -290,9 +290,9 @@
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_DOCUMENT_METAINFO_HPP
|
#ifndef FK_YAML_DETAIL_DOCUMENT_METAINFO_HPP
|
||||||
@@ -308,9 +308,9 @@
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_META_NODE_TRAITS_HPP
|
#ifndef FK_YAML_DETAIL_META_NODE_TRAITS_HPP
|
||||||
@@ -323,9 +323,9 @@
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_META_DETECT_HPP
|
#ifndef FK_YAML_DETAIL_META_DETECT_HPP
|
||||||
@@ -341,9 +341,9 @@
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP
|
#ifndef FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP
|
||||||
@@ -373,13 +373,13 @@ FK_YAML_DETAIL_NAMESPACE_BEGIN
|
|||||||
|
|
||||||
/// @brief An alias template for std::add_pointer::type with C++11.
|
/// @brief An alias template for std::add_pointer::type with C++11.
|
||||||
/// @note std::add_pointer_t is available since C++14.
|
/// @note std::add_pointer_t is available since C++14.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/add_pointer
|
/// @sa https://en.cppreference.como/w/cpp/types/add_pointer
|
||||||
/// @tparam T A type to be added a pointer.
|
/// @tparam T A type to be added a pointer.
|
||||||
template <typename T> using add_pointer_t = typename std::add_pointer<T>::type;
|
template <typename T> using add_pointer_t = typename std::add_pointer<T>::type;
|
||||||
|
|
||||||
/// @brief An alias template for std::enable_if::type with C++11.
|
/// @brief An alias template for std::enable_if::type with C++11.
|
||||||
/// @note std::enable_if_t is available since C++14.
|
/// @note std::enable_if_t is available since C++14.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/enable_if
|
/// @sa https://en.cppreference.como/w/cpp/types/enable_if
|
||||||
/// @tparam Condition A condition tested at compile time.
|
/// @tparam Condition A condition tested at compile time.
|
||||||
/// @tparam T The type defined only if Condition is true.
|
/// @tparam T The type defined only if Condition is true.
|
||||||
template <bool Condition, typename T = void>
|
template <bool Condition, typename T = void>
|
||||||
@@ -387,7 +387,7 @@ using enable_if_t = typename std::enable_if<Condition, T>::type;
|
|||||||
|
|
||||||
/// @brief A simple implementation to use std::is_null_pointer with C++11.
|
/// @brief A simple implementation to use std::is_null_pointer with C++11.
|
||||||
/// @note std::is_null_pointer is available since C++14.
|
/// @note std::is_null_pointer is available since C++14.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/is_null_pointer
|
/// @sa https://en.cppreference.como/w/cpp/types/is_null_pointer
|
||||||
/// @tparam T The type to be checked if it's equal to std::nullptr_t.
|
/// @tparam T The type to be checked if it's equal to std::nullptr_t.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct is_null_pointer
|
struct is_null_pointer
|
||||||
@@ -395,20 +395,20 @@ struct is_null_pointer
|
|||||||
|
|
||||||
/// @brief An alias template for std::remove_cv::type with C++11.
|
/// @brief An alias template for std::remove_cv::type with C++11.
|
||||||
/// @note std::remove_cv_t is available since C++14.
|
/// @note std::remove_cv_t is available since C++14.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/remove_cv
|
/// @sa https://en.cppreference.como/w/cpp/types/remove_cv
|
||||||
/// @tparam T A type from which const-volatile qualifiers are removed.
|
/// @tparam T A type from which const-volatile qualifiers are removed.
|
||||||
template <typename T> using remove_cv_t = typename std::remove_cv<T>::type;
|
template <typename T> using remove_cv_t = typename std::remove_cv<T>::type;
|
||||||
|
|
||||||
/// @brief An alias template for std::remove_pointer::type with C++11.
|
/// @brief An alias template for std::remove_pointer::type with C++11.
|
||||||
/// @note std::remove_pointer_t is available since C++14.
|
/// @note std::remove_pointer_t is available since C++14.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/remove_pointer
|
/// @sa https://en.cppreference.como/w/cpp/types/remove_pointer
|
||||||
/// @tparam T A type from which a pointer is removed.
|
/// @tparam T A type from which a pointer is removed.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using remove_pointer_t = typename std::remove_pointer<T>::type;
|
using remove_pointer_t = typename std::remove_pointer<T>::type;
|
||||||
|
|
||||||
/// @brief An alias template for std::remove_reference::type with C++11.
|
/// @brief An alias template for std::remove_reference::type with C++11.
|
||||||
/// @note std::remove_reference_t is available since C++14.
|
/// @note std::remove_reference_t is available since C++14.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/remove_reference
|
/// @sa https://en.cppreference.como/w/cpp/types/remove_reference
|
||||||
/// @tparam T A type from which a reference is removed.
|
/// @tparam T A type from which a reference is removed.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using remove_reference_t = typename std::remove_reference<T>::type;
|
using remove_reference_t = typename std::remove_reference<T>::type;
|
||||||
@@ -498,7 +498,7 @@ template <bool Val> using bool_constant = std::integral_constant<bool, Val>;
|
|||||||
/// @note
|
/// @note
|
||||||
/// std::conjunction is available since C++17.
|
/// std::conjunction is available since C++17.
|
||||||
/// This is applied when no traits are specified as inputs.
|
/// This is applied when no traits are specified as inputs.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/conjunction
|
/// @sa https://en.cppreference.como/w/cpp/types/conjunction
|
||||||
/// @tparam Traits Type traits to be checked if their ::value are all true.
|
/// @tparam Traits Type traits to be checked if their ::value are all true.
|
||||||
template <typename... Traits> struct conjunction : std::true_type {};
|
template <typename... Traits> struct conjunction : std::true_type {};
|
||||||
|
|
||||||
@@ -519,7 +519,7 @@ struct conjunction<First, Rest...>
|
|||||||
/// @note
|
/// @note
|
||||||
/// std::disjunction is available since C++17.
|
/// std::disjunction is available since C++17.
|
||||||
/// This is applied when no traits are specified as inputs.
|
/// This is applied when no traits are specified as inputs.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/disjunction
|
/// @sa https://en.cppreference.como/w/cpp/types/disjunction
|
||||||
/// @tparam Traits Type traits to be checked if at least one of their ::value is
|
/// @tparam Traits Type traits to be checked if at least one of their ::value is
|
||||||
/// true.
|
/// true.
|
||||||
template <typename... Traits> struct disjunction : std::false_type {};
|
template <typename... Traits> struct disjunction : std::false_type {};
|
||||||
@@ -539,7 +539,7 @@ struct disjunction<First, Rest...>
|
|||||||
|
|
||||||
/// @brief A simple implementation to use std::negation with C++11/C++14.
|
/// @brief A simple implementation to use std::negation with C++11/C++14.
|
||||||
/// @note std::negation is available since C++17.
|
/// @note std::negation is available since C++17.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/negation
|
/// @sa https://en.cppreference.como/w/cpp/types/negation
|
||||||
/// @tparam Trait Type trait whose ::value is negated.
|
/// @tparam Trait Type trait whose ::value is negated.
|
||||||
template <typename Trait>
|
template <typename Trait>
|
||||||
struct negation : std::integral_constant<bool, !Trait::value> {};
|
struct negation : std::integral_constant<bool, !Trait::value> {};
|
||||||
@@ -552,7 +552,7 @@ template <typename... Types> struct make_void {
|
|||||||
|
|
||||||
/// @brief A simple implementation to use std::void_t with C++11/C++14.
|
/// @brief A simple implementation to use std::void_t with C++11/C++14.
|
||||||
/// @note std::void_t is available since C++17.
|
/// @note std::void_t is available since C++17.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/void_t
|
/// @sa https://en.cppreference.como/w/cpp/types/void_t
|
||||||
/// @tparam Types Any types to be transformed to void type.
|
/// @tparam Types Any types to be transformed to void type.
|
||||||
template <typename... Types> using void_t = typename make_void<Types...>::type;
|
template <typename... Types> using void_t = typename make_void<Types...>::type;
|
||||||
|
|
||||||
@@ -571,7 +571,7 @@ using std::void_t;
|
|||||||
/// @brief A simple implementation to use std::remove_cvref_t with
|
/// @brief A simple implementation to use std::remove_cvref_t with
|
||||||
/// C++11/C++14/C++17.
|
/// C++11/C++14/C++17.
|
||||||
/// @note std::remove_cvref & std::remove_cvref_t are available since C++20.
|
/// @note std::remove_cvref & std::remove_cvref_t are available since C++20.
|
||||||
/// @sa https://en.cppreference.com/w/cpp/types/remove_cvref
|
/// @sa https://en.cppreference.como/w/cpp/types/remove_cvref
|
||||||
/// @tparam T A type from which cv-qualifiers and reference are removed.
|
/// @tparam T A type from which cv-qualifiers and reference are removed.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using remove_cvref_t =
|
using remove_cvref_t =
|
||||||
@@ -790,9 +790,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_META_TYPE_TRAITS_HPP
|
#ifndef FK_YAML_DETAIL_META_TYPE_TRAITS_HPP
|
||||||
@@ -890,9 +890,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_FKYAML_FWD_HPP
|
#ifndef FK_YAML_FKYAML_FWD_HPP
|
||||||
@@ -1106,9 +1106,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_YAML_VERSION_TYPE_HPP
|
#ifndef FK_YAML_YAML_VERSION_TYPE_HPP
|
||||||
@@ -1171,9 +1171,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_EXCEPTION_SAFE_ALLOCATION_HPP
|
#ifndef FK_YAML_DETAIL_EXCEPTION_SAFE_ALLOCATION_HPP
|
||||||
@@ -1278,9 +1278,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_INPUT_DESERIALIZER_HPP
|
#ifndef FK_YAML_DETAIL_INPUT_DESERIALIZER_HPP
|
||||||
@@ -1299,9 +1299,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_INPUT_LEXICAL_ANALYZER_HPP
|
#ifndef FK_YAML_DETAIL_INPUT_LEXICAL_ANALYZER_HPP
|
||||||
@@ -1320,9 +1320,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_ENCODINGS_URI_ENCODING_HPP
|
#ifndef FK_YAML_DETAIL_ENCODINGS_URI_ENCODING_HPP
|
||||||
@@ -1453,9 +1453,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_ENCODINGS_UTF_ENCODINGS_HPP
|
#ifndef FK_YAML_DETAIL_ENCODINGS_UTF_ENCODINGS_HPP
|
||||||
@@ -1471,9 +1471,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_EXCEPTION_HPP
|
#ifndef FK_YAML_EXCEPTION_HPP
|
||||||
@@ -1491,9 +1491,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_STRING_FORMATTER_HPP
|
#ifndef FK_YAML_DETAIL_STRING_FORMATTER_HPP
|
||||||
@@ -1541,9 +1541,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_TYPES_NODE_T_HPP
|
#ifndef FK_YAML_DETAIL_TYPES_NODE_T_HPP
|
||||||
@@ -1558,9 +1558,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_NODE_TYPE_HPP
|
#ifndef FK_YAML_NODE_TYPE_HPP
|
||||||
@@ -2155,9 +2155,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_INPUT_BLOCK_SCALAR_HEADER_HPP
|
#ifndef FK_YAML_DETAIL_INPUT_BLOCK_SCALAR_HEADER_HPP
|
||||||
@@ -2196,9 +2196,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_INPUT_POSITION_TRACKER_HPP
|
#ifndef FK_YAML_DETAIL_INPUT_POSITION_TRACKER_HPP
|
||||||
@@ -2213,9 +2213,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_STR_VIEW_HPP
|
#ifndef FK_YAML_DETAIL_STR_VIEW_HPP
|
||||||
@@ -3346,9 +3346,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_TYPES_LEXICAL_TOKEN_T_HPP
|
#ifndef FK_YAML_DETAIL_TYPES_LEXICAL_TOKEN_T_HPP
|
||||||
@@ -4396,7 +4396,7 @@ private:
|
|||||||
if FK_YAML_UNLIKELY (cur_itr == m_end_itr) {
|
if FK_YAML_UNLIKELY (cur_itr == m_end_itr) {
|
||||||
// Without the following iterator update, lexer cannot reach the end of
|
// Without the following iterator update, lexer cannot reach the end of
|
||||||
// input buffer and causes infinite loops from the next loop.
|
// input buffer and causes infinite loops from the next loop.
|
||||||
// (https://github.com/fktn-k/fkYAML/pull/410)
|
// (https://github.como/fktn-k/fkYAML/pull/410)
|
||||||
m_cur_itr = m_end_itr;
|
m_cur_itr = m_end_itr;
|
||||||
|
|
||||||
// If there's no non-empty line, the content indentation level is equal to
|
// If there's no non-empty line, the content indentation level is equal to
|
||||||
@@ -4726,9 +4726,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_INPUT_SCALAR_PARSER_HPP
|
#ifndef FK_YAML_DETAIL_INPUT_SCALAR_PARSER_HPP
|
||||||
@@ -4743,9 +4743,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// **NOTE FOR LIBRARY DEVELOPERS**:
|
// **NOTE FOR LIBRARY DEVELOPERS**:
|
||||||
@@ -5644,9 +5644,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_ENCODINGS_YAML_ESCAPER_HPP
|
#ifndef FK_YAML_DETAIL_ENCODINGS_YAML_ESCAPER_HPP
|
||||||
@@ -6005,9 +6005,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_INPUT_SCALAR_SCANNER_HPP
|
#ifndef FK_YAML_DETAIL_INPUT_SCALAR_SCANNER_HPP
|
||||||
@@ -6377,9 +6377,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_INPUT_TAG_T_HPP
|
#ifndef FK_YAML_DETAIL_INPUT_TAG_T_HPP
|
||||||
@@ -6948,7 +6948,7 @@ private:
|
|||||||
|
|
||||||
// conversion error from a scalar which is not tagged with !!int is
|
// conversion error from a scalar which is not tagged with !!int is
|
||||||
// recovered by treating it as a string scalar. See
|
// recovered by treating it as a string scalar. See
|
||||||
// https://github.com/fktn-k/fkYAML/issues/428.
|
// https://github.como/fktn-k/fkYAML/issues/428.
|
||||||
return basic_node_type(string_type(token.begin(), token.end()));
|
return basic_node_type(string_type(token.begin(), token.end()));
|
||||||
}
|
}
|
||||||
case node_type::FLOAT: {
|
case node_type::FLOAT: {
|
||||||
@@ -6966,7 +6966,7 @@ private:
|
|||||||
|
|
||||||
// conversion error from a scalar which is not tagged with !!float is
|
// conversion error from a scalar which is not tagged with !!float is
|
||||||
// recovered by treating it as a string scalar. See
|
// recovered by treating it as a string scalar. See
|
||||||
// https://github.com/fktn-k/fkYAML/issues/428.
|
// https://github.como/fktn-k/fkYAML/issues/428.
|
||||||
return basic_node_type(string_type(token.begin(), token.end()));
|
return basic_node_type(string_type(token.begin(), token.end()));
|
||||||
}
|
}
|
||||||
case node_type::STRING:
|
case node_type::STRING:
|
||||||
@@ -7000,9 +7000,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_INPUT_TAG_RESOLVER_HPP
|
#ifndef FK_YAML_DETAIL_INPUT_TAG_RESOLVER_HPP
|
||||||
@@ -7208,9 +7208,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_META_INPUT_ADAPTER_TRAITS_HPP
|
#ifndef FK_YAML_DETAIL_META_INPUT_ADAPTER_TRAITS_HPP
|
||||||
@@ -7274,9 +7274,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_NODE_ATTRS_HPP
|
#ifndef FK_YAML_DETAIL_NODE_ATTRS_HPP
|
||||||
@@ -7417,9 +7417,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_NODE_PROPERTY_HPP
|
#ifndef FK_YAML_DETAIL_NODE_PROPERTY_HPP
|
||||||
@@ -8019,7 +8019,7 @@ private:
|
|||||||
// (i.e., the properties are for a container node), the
|
// (i.e., the properties are for a container node), the
|
||||||
// application and the line advancement must happen here.
|
// application and the line advancement must happen here.
|
||||||
// Otherwise, a false indent error will be emitted. See
|
// Otherwise, a false indent error will be emitted. See
|
||||||
// https://github.com/fktn-k/fkYAML/issues/368 for more details.
|
// https://github.como/fktn-k/fkYAML/issues/368 for more details.
|
||||||
line = line_after_props;
|
line = line_after_props;
|
||||||
indent = lexer.get_last_token_begin_pos();
|
indent = lexer.get_last_token_begin_pos();
|
||||||
*mp_current_node = basic_node_type::mapping();
|
*mp_current_node = basic_node_type::mapping();
|
||||||
@@ -8910,9 +8910,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_INPUT_INPUT_ADAPTER_HPP
|
#ifndef FK_YAML_DETAIL_INPUT_INPUT_ADAPTER_HPP
|
||||||
@@ -8935,9 +8935,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_DETECTOR_HPP
|
#ifndef FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_DETECTOR_HPP
|
||||||
@@ -8953,9 +8953,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_T_HPP
|
#ifndef FK_YAML_DETAIL_ENCODINGS_UTF_ENCODE_T_HPP
|
||||||
@@ -10496,9 +10496,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_ITERATOR_HPP
|
#ifndef FK_YAML_DETAIL_ITERATOR_HPP
|
||||||
@@ -10914,7 +10914,7 @@ namespace std {
|
|||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
// clang emits warnings against mixed usage of class/struct for
|
// clang emits warnings against mixed usage of class/struct for
|
||||||
// tuple_size/tuple_element. see also:
|
// tuple_size/tuple_element. see also:
|
||||||
// https://groups.google.com/a/isocpp.org/g/std-discussion/c/QC-AMb5oO1w
|
// https://groups.google.como/a/isocpp.org/g/std-discussion/c/QC-AMb5oO1w
|
||||||
#pragma clang diagnostic push
|
#pragma clang diagnostic push
|
||||||
#pragma clang diagnostic ignored "-Wmismatched-tags"
|
#pragma clang diagnostic ignored "-Wmismatched-tags"
|
||||||
#endif
|
#endif
|
||||||
@@ -10949,9 +10949,9 @@ struct tuple_element<I, ::fkyaml::detail::iterator<ValueType>> {
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP
|
#ifndef FK_YAML_DETAIL_MAP_RANGE_PROXY_HPP
|
||||||
@@ -11139,9 +11139,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_NODE_REF_STORAGE_HPP
|
#ifndef FK_YAML_DETAIL_NODE_REF_STORAGE_HPP
|
||||||
@@ -11235,9 +11235,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP
|
#ifndef FK_YAML_DETAIL_OUTPUT_SERIALIZER_HPP
|
||||||
@@ -11255,9 +11255,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP
|
#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_STRING_HPP
|
||||||
@@ -11337,7 +11337,7 @@ to_string(FloatType v, std::string &s) noexcept {
|
|||||||
|
|
||||||
// If `v` is actually an integer and no scientific notation is used for
|
// If `v` is actually an integer and no scientific notation is used for
|
||||||
// serialization, ".0" must be appended. The result would cause a roundtrip
|
// serialization, ".0" must be appended. The result would cause a roundtrip
|
||||||
// issue otherwise. https://github.com/fktn-k/fkYAML/issues/405
|
// issue otherwise. https://github.como/fktn-k/fkYAML/issues/405
|
||||||
const std::size_t pos = s.find_first_of(".e");
|
const std::size_t pos = s.find_first_of(".e");
|
||||||
if (pos == std::string::npos) {
|
if (pos == std::string::npos) {
|
||||||
s += ".0";
|
s += ".0";
|
||||||
@@ -11744,9 +11744,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_REVERSE_ITERATOR_HPP
|
#ifndef FK_YAML_DETAIL_REVERSE_ITERATOR_HPP
|
||||||
@@ -12016,9 +12016,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP
|
#ifndef FK_YAML_DETAIL_TYPES_YAML_VERSION_T_HPP
|
||||||
@@ -12073,9 +12073,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_NODE_VALUE_CONVERTER_HPP
|
#ifndef FK_YAML_NODE_VALUE_CONVERTER_HPP
|
||||||
@@ -12090,9 +12090,9 @@ FK_YAML_DETAIL_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP
|
#ifndef FK_YAML_DETAIL_CONVERSIONS_FROM_NODE_HPP
|
||||||
@@ -12875,9 +12875,9 @@ FK_YAML_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP
|
#ifndef FK_YAML_DETAIL_CONVERSIONS_TO_NODE_HPP
|
||||||
@@ -13262,9 +13262,9 @@ FK_YAML_NAMESPACE_END
|
|||||||
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML
|
||||||
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
// library | __| _ < \_ _/| ___ | _ | |___ version 0.4.2
|
||||||
// |__| |_| \__| |_| |_| |_|___||___|______|
|
// |__| |_| \__| |_| |_| |_|___||___|______|
|
||||||
// https://github.com/fktn-k/fkYAML
|
// https://github.como/fktn-k/fkYAML
|
||||||
//
|
//
|
||||||
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani <fktn.dev@gmail.como>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FK_YAML_ORDERED_MAP_HPP
|
#ifndef FK_YAML_ORDERED_MAP_HPP
|
||||||
@@ -13875,7 +13875,7 @@ public:
|
|||||||
/// iterators into a basic_node object.
|
/// iterators into a basic_node object.
|
||||||
/// @note
|
/// @note
|
||||||
/// Iterators must satisfy the LegacyInputIterator requirements.
|
/// Iterators must satisfy the LegacyInputIterator requirements.
|
||||||
/// See https://en.cppreference.com/w/cpp/named_req/InputIterator.
|
/// See https://en.cppreference.como/w/cpp/named_req/InputIterator.
|
||||||
/// @tparam ItrType Type of a compatible iterator
|
/// @tparam ItrType Type of a compatible iterator
|
||||||
/// @param[in] begin An iterator to the first element of an input sequence.
|
/// @param[in] begin An iterator to the first element of an input sequence.
|
||||||
/// @param[in] end An iterator to the past-the-last element of an input
|
/// @param[in] end An iterator to the past-the-last element of an input
|
||||||
@@ -15517,7 +15517,7 @@ inline namespace yaml_literals {
|
|||||||
|
|
||||||
// Whitespace before the literal operator identifier is deprecated in C++23 or
|
// Whitespace before the literal operator identifier is deprecated in C++23 or
|
||||||
// better but required in C++11. Ignore the warning as a workaround.
|
// better but required in C++11. Ignore the warning as a workaround.
|
||||||
// https://github.com/fktn-k/fkYAML/pull/417
|
// https://github.como/fktn-k/fkYAML/pull/417
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic push
|
#pragma clang diagnostic push
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||||
|
|||||||
+68
-49
@@ -1,4 +1,4 @@
|
|||||||
// Ogg Vorbis audio decoder - v1.20 - public domain
|
// Ogg Vorbis audio decoder - v1.22 - public domain
|
||||||
// http://nothings.org/stb_vorbis/
|
// http://nothings.org/stb_vorbis/
|
||||||
//
|
//
|
||||||
// Original version written by Sean Barrett in 2007.
|
// Original version written by Sean Barrett in 2007.
|
||||||
@@ -29,12 +29,15 @@
|
|||||||
// Bernhard Wodo Evan Balster github:alxprd
|
// Bernhard Wodo Evan Balster github:alxprd
|
||||||
// Tom Beaumont Ingo Leitgeb Nicolas Guillemot
|
// Tom Beaumont Ingo Leitgeb Nicolas Guillemot
|
||||||
// Phillip Bennefall Rohit Thiago Goulart
|
// Phillip Bennefall Rohit Thiago Goulart
|
||||||
// github:manxorist saga musix github:infatum
|
// github:manxorist Saga Musix github:infatum
|
||||||
// Timur Gagiev Maxwell Koo Peter Waller
|
// Timur Gagiev Maxwell Koo Peter Waller
|
||||||
// github:audinowho Dougall Johnson David Reid
|
// github:audinowho Dougall Johnson David Reid
|
||||||
// github:Clownacy Pedro J. Estebanez Remi Verschelde
|
// github:Clownacy Pedro J. Estebanez Remi Verschelde
|
||||||
|
// AnthoFoxo github:morlat Gabriel Ravier
|
||||||
//
|
//
|
||||||
// Partial history:
|
// Partial history:
|
||||||
|
// 1.22 - 2021-07-11 - various small fixes
|
||||||
|
// 1.21 - 2021-07-02 - fix bug for files with no comments
|
||||||
// 1.20 - 2020-07-11 - several small fixes
|
// 1.20 - 2020-07-11 - several small fixes
|
||||||
// 1.19 - 2020-02-05 - warnings
|
// 1.19 - 2020-02-05 - warnings
|
||||||
// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.
|
// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.
|
||||||
@@ -220,6 +223,12 @@ extern int stb_vorbis_decode_frame_pushdata(
|
|||||||
// channel. In other words, (*output)[0][0] contains the first sample from
|
// channel. In other words, (*output)[0][0] contains the first sample from
|
||||||
// the first channel, and (*output)[1][0] contains the first sample from
|
// the first channel, and (*output)[1][0] contains the first sample from
|
||||||
// the second channel.
|
// the second channel.
|
||||||
|
//
|
||||||
|
// *output points into stb_vorbis's internal output buffer storage; these
|
||||||
|
// buffers are owned by stb_vorbis and application code should not free
|
||||||
|
// them or modify their contents. They are transient and will be overwritten
|
||||||
|
// once you ask for more data to get decoded, so be sure to grab any data
|
||||||
|
// you need before then.
|
||||||
|
|
||||||
extern void stb_vorbis_flush_pushdata(stb_vorbis *f);
|
extern void stb_vorbis_flush_pushdata(stb_vorbis *f);
|
||||||
// inform stb_vorbis that your next datablock will not be contiguous with
|
// inform stb_vorbis that your next datablock will not be contiguous with
|
||||||
@@ -579,7 +588,7 @@ enum STBVorbisError
|
|||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
#endif
|
#endif
|
||||||
#if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__)
|
#if defined(__linux__) || defined(__linux) || defined(__sun__) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__)
|
||||||
#include <alloca.h>
|
#include <alloca.h>
|
||||||
#endif
|
#endif
|
||||||
#else // STB_VORBIS_NO_CRT
|
#else // STB_VORBIS_NO_CRT
|
||||||
@@ -646,6 +655,12 @@ typedef signed int int32;
|
|||||||
|
|
||||||
typedef float codetype;
|
typedef float codetype;
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define STBV_NOTUSED(v) (void)(v)
|
||||||
|
#else
|
||||||
|
#define STBV_NOTUSED(v) (void)sizeof(v)
|
||||||
|
#endif
|
||||||
|
|
||||||
// @NOTE
|
// @NOTE
|
||||||
//
|
//
|
||||||
// Some arrays below are tagged "//varies", which means it's actually
|
// Some arrays below are tagged "//varies", which means it's actually
|
||||||
@@ -1046,7 +1061,7 @@ static float float32_unpack(uint32 x)
|
|||||||
uint32 sign = x & 0x80000000;
|
uint32 sign = x & 0x80000000;
|
||||||
uint32 exp = (x & 0x7fe00000) >> 21;
|
uint32 exp = (x & 0x7fe00000) >> 21;
|
||||||
double res = sign ? -(double)mantissa : (double)mantissa;
|
double res = sign ? -(double)mantissa : (double)mantissa;
|
||||||
return (float) ldexp((float)res, exp-788);
|
return (float) ldexp((float)res, (int)exp-788);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1077,6 +1092,7 @@ static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values)
|
|||||||
// find the first entry
|
// find the first entry
|
||||||
for (k=0; k < n; ++k) if (len[k] < NO_CODE) break;
|
for (k=0; k < n; ++k) if (len[k] < NO_CODE) break;
|
||||||
if (k == n) { assert(c->sorted_entries == 0); return TRUE; }
|
if (k == n) { assert(c->sorted_entries == 0); return TRUE; }
|
||||||
|
assert(len[k] < 32); // no error return required, code reading lens checks this
|
||||||
// add to the list
|
// add to the list
|
||||||
add_entry(c, 0, k, m++, len[k], values);
|
add_entry(c, 0, k, m++, len[k], values);
|
||||||
// add all available leaves
|
// add all available leaves
|
||||||
@@ -1090,6 +1106,7 @@ static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values)
|
|||||||
uint32 res;
|
uint32 res;
|
||||||
int z = len[i], y;
|
int z = len[i], y;
|
||||||
if (z == NO_CODE) continue;
|
if (z == NO_CODE) continue;
|
||||||
|
assert(z < 32); // no error return required, code reading lens checks this
|
||||||
// find lowest available leaf (should always be earliest,
|
// find lowest available leaf (should always be earliest,
|
||||||
// which is what the specification calls for)
|
// which is what the specification calls for)
|
||||||
// note that this property, and the fact we can never have
|
// note that this property, and the fact we can never have
|
||||||
@@ -1099,12 +1116,10 @@ static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values)
|
|||||||
while (z > 0 && !available[z]) --z;
|
while (z > 0 && !available[z]) --z;
|
||||||
if (z == 0) { return FALSE; }
|
if (z == 0) { return FALSE; }
|
||||||
res = available[z];
|
res = available[z];
|
||||||
assert(z >= 0 && z < 32);
|
|
||||||
available[z] = 0;
|
available[z] = 0;
|
||||||
add_entry(c, bit_reverse(res), i, m++, len[i], values);
|
add_entry(c, bit_reverse(res), i, m++, len[i], values);
|
||||||
// propagate availability up the tree
|
// propagate availability up the tree
|
||||||
if (z != len[i]) {
|
if (z != len[i]) {
|
||||||
assert(len[i] >= 0 && len[i] < 32);
|
|
||||||
for (y=len[i]; y > z; --y) {
|
for (y=len[i]; y > z; --y) {
|
||||||
assert(available[y] == 0);
|
assert(available[y] == 0);
|
||||||
available[y] = res + (1 << (32-y));
|
available[y] = res + (1 << (32-y));
|
||||||
@@ -2577,34 +2592,33 @@ static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A,
|
|||||||
|
|
||||||
while (z > base) {
|
while (z > base) {
|
||||||
float k00,k11;
|
float k00,k11;
|
||||||
|
float l00,l11;
|
||||||
|
|
||||||
k00 = z[-0] - z[-8];
|
k00 = z[-0] - z[ -8];
|
||||||
k11 = z[-1] - z[-9];
|
k11 = z[-1] - z[ -9];
|
||||||
z[-0] = z[-0] + z[-8];
|
l00 = z[-2] - z[-10];
|
||||||
z[-1] = z[-1] + z[-9];
|
l11 = z[-3] - z[-11];
|
||||||
z[-8] = k00;
|
z[ -0] = z[-0] + z[ -8];
|
||||||
z[-9] = k11 ;
|
z[ -1] = z[-1] + z[ -9];
|
||||||
|
z[ -2] = z[-2] + z[-10];
|
||||||
|
z[ -3] = z[-3] + z[-11];
|
||||||
|
z[ -8] = k00;
|
||||||
|
z[ -9] = k11;
|
||||||
|
z[-10] = (l00+l11) * A2;
|
||||||
|
z[-11] = (l11-l00) * A2;
|
||||||
|
|
||||||
k00 = z[ -2] - z[-10];
|
k00 = z[ -4] - z[-12];
|
||||||
k11 = z[ -3] - z[-11];
|
|
||||||
z[ -2] = z[ -2] + z[-10];
|
|
||||||
z[ -3] = z[ -3] + z[-11];
|
|
||||||
z[-10] = (k00+k11) * A2;
|
|
||||||
z[-11] = (k11-k00) * A2;
|
|
||||||
|
|
||||||
k00 = z[-12] - z[ -4]; // reverse to avoid a unary negation
|
|
||||||
k11 = z[ -5] - z[-13];
|
k11 = z[ -5] - z[-13];
|
||||||
|
l00 = z[ -6] - z[-14];
|
||||||
|
l11 = z[ -7] - z[-15];
|
||||||
z[ -4] = z[ -4] + z[-12];
|
z[ -4] = z[ -4] + z[-12];
|
||||||
z[ -5] = z[ -5] + z[-13];
|
z[ -5] = z[ -5] + z[-13];
|
||||||
z[-12] = k11;
|
|
||||||
z[-13] = k00;
|
|
||||||
|
|
||||||
k00 = z[-14] - z[ -6]; // reverse to avoid a unary negation
|
|
||||||
k11 = z[ -7] - z[-15];
|
|
||||||
z[ -6] = z[ -6] + z[-14];
|
z[ -6] = z[ -6] + z[-14];
|
||||||
z[ -7] = z[ -7] + z[-15];
|
z[ -7] = z[ -7] + z[-15];
|
||||||
z[-14] = (k00+k11) * A2;
|
z[-12] = k11;
|
||||||
z[-15] = (k00-k11) * A2;
|
z[-13] = -k00;
|
||||||
|
z[-14] = (l11-l00) * A2;
|
||||||
|
z[-15] = (l00+l11) * -A2;
|
||||||
|
|
||||||
iter_54(z);
|
iter_54(z);
|
||||||
iter_54(z-8);
|
iter_54(z-8);
|
||||||
@@ -3069,6 +3083,7 @@ static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *f
|
|||||||
for (q=1; q < g->values; ++q) {
|
for (q=1; q < g->values; ++q) {
|
||||||
j = g->sorted_order[q];
|
j = g->sorted_order[q];
|
||||||
#ifndef STB_VORBIS_NO_DEFER_FLOOR
|
#ifndef STB_VORBIS_NO_DEFER_FLOOR
|
||||||
|
STBV_NOTUSED(step2_flag);
|
||||||
if (finalY[j] >= 0)
|
if (finalY[j] >= 0)
|
||||||
#else
|
#else
|
||||||
if (step2_flag[j])
|
if (step2_flag[j])
|
||||||
@@ -3171,6 +3186,7 @@ static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start,
|
|||||||
|
|
||||||
// WINDOWING
|
// WINDOWING
|
||||||
|
|
||||||
|
STBV_NOTUSED(left_end);
|
||||||
n = f->blocksize[m->blockflag];
|
n = f->blocksize[m->blockflag];
|
||||||
map = &f->mapping[m->mapping];
|
map = &f->mapping[m->mapping];
|
||||||
|
|
||||||
@@ -3368,7 +3384,7 @@ static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start,
|
|||||||
// this isn't to spec, but spec would require us to read ahead
|
// this isn't to spec, but spec would require us to read ahead
|
||||||
// and decode the size of all current frames--could be done,
|
// and decode the size of all current frames--could be done,
|
||||||
// but presumably it's not a commonly used feature
|
// but presumably it's not a commonly used feature
|
||||||
f->current_loc = -n2; // start of first frame is positioned for discard
|
f->current_loc = 0u - n2; // start of first frame is positioned for discard (NB this is an intentional unsigned overflow/wrap-around)
|
||||||
// we might have to discard samples "from" the next frame too,
|
// we might have to discard samples "from" the next frame too,
|
||||||
// if we're lapping a large block then a small at the start?
|
// if we're lapping a large block then a small at the start?
|
||||||
f->discard_samples_deferred = n - right_end;
|
f->discard_samples_deferred = n - right_end;
|
||||||
@@ -3642,9 +3658,11 @@ static int start_decoder(vorb *f)
|
|||||||
f->vendor[len] = (char)'\0';
|
f->vendor[len] = (char)'\0';
|
||||||
//user comments
|
//user comments
|
||||||
f->comment_list_length = get32_packet(f);
|
f->comment_list_length = get32_packet(f);
|
||||||
if (f->comment_list_length > 0) {
|
f->comment_list = NULL;
|
||||||
f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length));
|
if (f->comment_list_length > 0)
|
||||||
if (f->comment_list == NULL) return error(f, VORBIS_outofmem);
|
{
|
||||||
|
f->comment_list = (char**) setup_malloc(f, sizeof(char*) * (f->comment_list_length));
|
||||||
|
if (f->comment_list == NULL) return error(f, VORBIS_outofmem);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(i=0; i < f->comment_list_length; ++i) {
|
for(i=0; i < f->comment_list_length; ++i) {
|
||||||
@@ -3867,8 +3885,7 @@ static int start_decoder(vorb *f)
|
|||||||
unsigned int div=1;
|
unsigned int div=1;
|
||||||
for (k=0; k < c->dimensions; ++k) {
|
for (k=0; k < c->dimensions; ++k) {
|
||||||
int off = (z / div) % c->lookup_values;
|
int off = (z / div) % c->lookup_values;
|
||||||
float val = mults[off];
|
float val = mults[off]*c->delta_value + c->minimum_value + last;
|
||||||
val = mults[off]*c->delta_value + c->minimum_value + last;
|
|
||||||
c->multiplicands[j*c->dimensions + k] = val;
|
c->multiplicands[j*c->dimensions + k] = val;
|
||||||
if (c->sequence_p)
|
if (c->sequence_p)
|
||||||
last = val;
|
last = val;
|
||||||
@@ -3951,7 +3968,7 @@ static int start_decoder(vorb *f)
|
|||||||
if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
|
if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
|
||||||
}
|
}
|
||||||
for (k=0; k < 1 << g->class_subclasses[j]; ++k) {
|
for (k=0; k < 1 << g->class_subclasses[j]; ++k) {
|
||||||
g->subclass_books[j][k] = get_bits(f,8)-1;
|
g->subclass_books[j][k] = (int16)get_bits(f,8)-1;
|
||||||
if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
|
if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4509,6 +4526,7 @@ stb_vorbis *stb_vorbis_open_pushdata(
|
|||||||
*error = VORBIS_need_more_data;
|
*error = VORBIS_need_more_data;
|
||||||
else
|
else
|
||||||
*error = p.error;
|
*error = p.error;
|
||||||
|
vorbis_deinit(&p);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
f = vorbis_alloc(&p);
|
f = vorbis_alloc(&p);
|
||||||
@@ -4566,7 +4584,7 @@ static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last)
|
|||||||
header[i] = get8(f);
|
header[i] = get8(f);
|
||||||
if (f->eof) return 0;
|
if (f->eof) return 0;
|
||||||
if (header[4] != 0) goto invalid;
|
if (header[4] != 0) goto invalid;
|
||||||
goal = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24);
|
goal = header[22] + (header[23] << 8) + (header[24]<<16) + ((uint32)header[25]<<24);
|
||||||
for (i=22; i < 26; ++i)
|
for (i=22; i < 26; ++i)
|
||||||
header[i] = 0;
|
header[i] = 0;
|
||||||
crc = 0;
|
crc = 0;
|
||||||
@@ -4970,7 +4988,7 @@ unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f)
|
|||||||
// set. whoops!
|
// set. whoops!
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
previous_safe = last_page_loc+1;
|
//previous_safe = last_page_loc+1; // NOTE: not used after this point, but note for debugging
|
||||||
last_page_loc = stb_vorbis_get_file_offset(f);
|
last_page_loc = stb_vorbis_get_file_offset(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5081,7 +5099,10 @@ stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const st
|
|||||||
stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc)
|
stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc)
|
||||||
{
|
{
|
||||||
stb_vorbis *f, p;
|
stb_vorbis *f, p;
|
||||||
if (data == NULL) return NULL;
|
if (!data) {
|
||||||
|
if (error) *error = VORBIS_unexpected_eof;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
vorbis_init(&p, alloc);
|
vorbis_init(&p, alloc);
|
||||||
p.stream = (uint8 *) data;
|
p.stream = (uint8 *) data;
|
||||||
p.stream_end = (uint8 *) data + len;
|
p.stream_end = (uint8 *) data + len;
|
||||||
@@ -5156,11 +5177,11 @@ static void copy_samples(short *dest, float *src, int len)
|
|||||||
|
|
||||||
static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len)
|
static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len)
|
||||||
{
|
{
|
||||||
#define BUFFER_SIZE 32
|
#define STB_BUFFER_SIZE 32
|
||||||
float buffer[BUFFER_SIZE];
|
float buffer[STB_BUFFER_SIZE];
|
||||||
int i,j,o,n = BUFFER_SIZE;
|
int i,j,o,n = STB_BUFFER_SIZE;
|
||||||
check_endianness();
|
check_endianness();
|
||||||
for (o = 0; o < len; o += BUFFER_SIZE) {
|
for (o = 0; o < len; o += STB_BUFFER_SIZE) {
|
||||||
memset(buffer, 0, sizeof(buffer));
|
memset(buffer, 0, sizeof(buffer));
|
||||||
if (o + n > len) n = len - o;
|
if (o + n > len) n = len - o;
|
||||||
for (j=0; j < num_c; ++j) {
|
for (j=0; j < num_c; ++j) {
|
||||||
@@ -5177,16 +5198,17 @@ static void compute_samples(int mask, short *output, int num_c, float **data, in
|
|||||||
output[o+i] = v;
|
output[o+i] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#undef STB_BUFFER_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len)
|
static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len)
|
||||||
{
|
{
|
||||||
#define BUFFER_SIZE 32
|
#define STB_BUFFER_SIZE 32
|
||||||
float buffer[BUFFER_SIZE];
|
float buffer[STB_BUFFER_SIZE];
|
||||||
int i,j,o,n = BUFFER_SIZE >> 1;
|
int i,j,o,n = STB_BUFFER_SIZE >> 1;
|
||||||
// o is the offset in the source data
|
// o is the offset in the source data
|
||||||
check_endianness();
|
check_endianness();
|
||||||
for (o = 0; o < len; o += BUFFER_SIZE >> 1) {
|
for (o = 0; o < len; o += STB_BUFFER_SIZE >> 1) {
|
||||||
// o2 is the offset in the output data
|
// o2 is the offset in the output data
|
||||||
int o2 = o << 1;
|
int o2 = o << 1;
|
||||||
memset(buffer, 0, sizeof(buffer));
|
memset(buffer, 0, sizeof(buffer));
|
||||||
@@ -5216,6 +5238,7 @@ static void compute_stereo_samples(short *output, int num_c, float **data, int d
|
|||||||
output[o2+i] = v;
|
output[o2+i] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#undef STB_BUFFER_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples)
|
static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples)
|
||||||
@@ -5288,8 +5311,6 @@ int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short
|
|||||||
float **outputs;
|
float **outputs;
|
||||||
int len = num_shorts / channels;
|
int len = num_shorts / channels;
|
||||||
int n=0;
|
int n=0;
|
||||||
int z = f->channels;
|
|
||||||
if (z > channels) z = channels;
|
|
||||||
while (n < len) {
|
while (n < len) {
|
||||||
int k = f->channel_buffer_end - f->channel_buffer_start;
|
int k = f->channel_buffer_end - f->channel_buffer_start;
|
||||||
if (n+k >= len) k = len - n;
|
if (n+k >= len) k = len - n;
|
||||||
@@ -5308,8 +5329,6 @@ int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, in
|
|||||||
{
|
{
|
||||||
float **outputs;
|
float **outputs;
|
||||||
int n=0;
|
int n=0;
|
||||||
int z = f->channels;
|
|
||||||
if (z > channels) z = channels;
|
|
||||||
while (n < len) {
|
while (n < len) {
|
||||||
int k = f->channel_buffer_end - f->channel_buffer_start;
|
int k = f->channel_buffer_end - f->channel_buffer_start;
|
||||||
if (n+k >= len) k = len - n;
|
if (n+k >= len) k = len - n;
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
// Unitat de compilació aïllada per a la implementació de stb_vorbis.
|
||||||
|
// Viu dins de source/external/ perquè el `.clang-tidy` d'aquesta carpeta
|
||||||
|
// desactiva tots els checks (com fa per stb_image_write_impl.cpp) i el
|
||||||
|
// pre-commit hook ja filtra aquesta ruta de clang-format / clang-tidy.
|
||||||
|
// Així els fals positius de clang-analyzer-* dins de codi C de tercers
|
||||||
|
// no afecten el nostre codi, que continua tenint tots els checks actius.
|
||||||
|
//
|
||||||
|
// jail_audio.cpp defineix STB_VORBIS_HEADER_ONLY abans d'incloure el .c,
|
||||||
|
// així només en veu les declaracions; les definicions les aporta aquest
|
||||||
|
// TU i l'enllaçador les resol.
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(bugprone-suspicious-include)
|
||||||
|
#include "external/stb_vorbis.c"
|
||||||
+12
-13
@@ -2,20 +2,19 @@
|
|||||||
|
|
||||||
#include "core/defaults.hpp"
|
#include "core/defaults.hpp"
|
||||||
|
|
||||||
// Aliases per a backward compatibility amb codi existent
|
// Aliases para backward compatibility con codi existent
|
||||||
// Permet usar Constants::MARGE_ESQ en lloc de Defaults::Game::MARGIN_LEFT
|
// Permet usar Constants::MARGIN_LEFT en lloc de Defaults::Game::MARGIN_LEFT
|
||||||
|
|
||||||
namespace Constants {
|
namespace Constants {
|
||||||
// Marges de l'àrea de joc (derivats de Defaults::Zones::GAME)
|
// Márgenes de l'àrea de juego (derivats de Defaults::Zones::GAME)
|
||||||
constexpr int MARGE_ESQ = static_cast<int>(Defaults::Zones::PLAYAREA.x);
|
constexpr int MARGIN_LEFT = static_cast<int>(Defaults::Zones::PLAYAREA.x);
|
||||||
constexpr int MARGE_DRET =
|
constexpr int MARGIN_RIGHT =
|
||||||
static_cast<int>(Defaults::Zones::PLAYAREA.x + Defaults::Zones::PLAYAREA.w);
|
static_cast<int>(Defaults::Zones::PLAYAREA.x + Defaults::Zones::PLAYAREA.w);
|
||||||
constexpr int MARGE_DALT = static_cast<int>(Defaults::Zones::PLAYAREA.y);
|
constexpr int MARGIN_TOP = static_cast<int>(Defaults::Zones::PLAYAREA.y);
|
||||||
constexpr int MARGE_BAIX =
|
constexpr int MARGIN_BOTTOM =
|
||||||
static_cast<int>(Defaults::Zones::PLAYAREA.y + Defaults::Zones::PLAYAREA.h);
|
static_cast<int>(Defaults::Zones::PLAYAREA.y + Defaults::Zones::PLAYAREA.h);
|
||||||
|
|
||||||
// Límits de polígons i objectes
|
// Límits de objectes
|
||||||
constexpr int MAX_IPUNTS = Defaults::Entities::MAX_IPUNTS;
|
|
||||||
constexpr int MAX_ORNIS = Defaults::Entities::MAX_ORNIS;
|
constexpr int MAX_ORNIS = Defaults::Entities::MAX_ORNIS;
|
||||||
constexpr int MAX_BALES = Defaults::Entities::MAX_BALES;
|
constexpr int MAX_BALES = Defaults::Entities::MAX_BALES;
|
||||||
|
|
||||||
@@ -28,8 +27,8 @@ constexpr float PI = Defaults::Math::PI;
|
|||||||
|
|
||||||
// Helpers per comprovar límits de zona
|
// Helpers per comprovar límits de zona
|
||||||
inline bool dins_zona_joc(float x, float y) {
|
inline bool dins_zona_joc(float x, float y) {
|
||||||
const SDL_FPoint punt = {x, y};
|
const SDL_FPoint point = {x, y};
|
||||||
return SDL_PointInRectFloat(&punt, &Defaults::Zones::PLAYAREA);
|
return SDL_PointInRectFloat(&point, &Defaults::Zones::PLAYAREA);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void obtenir_limits_zona(float& min_x, float& max_x, float& min_y, float& max_y) {
|
inline void obtenir_limits_zona(float& min_x, float& max_x, float& min_y, float& max_y) {
|
||||||
@@ -40,7 +39,7 @@ inline void obtenir_limits_zona(float& min_x, float& max_x, float& min_y, float&
|
|||||||
max_y = zona.y + zona.h;
|
max_y = zona.y + zona.h;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtenir límits segurs (compensant radi de l'entitat)
|
// Obtenir límits segurs (compensant radi de l'entidad)
|
||||||
inline void obtenir_limits_zona_segurs(float radi, float& min_x, float& max_x, float& min_y, float& max_y) {
|
inline void obtenir_limits_zona_segurs(float radi, float& min_x, float& max_x, float& min_y, float& max_y) {
|
||||||
const auto& zona = Defaults::Zones::PLAYAREA;
|
const auto& zona = Defaults::Zones::PLAYAREA;
|
||||||
constexpr float MARGE_SEGURETAT = 10.0F; // Safety margin
|
constexpr float MARGE_SEGURETAT = 10.0F; // Safety margin
|
||||||
@@ -51,7 +50,7 @@ inline void obtenir_limits_zona_segurs(float radi, float& min_x, float& max_x, f
|
|||||||
max_y = zona.y + zona.h - radi - MARGE_SEGURETAT;
|
max_y = zona.y + zona.h - radi - MARGE_SEGURETAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtenir centre de l'àrea de joc
|
// Obtenir centro de l'àrea de juego
|
||||||
inline void obtenir_centre_zona(float& centre_x, float& centre_y) {
|
inline void obtenir_centre_zona(float& centre_x, float& centre_y) {
|
||||||
const auto& zona = Defaults::Zones::PLAYAREA;
|
const auto& zona = Defaults::Zones::PLAYAREA;
|
||||||
centre_x = zona.x + (zona.w / 2.0F);
|
centre_x = zona.x + (zona.w / 2.0F);
|
||||||
|
|||||||
@@ -1,37 +1,40 @@
|
|||||||
// debris.hpp - Fragment de línia volant (explosió de formes)
|
// debris.hpp - Fragment de línia volant (explosión de formes)
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
|
|
||||||
namespace Effects {
|
namespace Effects {
|
||||||
|
|
||||||
// Debris: un segment de línia que vola perpendicular a sí mateix
|
// Debris: un segment de línia que vola perpendicular a sí mismo
|
||||||
// Representa un fragment d'una forma destruïda (nau, enemic, bala)
|
// Representa un fragment de una shape destruïda (ship, enemy, bullet)
|
||||||
struct Debris {
|
struct Debris {
|
||||||
// Geometria del segment (2 punts en coordenades mundials)
|
// Geometria del segment (2 points en coordenades mundials)
|
||||||
Punt p1; // Punt inicial del segment
|
Vec2 p1; // Vec2 inicial del segment
|
||||||
Punt p2; // Punt final del segment
|
Vec2 p2; // Vec2 final del segment
|
||||||
|
|
||||||
// Física
|
// Física
|
||||||
Punt velocitat; // Velocitat en px/s (components x, y)
|
Vec2 velocity; // Velocidad en px/s (components x, y)
|
||||||
float acceleracio; // Acceleració negativa (fricció) en px/s²
|
float acceleration; // Aceleración negativa (fricció) en px/s²
|
||||||
|
|
||||||
// Rotació
|
// Rotación
|
||||||
float angle_rotacio; // Angle de rotació acumulat (radians)
|
float angle_rotacio; // Angle de rotación acumulat (radians)
|
||||||
float velocitat_rot; // Velocitat de rotació de TRAYECTORIA (rad/s)
|
float velocitat_rot; // Velocidad de rotación de TRAYECTORIA (rad/s)
|
||||||
float velocitat_rot_visual; // Velocitat de rotació VISUAL del segment (rad/s)
|
float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s)
|
||||||
|
|
||||||
// Estat de vida
|
// Estat de vida
|
||||||
float temps_vida; // Temps transcorregut (segons)
|
float temps_vida; // Temps transcorregut (segons)
|
||||||
float temps_max; // Temps de vida màxim (segons)
|
float temps_max; // Temps de vida màxim (segons)
|
||||||
bool actiu; // Està actiu?
|
bool active; // Está active?
|
||||||
|
|
||||||
// Shrinking (reducció de distància entre punts)
|
// Shrinking (reducció de distancia entre points)
|
||||||
float factor_shrink; // Factor de reducció per segon (0.0-1.0)
|
float factor_shrink; // Factor de reducció per segon (0.0-1.0)
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
float brightness; // Factor de brillantor (0.0-1.0, heretat de l'objecte original)
|
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
|
} // namespace Effects
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// debris_manager.cpp - Implementació del gestor de fragments
|
// debris_manager.cpp - Implementació del gestor de fragments
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#include "debris_manager.hpp"
|
#include "debris_manager.hpp"
|
||||||
|
|
||||||
@@ -14,60 +14,61 @@
|
|||||||
|
|
||||||
namespace Effects {
|
namespace Effects {
|
||||||
|
|
||||||
// Helper: transformar punt amb rotació, escala i trasllació
|
// Helper: transformar point con rotación, scale i traslación
|
||||||
// (Copiat de shape_renderer.cpp:12-34)
|
// (Copiat de shape_renderer.cpp:12-34)
|
||||||
static Punt transform_point(const Punt& point, const Punt& shape_centre, const Punt& posicio, float angle, float escala) {
|
static Vec2 transform_point(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale) {
|
||||||
// 1. Centrar el punt respecte al centre de la forma
|
// 1. Centrar el point respecte al centro de la shape
|
||||||
float centered_x = point.x - shape_centre.x;
|
float centered_x = point.x - shape_centre.x;
|
||||||
float centered_y = point.y - shape_centre.y;
|
float centered_y = point.y - shape_centre.y;
|
||||||
|
|
||||||
// 2. Aplicar escala al punt centrat
|
// 2. Aplicar scale al point centrat
|
||||||
float scaled_x = centered_x * escala;
|
float scaled_x = centered_x * scale;
|
||||||
float scaled_y = centered_y * escala;
|
float scaled_y = centered_y * scale;
|
||||||
|
|
||||||
// 3. Aplicar rotació
|
// 3. Aplicar rotación
|
||||||
float cos_a = std::cos(angle);
|
float cos_a = std::cos(angle);
|
||||||
float sin_a = std::sin(angle);
|
float sin_a = std::sin(angle);
|
||||||
|
|
||||||
float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a);
|
float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a);
|
||||||
float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a);
|
float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a);
|
||||||
|
|
||||||
// 4. Aplicar trasllació a posició mundial
|
// 4. Aplicar traslación a posición mundial
|
||||||
return {.x = rotated_x + posicio.x, .y = rotated_y + posicio.y};
|
return {.x = rotated_x + position.x, .y = rotated_y + position.y};
|
||||||
}
|
}
|
||||||
|
|
||||||
DebrisManager::DebrisManager(SDL_Renderer* renderer)
|
DebrisManager::DebrisManager(Rendering::Renderer* renderer)
|
||||||
: renderer_(renderer) {
|
: renderer_(renderer) {
|
||||||
// Inicialitzar tots els debris com inactius
|
// Inicialitzar todos los debris como inactius
|
||||||
for (auto& debris : debris_pool_) {
|
for (auto& debris : debris_pool_) {
|
||||||
debris.actiu = false;
|
debris.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebrisManager::explotar(const std::shared_ptr<Graphics::Shape>& shape,
|
void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
|
||||||
const Punt& centre,
|
const Vec2& centro,
|
||||||
float angle,
|
float angle,
|
||||||
float escala,
|
float scale,
|
||||||
float velocitat_base,
|
float velocitat_base,
|
||||||
float brightness,
|
float brightness,
|
||||||
const Punt& velocitat_objecte,
|
const Vec2& velocitat_objecte,
|
||||||
float velocitat_angular,
|
float velocitat_angular,
|
||||||
float factor_herencia_visual,
|
float factor_herencia_visual,
|
||||||
const std::string& sound) {
|
const std::string& sound,
|
||||||
if (!shape || !shape->es_valida()) {
|
SDL_Color color) {
|
||||||
|
if (!shape || !shape->isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reproducir sonido de explosión
|
// Reproducir sonido de explosión
|
||||||
Audio::get()->playSound(sound, Audio::Group::GAME);
|
Audio::get()->playSound(sound, Audio::Group::GAME);
|
||||||
|
|
||||||
// Obtenir centre de la forma per a transformacions
|
// Obtenir centro de la shape para transformacions
|
||||||
const Punt& shape_centre = shape->get_centre();
|
const Vec2& shape_centre = shape->getCenter();
|
||||||
|
|
||||||
// Iterar sobre totes les primitives de la forma
|
// Iterar sobre todas las primitives de la shape
|
||||||
for (const auto& primitive : shape->get_primitives()) {
|
for (const auto& primitive : shape->get_primitives()) {
|
||||||
// Processar cada segment de línia
|
// Processar cada segment de línia
|
||||||
std::vector<std::pair<Punt, Punt>> segments;
|
std::vector<std::pair<Vec2, Vec2>> segments;
|
||||||
|
|
||||||
if (primitive.type == Graphics::PrimitiveType::POLYLINE) {
|
if (primitive.type == Graphics::PrimitiveType::POLYLINE) {
|
||||||
// Polyline: extreure segments consecutius
|
// Polyline: extreure segments consecutius
|
||||||
@@ -81,16 +82,16 @@ void DebrisManager::explotar(const std::shared_ptr<Graphics::Shape>& shape,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear debris per a cada segment
|
// Crear debris para cada segment
|
||||||
for (const auto& [local_p1, local_p2] : segments) {
|
for (const auto& [local_p1, local_p2] : segments) {
|
||||||
// 1. Transformar punts locals → coordenades mundials
|
// 1. Transformar points locals → coordenades mundials
|
||||||
Punt world_p1 =
|
Vec2 world_p1 =
|
||||||
transform_point(local_p1, shape_centre, centre, angle, escala);
|
transform_point(local_p1, shape_centre, centro, angle, scale);
|
||||||
Punt world_p2 =
|
Vec2 world_p2 =
|
||||||
transform_point(local_p2, shape_centre, centre, angle, escala);
|
transform_point(local_p2, shape_centre, centro, angle, scale);
|
||||||
|
|
||||||
// 2. Trobar slot lliure
|
// 2. Trobar slot lliure
|
||||||
Debris* debris = trobar_slot_lliure();
|
Debris* debris = findFreeSlot();
|
||||||
if (debris == nullptr) {
|
if (debris == nullptr) {
|
||||||
std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n";
|
std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n";
|
||||||
return; // Pool ple
|
return; // Pool ple
|
||||||
@@ -100,25 +101,25 @@ void DebrisManager::explotar(const std::shared_ptr<Graphics::Shape>& shape,
|
|||||||
debris->p1 = world_p1;
|
debris->p1 = world_p1;
|
||||||
debris->p2 = world_p2;
|
debris->p2 = world_p2;
|
||||||
|
|
||||||
// 4. Calcular direcció d'explosió (radial, des del centre cap a fora)
|
// 4. Calcular direcció de explosión (radial, des del centro hacia fuera)
|
||||||
Punt direccio = calcular_direccio_explosio(world_p1, world_p2, centre);
|
Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro);
|
||||||
|
|
||||||
// 5. Velocitat inicial (base ± variació aleatòria + velocitat heretada)
|
// 5. Velocidad inicial (base ± variació aleatòria + velocity heretada)
|
||||||
float speed =
|
float speed =
|
||||||
velocitat_base +
|
velocitat_base +
|
||||||
(((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F - 1.0F) *
|
(((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F - 1.0F) *
|
||||||
Defaults::Physics::Debris::VARIACIO_VELOCITAT);
|
Defaults::Physics::Debris::VARIACIO_VELOCITAT);
|
||||||
|
|
||||||
// Heredar velocitat de l'objecte original (suma vectorial)
|
// Heredar velocity de l'objecte original (suma vectorial)
|
||||||
debris->velocitat.x = (direccio.x * speed) + velocitat_objecte.x;
|
debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x;
|
||||||
debris->velocitat.y = (direccio.y * speed) + velocitat_objecte.y;
|
debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y;
|
||||||
debris->acceleracio = Defaults::Physics::Debris::ACCELERACIO;
|
debris->acceleration = Defaults::Physics::Debris::ACCELERACIO;
|
||||||
|
|
||||||
// 6. Herència de velocitat angular amb cap + conversió d'excés
|
// 6. Herència de velocity angular con sin + conversió de excés
|
||||||
|
|
||||||
// 6a. Rotació de TRAYECTORIA amb cap + conversió tangencial
|
// 6a. Rotación de TRAYECTORIA con sin + conversió tangencial
|
||||||
if (std::abs(velocitat_angular) > 0.01F) {
|
if (std::abs(velocitat_angular) > 0.01F) {
|
||||||
// FASE 1: Aplicar herència i variació (igual que abans)
|
// FASE 1: Aplicar herència i variació (igual que antes)
|
||||||
float factor_herencia =
|
float factor_herencia =
|
||||||
Defaults::Physics::Debris::FACTOR_HERENCIA_MIN +
|
Defaults::Physics::Debris::FACTOR_HERENCIA_MIN +
|
||||||
((std::rand() / static_cast<float>(RAND_MAX)) *
|
((std::rand() / static_cast<float>(RAND_MAX)) *
|
||||||
@@ -131,19 +132,19 @@ void DebrisManager::explotar(const std::shared_ptr<Graphics::Shape>& shape,
|
|||||||
((std::rand() / static_cast<float>(RAND_MAX)) * 0.2F) - 0.1F;
|
((std::rand() / static_cast<float>(RAND_MAX)) * 0.2F) - 0.1F;
|
||||||
velocitat_ang_heretada *= (1.0F + variacio);
|
velocitat_ang_heretada *= (1.0F + variacio);
|
||||||
|
|
||||||
// FASE 2: Aplicar cap i calcular excés
|
// FASE 2: Aplicar sin i calcular excés
|
||||||
constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_ROT_MAX;
|
constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_ROT_MAX;
|
||||||
float abs_ang = std::abs(velocitat_ang_heretada);
|
float abs_ang = std::abs(velocitat_ang_heretada);
|
||||||
float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F;
|
float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F;
|
||||||
|
|
||||||
if (abs_ang > CAP) {
|
if (abs_ang > CAP) {
|
||||||
// Excés: convertir a velocitat tangencial
|
// Excés: convertir a velocity tangencial
|
||||||
float excess = abs_ang - CAP;
|
float excess = abs_ang - CAP;
|
||||||
|
|
||||||
// Radi de la forma (enemics = 20 px)
|
// Radi de la shape (enemigos = 20 px)
|
||||||
float radius = 20.0F;
|
float radius = 20.0F;
|
||||||
|
|
||||||
// Velocitat tangencial = ω_excés × radi
|
// Velocidad tangencial = ω_excés × radi
|
||||||
float v_tangential = excess * radius;
|
float v_tangential = excess * radius;
|
||||||
|
|
||||||
// Direcció tangencial: perpendicular a la radial (90° CCW)
|
// Direcció tangencial: perpendicular a la radial (90° CCW)
|
||||||
@@ -151,38 +152,38 @@ void DebrisManager::explotar(const std::shared_ptr<Graphics::Shape>& shape,
|
|||||||
float tangent_x = -direccio.y;
|
float tangent_x = -direccio.y;
|
||||||
float tangent_y = direccio.x;
|
float tangent_y = direccio.x;
|
||||||
|
|
||||||
// Afegir velocitat tangencial (suma vectorial)
|
// Añadir velocity tangencial (suma vectorial)
|
||||||
debris->velocitat.x += tangent_x * v_tangential;
|
debris->velocity.x += tangent_x * v_tangential;
|
||||||
debris->velocitat.y += tangent_y * v_tangential;
|
debris->velocity.y += tangent_y * v_tangential;
|
||||||
|
|
||||||
// Aplicar cap a velocitat angular (preservar signe)
|
// Aplicar hacia velocity angular (preservar signe)
|
||||||
debris->velocitat_rot = sign_ang * CAP;
|
debris->velocitat_rot = sign_ang * CAP;
|
||||||
} else {
|
} else {
|
||||||
// Per sota del cap: comportament normal
|
// Per sota del sin: comportament normal
|
||||||
debris->velocitat_rot = velocitat_ang_heretada;
|
debris->velocitat_rot = velocitat_ang_heretada;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debris->velocitat_rot = 0.0F; // Nave: sin curvas
|
debris->velocitat_rot = 0.0F; // Nave: sin curvas
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6b. Rotació VISUAL (proporcional según factor_herencia_visual)
|
// 6b. Rotación VISUAL (proporcional según factor_herencia_visual)
|
||||||
if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) {
|
if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) {
|
||||||
// Heredar rotación visual con factor proporcional
|
// Heredar rotación visual con factor proporcional
|
||||||
debris->velocitat_rot_visual = debris->velocitat_rot * factor_herencia_visual;
|
debris->velocitat_rot_visual = debris->velocitat_rot * factor_herencia_visual;
|
||||||
|
|
||||||
// Variació aleatòria petita (±5%) per naturalitat
|
// Variació aleatòria pequeña (±5%) per naturalitat
|
||||||
float variacio_visual =
|
float variacio_visual =
|
||||||
((std::rand() / static_cast<float>(RAND_MAX)) * 0.1F) - 0.05F;
|
((std::rand() / static_cast<float>(RAND_MAX)) * 0.1F) - 0.05F;
|
||||||
debris->velocitat_rot_visual *= (1.0F + variacio_visual);
|
debris->velocitat_rot_visual *= (1.0F + variacio_visual);
|
||||||
} else {
|
} else {
|
||||||
// Rotació visual aleatòria (factor = 0.0 o sin velocidad angular)
|
// Rotación visual aleatòria (factor = 0.0 o sin velocidad angular)
|
||||||
debris->velocitat_rot_visual =
|
debris->velocitat_rot_visual =
|
||||||
Defaults::Physics::Debris::ROTACIO_MIN +
|
Defaults::Physics::Debris::ROTACIO_MIN +
|
||||||
((std::rand() / static_cast<float>(RAND_MAX)) *
|
((std::rand() / static_cast<float>(RAND_MAX)) *
|
||||||
(Defaults::Physics::Debris::ROTACIO_MAX -
|
(Defaults::Physics::Debris::ROTACIO_MAX -
|
||||||
Defaults::Physics::Debris::ROTACIO_MIN));
|
Defaults::Physics::Debris::ROTACIO_MIN));
|
||||||
|
|
||||||
// 50% probabilitat de rotació en sentit contrari
|
// 50% probabilitat de rotación en sentit contrari
|
||||||
if (std::rand() % 2 == 0) {
|
if (std::rand() % 2 == 0) {
|
||||||
debris->velocitat_rot_visual = -debris->velocitat_rot_visual;
|
debris->velocitat_rot_visual = -debris->velocitat_rot_visual;
|
||||||
}
|
}
|
||||||
@@ -195,66 +196,67 @@ void DebrisManager::explotar(const std::shared_ptr<Graphics::Shape>& shape,
|
|||||||
debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA;
|
debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA;
|
||||||
debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;
|
debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;
|
||||||
|
|
||||||
// 8. Heredar brightness
|
// 8. Heredar brightness y color del padre
|
||||||
debris->brightness = brightness;
|
debris->brightness = brightness;
|
||||||
|
debris->color = color;
|
||||||
|
|
||||||
// 9. Activar
|
// 9. Activar
|
||||||
debris->actiu = true;
|
debris->active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebrisManager::actualitzar(float delta_time) {
|
void DebrisManager::update(float delta_time) {
|
||||||
for (auto& debris : debris_pool_) {
|
for (auto& debris : debris_pool_) {
|
||||||
if (!debris.actiu) {
|
if (!debris.active) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Actualitzar temps de vida
|
// 1. Actualitzar time de vida
|
||||||
debris.temps_vida += delta_time;
|
debris.temps_vida += delta_time;
|
||||||
|
|
||||||
// Desactivar si ha superat temps màxim
|
// Desactivar si ha superat time màxim
|
||||||
if (debris.temps_vida >= debris.temps_max) {
|
if (debris.temps_vida >= debris.temps_max) {
|
||||||
debris.actiu = false;
|
debris.active = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Actualitzar velocitat (desacceleració)
|
// 2. Actualitzar velocity (desacceleració)
|
||||||
// Aplicar fricció en la direcció del moviment
|
// Aplicar fricció en la direcció del movement
|
||||||
float speed = std::sqrt((debris.velocitat.x * debris.velocitat.x) +
|
float speed = std::sqrt((debris.velocity.x * debris.velocity.x) +
|
||||||
(debris.velocitat.y * debris.velocitat.y));
|
(debris.velocity.y * debris.velocity.y));
|
||||||
|
|
||||||
if (speed > 1.0F) {
|
if (speed > 1.0F) {
|
||||||
// Calcular direcció normalitzada
|
// Calcular direcció normalitzada
|
||||||
float dir_x = debris.velocitat.x / speed;
|
float dir_x = debris.velocity.x / speed;
|
||||||
float dir_y = debris.velocitat.y / speed;
|
float dir_y = debris.velocity.y / speed;
|
||||||
|
|
||||||
// Aplicar acceleració negativa (fricció)
|
// Aplicar aceleración negativa (fricció)
|
||||||
float nova_speed = speed + (debris.acceleracio * delta_time);
|
float nova_speed = speed + (debris.acceleration * delta_time);
|
||||||
nova_speed = std::max(nova_speed, 0.0F);
|
nova_speed = std::max(nova_speed, 0.0F);
|
||||||
|
|
||||||
debris.velocitat.x = dir_x * nova_speed;
|
debris.velocity.x = dir_x * nova_speed;
|
||||||
debris.velocitat.y = dir_y * nova_speed;
|
debris.velocity.y = dir_y * nova_speed;
|
||||||
} else {
|
} else {
|
||||||
// Velocitat molt baixa, aturar
|
// Velocidad mucho baixa, aturar
|
||||||
debris.velocitat.x = 0.0F;
|
debris.velocity.x = 0.0F;
|
||||||
debris.velocitat.y = 0.0F;
|
debris.velocity.y = 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2b. Rotar vector de velocitat (trayectoria curva)
|
// 2b. Rotar vector de velocity (trayectoria curva)
|
||||||
if (std::abs(debris.velocitat_rot) > 0.01F) {
|
if (std::abs(debris.velocitat_rot) > 0.01F) {
|
||||||
// Calcular angle de rotació aquest frame
|
// Calcular angle de rotación este frame
|
||||||
float dangle = debris.velocitat_rot * delta_time;
|
float dangle = debris.velocitat_rot * delta_time;
|
||||||
|
|
||||||
// Rotar vector de velocitat usant matriu de rotació 2D
|
// Rotar vector de velocity usant matriu de rotación 2D
|
||||||
float vel_x_old = debris.velocitat.x;
|
float vel_x_old = debris.velocity.x;
|
||||||
float vel_y_old = debris.velocitat.y;
|
float vel_y_old = debris.velocity.y;
|
||||||
|
|
||||||
float cos_a = std::cos(dangle);
|
float cos_a = std::cos(dangle);
|
||||||
float sin_a = std::sin(dangle);
|
float sin_a = std::sin(dangle);
|
||||||
|
|
||||||
debris.velocitat.x = (vel_x_old * cos_a) - (vel_y_old * sin_a);
|
debris.velocity.x = (vel_x_old * cos_a) - (vel_y_old * sin_a);
|
||||||
debris.velocitat.y = (vel_x_old * sin_a) + (vel_y_old * cos_a);
|
debris.velocity.y = (vel_x_old * sin_a) + (vel_y_old * cos_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2c. Aplicar fricció angular (desacceleració gradual)
|
// 2c. Aplicar fricció angular (desacceleració gradual)
|
||||||
@@ -270,80 +272,79 @@ void DebrisManager::actualitzar(float delta_time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Calcular centre del segment
|
// 3. Calcular centro del segment
|
||||||
Punt centre = {.x = (debris.p1.x + debris.p2.x) / 2.0F,
|
Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F,
|
||||||
.y = (debris.p1.y + debris.p2.y) / 2.0F};
|
.y = (debris.p1.y + debris.p2.y) / 2.0F};
|
||||||
|
|
||||||
// 4. Actualitzar posició del centre
|
// 4. Actualitzar posición del centro
|
||||||
centre.x += debris.velocitat.x * delta_time;
|
centro.x += debris.velocity.x * delta_time;
|
||||||
centre.y += debris.velocitat.y * delta_time;
|
centro.y += debris.velocity.y * delta_time;
|
||||||
|
|
||||||
// 5. Actualitzar rotació VISUAL
|
// 5. Actualitzar rotación VISUAL
|
||||||
debris.angle_rotacio += debris.velocitat_rot_visual * delta_time;
|
debris.angle_rotacio += debris.velocitat_rot_visual * delta_time;
|
||||||
|
|
||||||
// 6. Aplicar shrinking (reducció de distància entre punts)
|
// 6. Aplicar shrinking (reducció de distancia entre points)
|
||||||
float shrink_factor =
|
float shrink_factor =
|
||||||
1.0F - (debris.factor_shrink * debris.temps_vida / debris.temps_max);
|
1.0F - (debris.factor_shrink * debris.temps_vida / debris.temps_max);
|
||||||
shrink_factor = std::max(0.0F, shrink_factor); // No negatiu
|
shrink_factor = std::max(0.0F, shrink_factor); // No negatiu
|
||||||
|
|
||||||
// Calcular distància original entre punts
|
// Calcular distancia original entre points
|
||||||
float dx = debris.p2.x - debris.p1.x;
|
float dx = debris.p2.x - debris.p1.x;
|
||||||
float dy = debris.p2.y - debris.p1.y;
|
float dy = debris.p2.y - debris.p1.y;
|
||||||
|
|
||||||
// 7. Reconstruir segment amb nova mida i rotació
|
// 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 original_angle = std::atan2(dy, dx);
|
||||||
float new_angle = original_angle + debris.angle_rotacio;
|
float new_angle = original_angle + debris.angle_rotacio;
|
||||||
|
|
||||||
debris.p1.x = centre.x - (half_length * std::cos(new_angle));
|
debris.p1.x = centro.x - (half_length * std::cos(new_angle));
|
||||||
debris.p1.y = centre.y - (half_length * std::sin(new_angle));
|
debris.p1.y = centro.y - (half_length * std::sin(new_angle));
|
||||||
debris.p2.x = centre.x + (half_length * std::cos(new_angle));
|
debris.p2.x = centro.x + (half_length * std::cos(new_angle));
|
||||||
debris.p2.y = centre.y + (half_length * std::sin(new_angle));
|
debris.p2.y = centro.y + (half_length * std::sin(new_angle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebrisManager::dibuixar() const {
|
void DebrisManager::draw() const {
|
||||||
for (const auto& debris : debris_pool_) {
|
for (const auto& debris : debris_pool_) {
|
||||||
if (!debris.actiu) {
|
if (!debris.active) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dibuixar segment de línia amb brightness heretat
|
// Dibujar segmento con brightness y color heredados del padre.
|
||||||
Rendering::linea(renderer_,
|
Rendering::linea(renderer_,
|
||||||
static_cast<int>(debris.p1.x),
|
static_cast<int>(debris.p1.x),
|
||||||
static_cast<int>(debris.p1.y),
|
static_cast<int>(debris.p1.y),
|
||||||
static_cast<int>(debris.p2.x),
|
static_cast<int>(debris.p2.x),
|
||||||
static_cast<int>(debris.p2.y),
|
static_cast<int>(debris.p2.y),
|
||||||
true,
|
debris.brightness, 0.0F, debris.color);
|
||||||
debris.brightness);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Debris* DebrisManager::trobar_slot_lliure() {
|
Debris* DebrisManager::findFreeSlot() {
|
||||||
for (auto& debris : debris_pool_) {
|
for (auto& debris : debris_pool_) {
|
||||||
if (!debris.actiu) {
|
if (!debris.active) {
|
||||||
return &debris;
|
return &debris;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr; // Pool ple
|
return nullptr; // Pool ple
|
||||||
}
|
}
|
||||||
|
|
||||||
Punt DebrisManager::calcular_direccio_explosio(const Punt& p1,
|
Vec2 DebrisManager::computeExplosionDirection(const Vec2& p1,
|
||||||
const Punt& p2,
|
const Vec2& p2,
|
||||||
const Punt& centre_objecte) const {
|
const Vec2& centre_objecte) const {
|
||||||
// 1. Calcular centre del segment
|
// 1. Calcular centro del segment
|
||||||
float centro_seg_x = (p1.x + p2.x) / 2.0F;
|
float centro_seg_x = (p1.x + p2.x) / 2.0F;
|
||||||
float centro_seg_y = (p1.y + p2.y) / 2.0F;
|
float centro_seg_y = (p1.y + p2.y) / 2.0F;
|
||||||
|
|
||||||
// 2. Calcular vector des del centre de l'objecte cap al centre del segment
|
// 2. Calcular vector des del centro de l'objecte hacia el centro del segment
|
||||||
// Això garanteix que la direcció sempre apunte cap a fora (direcció radial)
|
// Això garanteix que la direcció siempre apunte hacia fuera (direcció radial)
|
||||||
float dx = centro_seg_x - centre_objecte.x;
|
float dx = centro_seg_x - centre_objecte.x;
|
||||||
float dy = centro_seg_y - centre_objecte.y;
|
float dy = centro_seg_y - centre_objecte.y;
|
||||||
|
|
||||||
// 3. Normalitzar (obtenir vector unitari)
|
// 3. Normalitzar (obtenir vector unitari)
|
||||||
float length = std::sqrt((dx * dx) + (dy * dy));
|
float length = std::sqrt((dx * dx) + (dy * dy));
|
||||||
if (length < 0.001F) {
|
if (length < 0.001F) {
|
||||||
// Segment al centre (cas extrem molt improbable), retornar direcció aleatòria
|
// Segment al centro (cas extrem mucho improbable), retornar direcció aleatòria
|
||||||
float angle_rand =
|
float angle_rand =
|
||||||
(std::rand() / static_cast<float>(RAND_MAX)) * 2.0F * Defaults::Math::PI;
|
(std::rand() / static_cast<float>(RAND_MAX)) * 2.0F * Defaults::Math::PI;
|
||||||
return {.x = std::cos(angle_rand), .y = std::sin(angle_rand)};
|
return {.x = std::cos(angle_rand), .y = std::sin(angle_rand)};
|
||||||
@@ -352,7 +353,7 @@ Punt DebrisManager::calcular_direccio_explosio(const Punt& p1,
|
|||||||
dx /= length;
|
dx /= length;
|
||||||
dy /= length;
|
dy /= length;
|
||||||
|
|
||||||
// 4. Afegir variació aleatòria petita (±15°) per varietat visual
|
// 4. Añadir variació aleatòria pequeña (±15°) per varietat visual
|
||||||
float angle_variacio =
|
float angle_variacio =
|
||||||
((std::rand() % 30) - 15) * Defaults::Math::PI / 180.0F;
|
((std::rand() % 30) - 15) * Defaults::Math::PI / 180.0F;
|
||||||
|
|
||||||
@@ -365,16 +366,16 @@ Punt DebrisManager::calcular_direccio_explosio(const Punt& p1,
|
|||||||
return {.x = final_x, .y = final_y};
|
return {.x = final_x, .y = final_y};
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebrisManager::reiniciar() {
|
void DebrisManager::reset() {
|
||||||
for (auto& debris : debris_pool_) {
|
for (auto& debris : debris_pool_) {
|
||||||
debris.actiu = false;
|
debris.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int DebrisManager::get_num_actius() const {
|
int DebrisManager::getActiveCount() const {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (const auto& debris : debris_pool_) {
|
for (const auto& debris : debris_pool_) {
|
||||||
if (debris.actiu) {
|
if (debris.active) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,76 @@
|
|||||||
// debris_manager.hpp - Gestor de fragments d'explosions
|
// debris_manager.hpp - Gestor de fragments de explosions
|
||||||
// © 2025 Port a C++20 amb SDL3
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "core/defaults.hpp"
|
||||||
#include "core/graphics/shape.hpp"
|
#include "core/graphics/shape.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
#include "debris.hpp"
|
#include "debris.hpp"
|
||||||
|
|
||||||
namespace Effects {
|
namespace Effects {
|
||||||
|
|
||||||
// Gestor de fragments d'explosions
|
// Gestor de fragments de explosions
|
||||||
// Manté un pool d'objectes Debris i gestiona el seu cicle de vida
|
// Manté un pool de objectes Debris i gestiona el seu cicle de vida
|
||||||
class DebrisManager {
|
class DebrisManager {
|
||||||
public:
|
public:
|
||||||
explicit DebrisManager(SDL_Renderer* renderer);
|
explicit DebrisManager(Rendering::Renderer* renderer);
|
||||||
|
|
||||||
// Crear explosió a partir d'una forma
|
// Crear explosión a partir de una shape
|
||||||
// - shape: forma vectorial a explotar
|
// - shape: shape vectorial a explode
|
||||||
// - centre: posició del centre de l'objecte
|
// - centro: posición del centro de l'objecte
|
||||||
// - angle: orientació de l'objecte (radians)
|
// - angle: orientació de l'objecte (radians)
|
||||||
// - escala: escala de l'objecte (1.0 = normal)
|
// - scale: scale de l'objecte (1.0 = normal)
|
||||||
// - velocitat_base: velocitat inicial dels fragments (px/s)
|
// - velocitat_base: velocity inicial dels fragments (px/s)
|
||||||
// - brightness: factor de brillantor heretat (0.0-1.0, per defecte 1.0)
|
// - brightness: factor de brightness heretat (0.0-1.0, per defecte 1.0)
|
||||||
// - velocitat_objecte: velocitat de l'objecte que explota (px/s, per defecte 0)
|
// - velocitat_objecte: velocity de l'objecte que explota (px/s, per defecte 0)
|
||||||
// - velocitat_angular: velocitat angular heretada (rad/s, per defecte 0)
|
// - velocitat_angular: velocity angular heretada (rad/s, per defecte 0)
|
||||||
// - factor_herencia_visual: factor de herència rotació visual (0.0-1.0, per defecte 0.0)
|
// - factor_herencia_visual: factor de herència rotación visual (0.0-1.0, per defecte 0.0)
|
||||||
void explotar(const std::shared_ptr<Graphics::Shape>& shape,
|
void explode(const std::shared_ptr<Graphics::Shape>& shape,
|
||||||
const Punt& centre,
|
const Vec2& centro,
|
||||||
float angle,
|
float angle,
|
||||||
float escala,
|
float scale,
|
||||||
float velocitat_base,
|
float velocitat_base,
|
||||||
float brightness = 1.0F,
|
float brightness = 1.0F,
|
||||||
const Punt& velocitat_objecte = {.x = 0.0F, .y = 0.0F},
|
const Vec2& velocitat_objecte = {.x = 0.0F, .y = 0.0F},
|
||||||
float velocitat_angular = 0.0F,
|
float velocitat_angular = 0.0F,
|
||||||
float factor_herencia_visual = 0.0F,
|
float factor_herencia_visual = 0.0F,
|
||||||
const std::string& sound = Defaults::Sound::EXPLOSION);
|
const std::string& sound = Defaults::Sound::EXPLOSION,
|
||||||
|
SDL_Color color = {0, 0, 0, 0}); // alpha==0 → fragmentos usan oscilador global
|
||||||
|
|
||||||
// Actualitzar tots els fragments actius
|
// Actualitzar todos los fragments active
|
||||||
void actualitzar(float delta_time);
|
void update(float delta_time);
|
||||||
|
|
||||||
// Dibuixar tots els fragments actius
|
// Dibuixar todos los fragments active
|
||||||
void dibuixar() const;
|
void draw() const;
|
||||||
|
|
||||||
// Reiniciar tots els fragments (neteja)
|
// Reiniciar todos los fragments (clear)
|
||||||
void reiniciar();
|
void reset();
|
||||||
|
|
||||||
// Obtenir número de fragments actius
|
// Obtenir número de fragments active
|
||||||
[[nodiscard]] int get_num_actius() const;
|
[[nodiscard]] int getActiveCount() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_Renderer* renderer_;
|
Rendering::Renderer* renderer_;
|
||||||
|
|
||||||
// Pool de fragments (màxim concurrent)
|
// Pool de fragments (màxim concurrent)
|
||||||
// Un pentàgon té 5 línies, 15 enemics = 75 línies
|
// Un pentágono té 5 línies, 15 enemigos = 75 línies
|
||||||
// + nau (3 línies) + bales (5 línies * 3) = 93 línies màxim
|
// + ship (3 línies) + balas (5 línies * 3) = 93 línies màxim
|
||||||
// Arrodonit a 100 per seguretat
|
// Arrodonit a 100 per seguretat
|
||||||
static constexpr int MAX_DEBRIS = 150;
|
static constexpr int MAX_DEBRIS = 150;
|
||||||
std::array<Debris, MAX_DEBRIS> debris_pool_;
|
std::array<Debris, MAX_DEBRIS> debris_pool_;
|
||||||
|
|
||||||
// Trobar primer slot inactiu
|
// Trobar primer slot inactiu
|
||||||
Debris* trobar_slot_lliure();
|
Debris* findFreeSlot();
|
||||||
|
|
||||||
// Calcular direcció d'explosió (radial, des del centre cap al segment)
|
// Calcular direcció de explosión (radial, des del centro hacia el segment)
|
||||||
[[nodiscard]] Punt calcular_direccio_explosio(const Punt& p1, const Punt& p2, const Punt& centre_objecte) const;
|
[[nodiscard]] Vec2 computeExplosionDirection(const Vec2& p1, const Vec2& p2, const Vec2& centre_objecte) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Effects
|
} // namespace Effects
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// floating_score.hpp - Número de puntuación que apareix y desapareix
|
||||||
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/types.hpp"
|
||||||
|
|
||||||
|
namespace Effects {
|
||||||
|
|
||||||
|
// FloatingScore: text animat que muestra points guanyats
|
||||||
|
// S'activa cuando es destrueix un enemy i s'esvaeix después de un time
|
||||||
|
struct FloatingScore {
|
||||||
|
// Text a mostrar (e.g., "100", "150", "200")
|
||||||
|
std::string text;
|
||||||
|
|
||||||
|
// Posición actual (coordenades mundials)
|
||||||
|
Vec2 position;
|
||||||
|
|
||||||
|
// Animación de movement
|
||||||
|
Vec2 velocity; // px/s (normalment sin amunt: {0.0f, -30.0f})
|
||||||
|
|
||||||
|
// Animación de fade
|
||||||
|
float temps_vida; // Temps transcorregut (segons)
|
||||||
|
float temps_max; // Temps de vida màxim (segons)
|
||||||
|
float brightness; // Brillantor calculada (0.0-1.0)
|
||||||
|
|
||||||
|
// Estat
|
||||||
|
bool active;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Effects
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// floating_score_manager.cpp - Implementació del gestor de números flotantes
|
||||||
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
|
#include "floating_score_manager.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Effects {
|
||||||
|
|
||||||
|
FloatingScoreManager::FloatingScoreManager(Rendering::Renderer* renderer)
|
||||||
|
: text_(renderer) {
|
||||||
|
// Inicialitzar todos los slots como inactius
|
||||||
|
for (auto& pf : pool_) {
|
||||||
|
pf.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FloatingScoreManager::crear(int points, const Vec2& position) {
|
||||||
|
// 1. Trobar slot lliure
|
||||||
|
FloatingScore* pf = findFreeSlot();
|
||||||
|
if (pf == nullptr) {
|
||||||
|
return; // Pool ple (improbable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Inicialitzar puntuación flotante
|
||||||
|
pf->text = std::to_string(points);
|
||||||
|
pf->position = position;
|
||||||
|
pf->velocity = {.x = Defaults::FloatingScore::VELOCITY_X,
|
||||||
|
.y = Defaults::FloatingScore::VELOCITY_Y};
|
||||||
|
pf->temps_vida = 0.0F;
|
||||||
|
pf->temps_max = Defaults::FloatingScore::LIFETIME;
|
||||||
|
pf->brightness = 1.0F;
|
||||||
|
pf->active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FloatingScoreManager::update(float delta_time) {
|
||||||
|
for (auto& pf : pool_) {
|
||||||
|
if (!pf.active) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Actualitzar posición (deriva sin amunt)
|
||||||
|
pf.position.x += pf.velocity.x * delta_time;
|
||||||
|
pf.position.y += pf.velocity.y * delta_time;
|
||||||
|
|
||||||
|
// 2. Actualitzar time de vida
|
||||||
|
pf.temps_vida += delta_time;
|
||||||
|
|
||||||
|
// 3. Calcular brightness (fade lineal)
|
||||||
|
float progress = pf.temps_vida / pf.temps_max; // 0.0 → 1.0
|
||||||
|
pf.brightness = 1.0F - progress; // 1.0 → 0.0
|
||||||
|
|
||||||
|
// 4. Desactivar cuando acaba el time
|
||||||
|
if (pf.temps_vida >= pf.temps_max) {
|
||||||
|
pf.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FloatingScoreManager::draw() {
|
||||||
|
for (const auto& pf : pool_) {
|
||||||
|
if (!pf.active) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderizar centrat con brightness (fade)
|
||||||
|
constexpr float scale = Defaults::FloatingScore::SCALE;
|
||||||
|
constexpr float spacing = Defaults::FloatingScore::SPACING;
|
||||||
|
|
||||||
|
text_.renderCentered(pf.text, pf.position, scale, spacing, pf.brightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FloatingScoreManager::reset() {
|
||||||
|
for (auto& pf : pool_) {
|
||||||
|
pf.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int FloatingScoreManager::getActiveCount() const {
|
||||||
|
int count = 0;
|
||||||
|
for (const auto& pf : pool_) {
|
||||||
|
if (pf.active) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingScore* FloatingScoreManager::findFreeSlot() {
|
||||||
|
for (auto& pf : pool_) {
|
||||||
|
if (!pf.active) {
|
||||||
|
return &pf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr; // Pool ple
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Effects
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// floating_score_manager.hpp - Gestor de números de puntuación flotantes
|
||||||
|
// © 2025 Port a C++20 con SDL3
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "core/defaults.hpp"
|
||||||
|
#include "core/graphics/vector_text.hpp"
|
||||||
|
#include "core/types.hpp"
|
||||||
|
#include "floating_score.hpp"
|
||||||
|
|
||||||
|
namespace Effects {
|
||||||
|
|
||||||
|
// Gestor de números de puntuación flotantes
|
||||||
|
// Manté un pool de FloatingScore i gestiona el seu cicle de vida
|
||||||
|
class FloatingScoreManager {
|
||||||
|
public:
|
||||||
|
explicit FloatingScoreManager(Rendering::Renderer* renderer);
|
||||||
|
|
||||||
|
// Crear número flotante
|
||||||
|
// - points: value numèric (100, 150, 200)
|
||||||
|
// - position: on apareix (normalment centro de enemy destruït)
|
||||||
|
void crear(int points, const Vec2& position);
|
||||||
|
|
||||||
|
// Actualitzar todos los números active
|
||||||
|
void update(float delta_time);
|
||||||
|
|
||||||
|
// Dibuixar todos los números active
|
||||||
|
void draw();
|
||||||
|
|
||||||
|
// Reiniciar tots (clear)
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
// Obtenir número active (debug)
|
||||||
|
[[nodiscard]] int getActiveCount() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Graphics::VectorText text_; // Sistema de text vectorial
|
||||||
|
|
||||||
|
// Pool de números flotantes (màxim concurrent)
|
||||||
|
// Màxim 15 enemigos simultanis = màxim 15 números
|
||||||
|
static constexpr int MAX_PUNTUACIONS =
|
||||||
|
Defaults::FloatingScore::MAX_CONCURRENT;
|
||||||
|
std::array<FloatingScore, MAX_PUNTUACIONS> pool_;
|
||||||
|
|
||||||
|
// Trobar primer slot inactiu
|
||||||
|
FloatingScore* findFreeSlot();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Effects
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user