72 Commits

Author SHA1 Message Date
6aa4a1227e ordenació per buckets 2026-03-12 22:55:33 +01:00
02fdcd4113 Ara --custom-balls N --skip-benchmark (o --max-balls N) inclou el custom escenari en la rotació automàtica de DEMO/DEMO_LITE 2026-03-12 22:35:10 +01:00
7db9e46f95 soport per a pantalles de poca resolució en mode finestra 2026-03-12 09:05:57 +01:00
ff6aaef7c6 parametres per saltarse el benchmark i per limitar el maxim de pilotes en els modes automatics 2026-03-12 08:56:59 +01:00
8e2e681b2c el benchmark es fa ara amb una figura i no amb el mode de fisica 2026-03-12 08:48:14 +01:00
f06123feff eliminat un binari que s'havia colat en el repo 2026-03-12 08:31:04 +01:00
cbe6dc9744 varies millores en el hud de debug 2026-03-11 23:27:02 +01:00
dfbd8a430b afegit escenari personalitzat per parametre 2026-03-11 22:44:17 +01:00
ea27a771ab benchmark inicial per a determinar modes de baix rendiment
ajustats escenaris maxims i minims per als diferents modes automatics
2026-03-11 20:30:32 +01:00
09303537a4 advertencia de modo kiosko al intentar cambiar els modes de finestra 2026-03-11 20:07:20 +01:00
df17e85a8a afegides dll al release de windows 2026-03-11 19:30:18 +01:00
ce5c4681b8 afegit mode kiosko 2026-03-11 19:14:22 +01:00
b79f1c3424 afegida cache a resource manager per evitar accessos a disc 2026-03-11 18:59:56 +01:00
a65544e8b3 fix: png_shape ja carrega de resources.pack
Amb tots els fixos anteriors, el app de macos ja funciona correctament
2026-03-08 22:36:10 +01:00
b9264c96a1 fix: no carregava correctament data/shapes/jailgames.png si s'executava desde fora del directori de l'executable 2026-03-08 22:24:41 +01:00
fa285519b2 Fix: Corregir creación de DMG eliminando prefijo rw.* y conflictos
- Corregir nombre de archivos DMG usando TARGET_NAME en lugar de TARGET_FILE
- Agregar limpieza de volúmenes montados antes de crear DMG
- Eliminar creación manual de enlace Applications (create-dmg lo hace con --app-drop-link)
- Mejorar manejo de errores eliminando || true y agregando verificación de éxito
- Limpiar archivos temporales rw.*.dmg después de crear DMG

Esto resuelve el problema donde el DMG final tenía prefijo "rw.XXXXX" y no se
podía abrir correctamente debido a conflictos con volúmenes montados y doble
creación del enlace a Applications.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 11:03:31 +02:00
8285a8fafe Mejorar creación de DMG en macOS con create-dmg y posicionamiento de iconos
- Añadir instalación automática de create-dmg vía Homebrew si no está presente
- Reemplazar hdiutil por create-dmg para generar DMG con diseño profesional
- Configurar ventana de DMG: 720x300px con iconos de 96x96px
- Posicionar iconos centrados: Applications, .app, LICENSE, README.md
- Aplicar mejoras a ambas versiones: Intel y Apple Silicon
- Eliminar referencia obsoleta a tmp.dmg

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 10:31:25 +02:00
1a555e03f7 Fix: Mejorar escalado de fuentes para resoluciones altas y actualizar .gitignore
Problemas resueltos:
- Texto demasiado pequeño en resoluciones 2K/4K (1440p mostraba 36-41px)
- Archivos generados (resources.pack, *.zip) siendo versionados
- Carpetas temporales de empaquetado sin ignorar

Cambios realizados:

1. UIManager: Escalado más agresivo para resoluciones altas
   - Proporción mejorada: 1/40 → 1/26 (incremento ~35%)
   - Límite máximo: 36px → 72px
   - Resultados: 1080p→42px, 1440p→55px, 2160p→72px
   - Resoluciones bajas sin cambios (10-18px)

2. .gitignore: Excluir archivos generados y temporales
   - resources.pack (archivo empaquetado)
   - Archivos de distribución (*.zip, *.dmg, *.tar.gz, *.AppImage)
   - Carpetas temporales (vibe3_release/, Frameworks/)
   - Binarios de herramientas (tools/*.exe)

3. defines.hpp: Resolución por defecto actualizada
   - 640x360 (zoom 2x) → 1280x720 (zoom 1x)

Resultado:
- Texto significativamente más legible en pantallas grandes
- Repositorio limpio sin archivos generados

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 10:22:25 +02:00
af3ed6c2b3 Fix: Ajustar dimensionamiento de HelpOverlay para resoluciones bajas
Problemas resueltos:
- En 640x360, el overlay generaba textura enorme con letras grandes
- El cálculo de font size usaba dimensiones físicas (con zoom aplicado)
  en lugar de dimensiones lógicas (resolución interna)
- No había límite máximo de ancho para el overlay
- Padding fijo de 25px era excesivo en pantallas pequeñas

Cambios realizados:

1. UIManager: Usar dimensiones lógicas para calcular font size
   - Nuevo parámetro logical_width/logical_height en initialize()
   - calculateFontSize() ahora usa altura lógica sin zoom
   - Escalado híbrido: proporcional en extremos, escalonado en rango medio
   - Para 640x360: 10px (antes 18px con zoom 2x)
   - Para 640x480: 12px (antes 24px con zoom 2x)

2. HelpOverlay: Agregar límites máximos de dimensiones
   - Box width limitado al 95% del ancho físico
   - Box height limitado al 90% de la altura física
   - Padding dinámico: 25px para >=600px, escalado para menores
   - Para 360px altura: padding de 15px (antes 25px fijo)

3. Engine: Pasar dimensiones lógicas a UIManager
   - initialize() ahora recibe current_screen_width/height

Resultado:
- 640x360: Overlay compacto con fuente 10px que cabe en pantalla
- 640x480: Overlay con fuente 12px (tamaño apropiado)
- Tamaño de fuente consistente independiente del zoom de ventana

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 14:23:59 +02:00
a9d7b66e83 Refactorizar estilo del proyecto: .h → .hpp, #pragma once, includes desde raíz
Modernizar convenciones de código C++ aplicando las siguientes directivas:

## Cambios principales

**1. Renombrar headers (.h → .hpp)**
- 36 archivos renombrados a extensión .hpp (estándar C++)
- Mantenidos como .h: stb_image.h, stb_image_resize2.h (librerías C externas)

**2. Modernizar include guards (#ifndef → #pragma once)**
- resource_manager.hpp: #ifndef RESOURCE_MANAGER_H → #pragma once
- resource_pack.hpp: #ifndef RESOURCE_PACK_H → #pragma once
- spatial_grid.hpp: #ifndef SPATIAL_GRID_H → #pragma once

**3. Sistema de includes desde raíz del proyecto**
- CMakeLists.txt: añadido include_directories(${CMAKE_SOURCE_DIR}/source)
- Eliminadas rutas relativas (../) en todos los includes
- Includes ahora usan rutas absolutas desde source/

**Antes:**
```cpp
#include "../defines.h"
#include "../text/textrenderer.h"
```

**Ahora:**
```cpp
#include "defines.hpp"
#include "text/textrenderer.hpp"
```

## Archivos afectados

- 1 archivo CMakeLists.txt modificado
- 36 archivos renombrados (.h → .hpp)
- 32 archivos .cpp actualizados (includes)
- 36 archivos .hpp actualizados (includes + guards)
- 1 archivo tools/ actualizado

**Total: 70 archivos modificados**

## Verificación

 Proyecto compila sin errores
 Todas las rutas de includes correctas
 Include guards modernizados
 Librerías externas C mantienen extensión .h

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 13:49:58 +02:00
a929df6b73 Fix: Corregir inicialización de figuras en modo DEMO
Solucionar bug donde las pelotas aparecían en el centro sin formar
la figura geométrica al entrar en modo DEMO con SimulationMode::SHAPE.

## Problema
Al randomizar el estado en modo DEMO, si se elegía una figura:
1. Se configuraba el modo SHAPE
2. Se llamaba a changeScenario() que creaba pelotas en el centro
3. NO se llamaba a generateShape() para calcular los puntos de la figura
4. Resultado: pelotas amontonadas en el centro sin formar figura

## Solución
Reordenar operaciones en executeRandomizeOnDemoStart():
1. Decidir PRIMERO el modo (PHYSICS o SHAPE) antes de changeScenario
2. Si SHAPE: configurar figura manualmente sin generar puntos
3. Llamar a changeScenario() con el modo ya establecido
4. Después de changeScenario(), generar figura y activar atracción

Cambios adicionales:
- Arreglar warning de formato %zu en textrenderer.cpp (MinGW)
- Usar %lu con cast para size_t en logs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 13:22:03 +02:00
3f027d953c Eliminados warnings en textrenderer.cpp 2025-10-23 12:26:52 +02:00
1354ed82d2 Fix: Corregir carga de fuentes desde ResourceManager
Problema:
- Las fuentes TTF no se renderizaban (error "Text has zero width")
- Ocurría tanto al cargar desde resources.pack como desde disco
- El buffer de memoria se liberaba inmediatamente después de crear
  el SDL_IOStream, pero SDL_ttf necesita acceder a esos datos
  durante toda la vida de la fuente

Solución:
- Añadido campo font_data_buffer_ para mantener los datos en memoria
- Modificado init() y reinitialize() para NO liberar el buffer
  inmediatamente después de cargar la fuente
- Modificado cleanup() para liberar el buffer cuando se cierre la fuente
- Añadidos logs de debug para confirmar la carga desde ResourceManager

Archivos modificados:
- source/text/textrenderer.h: Añadido campo font_data_buffer_
- source/text/textrenderer.cpp: Correcciones en init(), reinitialize()
  y cleanup()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 12:15:54 +02:00
2fd6d99a61 Añadir sistema de Makefiles para herramienta de empaquetado de recursos
- Crear tools/Makefile con soporte multiplataforma (Windows/Linux/macOS)
- Añadir targets: pack_tool, resource_pack, test_pack, clean, help
- Mejorar Makefile raíz con target force_resource_pack
- Integrar regeneración automática de resources.pack en todos los releases
- Los releases siempre generan un resources.pack actualizado

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 09:48:57 +02:00
2fa1684f01 Refactorizar sistema de recursos: crear ResourceManager centralizado
- Crear ResourceManager singleton para gestión centralizada de recursos
- Separar lógica de ResourcePack de la clase Texture
- Adaptar TextRenderer para cargar fuentes TTF desde pack
- Adaptar LogoScaler para cargar imágenes PNG desde pack
- Actualizar main.cpp y engine.cpp para usar ResourceManager
- Regenerar resources.pack con fuentes y logos incluidos

Fixes:
- Resuelve error de carga de fuentes desde disco
- Resuelve error de carga de logos (can't fopen)
- Implementa fallback automático a disco si no existe pack
- Todas las clases ahora pueden cargar recursos desde pack

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 09:16:18 +02:00
41c76316ef Actualizar README.md 2025-10-19 17:39:35 +02:00
ce50a29019 Eliminado código DEPRECATED de ui_manager 2025-10-19 15:02:13 +02:00
f25cb96a91 Correciones en Makefile 2025-10-19 09:46:55 +02:00
d73781be9f Añadido vibe3.res 2025-10-19 09:29:26 +02:00
288e4813e8 fix: Recompilar pack_resources (binario antiguo generaba formato incorrecto)
**Problema crítico:**
Error al ejecutar aplicación: "Pack inválido (magic incorrecto)"

**Causa raíz:**
El binario tools/pack_resources era OBSOLETO (de proyecto anterior):
- Binario: "Coffee Crisis Arcade Edition" (5 oct, 68 KB)
- Magic number generado: "CCAE" (43 43 41 45)
- Magic esperado por código: "VBE3" (56 42 45 33)

**Verificación:**
```bash
$ strings tools/pack_resources | grep -i coffee
Coffee Crisis Arcade Edition  ← BINARIO ANTIGUO
CCAE_RESOURCES_2024
```

**Solución:**
1. Eliminado binario antiguo tools/pack_resources
2. Recompilado desde código actual (pack_resources.cpp)
3. Regenerado resources.pack con herramienta correcta

**Resultado:**
 Binario nuevo: "ViBe3 Physics - Resource Packer"
 Magic number correcto: 56 42 45 33 ("VBE3")
 Pack válido: 16 recursos, 3.7 MB
 Aplicación carga correctamente

**Nota:** El commit anterior (4d3ddec) añadió permisos +x al binario
antiguo, pero no lo recompiló. Este commit corrige el binario.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 09:19:39 +02:00
4d3ddec14e fix: Regenerar resources.pack automáticamente cuando cambian archivos en data/
**Problema crítico detectado:**
- resources.pack del 5 oct (14 días antiguo, 1.3 KB)
- Archivos en data/ modificados recientemente (logo.png: 18 oct)
- make *_release NO regeneraba resources.pack automáticamente
- Releases distribuían resources.pack obsoleto

**Causa:**
Makefile línea 108 solo dependía de $(PACK_TOOL):
```makefile
resources.pack: $(PACK_TOOL)  # ← Faltaban archivos de data/
```
Result: Make decía "up to date" aunque data/ tuviera cambios

**Solución:**
Añadido wildcard con todos los archivos de data/ como dependencias:
```makefile
DATA_FILES := $(shell find data -type f 2>/dev/null)
resources.pack: $(PACK_TOOL) $(DATA_FILES)  # ← Ahora detecta cambios
```

**Verificación:**
 Regeneración completa: 1.3 KB → 3.7 MB (16 recursos)
 Detección de cambios: touch data/logo.png → regenera automáticamente
 Eficiencia: Si no hay cambios → "up to date" (no regenera)
 tools/pack_resources: +x permisos de ejecución

**Impacto:**
Todas las recetas *_release ahora regeneran resources.pack si hay cambios

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 09:11:53 +02:00
ec1700b439 chore: Sincronizar versión mínima de macOS a 12.0 (Monterey)
**Problema:** Inconsistencia entre Makefile y Info.plist
- Info.plist declaraba 10.15 (Catalina)
- Makefile compilaba para 11.0 (Big Sur) en Apple Silicon
- Makefile compilaba para 10.15 en Intel

**Solución:** Unificar todo a macOS 12.0 (Monterey, 2021)
- Info.plist: LSMinimumSystemVersion 10.15 → 12.0
- Makefile arm64: -target arm64-apple-macos11 → macos12
- Makefile x86_64: -target x86_64-apple-macos10.15 → macos12

**Resultado:**
 Ambos archivos sincronizados (declaran 12.0)
 Elimina soporte para macOS 10.15 y 11.0
 Optimizaciones del compilador para Monterey+
 Warning cambiará a "building for macOS-12.0"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 09:07:16 +02:00
8aa2a112b4 fix: Enlazar SDL3_ttf en Makefile + corregir declaración SDL_Renderer
**Problema 1: Símbolos no definidos de SDL_ttf** (CRÍTICO)
- Error: "Undefined symbols: _TTF_Init, _TTF_OpenFont, etc."
- Causa: LDFLAGS solo incluía -lSDL3 (faltaba -lSDL3_ttf)
- Solución: Añadido -lSDL3_ttf a LDFLAGS para Unix/macOS (línea 81)
- Afecta: Linux, macOS y otros sistemas Unix

**Problema 2: Mismatch class/struct SDL_Renderer** (WARNING)
- Warning: "class 'SDL_Renderer' was previously declared as a struct"
- Causa: ui_manager.h:7 declaraba "class SDL_Renderer"
- SDL3 lo declara como "struct SDL_Renderer" (SDL_render.h:119)
- Solución: Cambiado class → struct en ui_manager.h:7
- Evita warnings y potenciales errores de linker en MSVC

**Resultado:**
 make macos_release completa exitosamente
 DMG creado: vibe3_physics-2025-10-19-macos-apple-silicon.dmg (17.9 MB)
 Sin errores de enlazado, solo warnings menores de versión macOS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 09:00:01 +02:00
dfebd8ece4 chore: Mover archivos .md a .claude/ + añadir DLLs/frameworks + fix Makefile Windows
**Cambios organizativos:**
- Archivos .md movidos de raíz a .claude/ (BOIDS_ROADMAP, CLAUDE, REFACTOR_*, ROADMAP, RULES)
- .claude/ ya está en .gitignore, archivos de sesión no versionados

**Nuevos recursos para release:**
- Añadido release/frameworks/SDL3_ttf.xcframework/ para macOS
- Añadidos release/SDL3.dll y release/SDL3_ttf.dll para Windows (forzado con -f)

**Configuración:**
- defines.h: APPLOGO_DISPLAY_INTERVAL 120→90 segundos (logo aparece más frecuente)
- defines.h: Ajustes de formato/indentación (sin cambios funcionales)

**Makefile windows_release:**
- Comandos Unix reemplazados por Windows CMD nativos:
  - rm -rf → if exist + rmdir /S /Q
  - mkdir -p → mkdir
  - cp -f → copy /Y
  - rm -f → if exist + del /Q
- Ahora funciona en Windows CMD sin necesitar Git Bash/MSYS2

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 08:51:56 +02:00
827d9f0e76 fix: Actualizar Makefile con detección automática de archivos y SDL3_ttf
**Problemas corregidos:**
1. APP_SOURCES ahora usa wildcards automáticos (como CMakeLists.txt)
   - Detecta automáticamente todos los .cpp en subdirectorios
   - Faltaban 16 archivos: themes, state, input, scene, shapes_mgr, boids_mgr, text, ui, app_logo, logo_scaler
   - Elimina mantenimiento manual de lista de archivos
2. macOS release ahora copia SDL3_ttf.xcframework correctamente
   - Añadido a Contents/Frameworks en el .app
   - Añadido a carpeta Frameworks temporal
3. Windows DLLs: línea mantenida para futuro uso

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 08:29:56 +02:00
df93d5080d docs: Reorganización completa del README.md
**Cambios principales:**

1. **Nueva estructura organizada**:
   - Secciones claramente separadas sin repeticiones
   - Información actualizada y completa
   - Navegación mejorada con separadores

2. **Nuevo contenido agregado**:
   - 🎮 Modos de Funcionamiento (AppMode y SimulationMode)
   - ⚙️ Parámetros de Línea de Comandos (completo)
   - 🐦 Modo BOIDS (documentado)
   - 🖼️ Sistema AppLogo (logo periódico)
   - Tecla O (PNG_SHAPE) agregada

3. **Secciones actualizadas**:
   - Controles: Reorganizados por categoría SIN repeticiones
   - Temas: 15 temas (9 estáticos + 6 dinámicos), 2 páginas
   - Figuras 3D: 9 figuras (incluye PNG_SHAPE)
   - Todas las teclas documentadas (D, Shift+D, K, C, O, P, etc.)

4. **Información eliminada**:
   - Duplicación de "Controles de Temas" (eliminada)
   - Sección de temas repetida (consolidada)
   - Información obsoleta o desactualizada

5. **Mejoras de formato**:
   - Tablas para mejor legibilidad
   - Ejemplos de código con sintaxis
   - Emojis para secciones (navegación visual)
   - Estructura del proyecto actualizada

**Cobertura completa:**
-  AppMode: SANDBOX, DEMO, DEMO_LITE, LOGO
-  SimulationMode: PHYSICS, SHAPE, BOIDS
-  15 temas con sistema de 2 páginas
-  9 figuras 3D incluyendo PNG_SHAPE
-  Parámetros CLI: -w, -h, -z, -f, -F, -m
-  Todas las teclas documentadas
-  Sistema AppLogo explicado
-  Modo BOIDS documentado

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 07:59:32 +02:00
0da4b45fef fix: Usar métodos de alto nivel para inicialización CLI de modos
**Problema:**
Cuando se iniciaba con `-m demo`, `-m demo-lite` o `-m logo`, se llamaba
a `setState()` directamente, que es un método de bajo nivel que solo
cambia el estado interno SIN ejecutar las acciones de configuración.

**Resultado del bug:**
- `-m demo`: NO aleatorizaba (tema default, primer escenario)
- `-m demo-lite`: NO aleatorizaba física
- `-m logo`: NO configuraba tema, PNG_SHAPE, ni pelotas pequeñas

**Arquitectura correcta:**
- `setState()` = Método primitivo bajo nivel (solo cambia estado)
- `toggleDemoMode()` = Método alto nivel (setState + randomize)
- `toggleDemoLiteMode()` = Método alto nivel (setState + randomize)
- `enterLogoMode()` = Método alto nivel (setState + configuración completa)

**Solución implementada:**
En lugar de llamar a setState() directamente, usar los métodos de
alto nivel que ejecutan las acciones de configuración:

```cpp
if (initial_mode == AppMode::DEMO) {
    state_manager_->toggleDemoMode(...);  // Entra a DEMO + randomiza
}
else if (initial_mode == AppMode::DEMO_LITE) {
    state_manager_->toggleDemoLiteMode(...);  // Entra a DEMO_LITE + randomiza
}
else if (initial_mode == AppMode::LOGO) {
    state_manager_->enterLogoMode(...);  // Entra a LOGO + configura todo
}
```

**Archivos modificados:**
- source/engine.cpp (líneas 249-263):
  - Reemplazado setState() por toggleDemoMode/toggleDemoLiteMode/enterLogoMode
  - Agregados comentarios explicativos

**Resultado esperado:**
-  `-m demo` → Aleatoriza todo como si pulsaras D
-  `-m demo-lite` → Aleatoriza física como si pulsaras Shift+D
-  `-m logo` → Configura tema/PNG_SHAPE/pelotas como si pulsaras K
-  Comportamiento consistente entre CLI y teclas
-  Arquitectura correcta: alto nivel para acciones, bajo nivel para estado

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 20:00:27 +02:00
db8acf0331 clean: Eliminar logging debug + fix: Centro fijo para animación ZOOM
**1. Eliminado logging de debug del FADE_OUT:**
- Removido log de timer/delta_time/progress (FADE_OUT inicial)
- Removido log de alpha1/alpha2
- Removido log de animaciones (ZOOM, ELASTIC, SPIRAL, BOUNCE)
- Removido log de completado de FADE_OUT
- Consola limpia en modo producción

**2. Fix centro del logo en animación ZOOM_ONLY:**

**Problema:**
- Centro del logo se calculaba basándose en width/height escalados
- Cuando scale cambiaba (1.2 → 1.0), corner_x/corner_y se movían
- Resultado: logo se desplazaba lateralmente durante zoom

**Solución:**
- Calcular esquina BASE (sin escala): corner_x_base, corner_y_base
- Calcular centro FIJO basándose en base_width/base_height
- Calcular width/height escalados DESPUÉS (solo para vértices)
- Resultado: centro permanece fijo, zoom crece/decrece alrededor del centro

**Archivos modificados:**
- source/app_logo.cpp:
  - Líneas 343-347: Eliminado log FADE_OUT inicial
  - Línea 347: Eliminado log completado
  - Líneas 365-366: Eliminado log alphas
  - Líneas 381-383: Eliminado log ZOOM
  - Líneas 396-398: Eliminado log ELASTIC
  - Líneas 414-417: Eliminado log SPIRAL
  - Líneas 444-446: Eliminado log BOUNCE
  - Líneas 609-625: Reordenado cálculo de centro (FIJO) y tamaño (ESCALADO)

**Resultado esperado:**
- Sin spam en consola
- Animación ZOOM perfectamente centrada en esquina inferior derecha
- Logo crece/decrece sin desplazamiento lateral

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 19:40:53 +02:00
5a35cc1abf fix: Aplicar alpha del logo a través de vértices en lugar de textura
**Problema:**
- SDL_SetTextureAlphaMod() no funciona correctamente con SDL_RenderGeometry()
- El alpha de los vértices (hardcodeado a 1.0) overrideaba el alpha de textura
- Resultado: fade invisible o instantáneo a pesar de valores correctos

**Solución:**
- Eliminar SDL_SetTextureAlphaMod()
- Convertir alpha de 0-255 a 0.0-1.0 (alpha_normalized)
- Aplicar alpha_normalized directamente al canal alpha de los 4 vértices
- Ahora SDL_RenderGeometry respeta el fade correctamente

**Archivos modificados:**
- source/app_logo.cpp:
  - Línea 630: Crear alpha_normalized en lugar de SetTextureAlphaMod
  - Líneas 669, 680, 691, 702: Aplicar alpha_normalized a vértices

**Resultado esperado:**
- Fade visible y suave durante 2 segundos completos
- Logo 2 con retraso de 0.25s como esperado
- Sincronización perfecta entre animación y fade

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 19:31:28 +02:00
d30a4fd440 debug: Agregar logging detallado a FADE_OUT de AppLogo
- Log de timer_, delta_time, progress en cada frame
- Log de alpha1/alpha2 calculados
- Log de valores de animación por tipo (ZOOM/ELASTIC/SPIRAL/BOUNCE)
- Log de ease_t1 en ROTATE_SPIRAL para diagnosticar desincronización
- Log cuando FADE_OUT se completa

Propósito: Diagnosticar por qué el fade parece instantáneo
y desincronizado con la animación (serie en lugar de paralelo).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 19:26:14 +02:00
97c0683f6e fix: Restaurar fade lineal del alpha + renombrar constante
Restaura fade lineal para el alpha de los logos, eliminando el easing
que hacía la transición casi imperceptible. Renombra constante para
mayor claridad sobre lo que controla.

Problema identificado:
- El alpha usaba easeInOutQuad, acelerando la transición en el medio
- Con 2 segundos, el easing hacía que el logo pareciera aparecer/
  desaparecer instantáneamente sin fade visible
- El usuario reportó "el logo termina y desaparece directamente"

Solución implementada:

1. **Fade lineal restaurado**:
   - FADE_IN: alpha aumenta linealmente 0→255
   - FADE_OUT: alpha disminuye linealmente 255→0
   - Progreso visible y constante durante toda la duración

2. **Constante renombrada**:
   - `APPLOGO_FADE_DURATION` → `APPLOGO_ANIMATION_DURATION`
   - Nombre más claro: controla duración de toda la animación
   - Actualizado valor a 2.0 segundos (configurable por usuario)

3. **Animaciones mantienen easing**:
   - Zoom, rotación, squash, etc. siguen usando sus easings
   - Solo el alpha es lineal para fade visible

Confirmaciones:
 Sistema time-based: usa delta_time correctamente
 Blend mode configurado: SDL_BLENDMODE_BLEND en todas las texturas
 Alpha se aplica: SDL_SetTextureAlphaMod en renderizado

Resultado con APPLOGO_ANIMATION_DURATION = 2.0s:
- t=0.0s → Alpha=0 (invisible)
- t=0.5s → Alpha=64 (25% visible)
- t=1.0s → Alpha=127 (50% visible)
- t=1.5s → Alpha=191 (75% visible)
- t=2.0s → Alpha=255 (100% visible)

Nota: El logo solo se muestra en modos DEMO/DEMO_LITE/LOGO,
no en SANDBOX. Para probar: ./vibe3_physics --mode demo

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 19:16:33 +02:00
c3d24cc07d feat: Argumentos CLI para establecer AppMode inicial
Permite arrancar directamente en modo DEMO, DEMO_LITE, LOGO o SANDBOX
mediante argumentos de línea de comandos, eliminando necesidad de
cambiar manualmente el modo después del arranque.

Nuevos argumentos:
- `-m, --mode <mode>` - Establece modo inicial
  - `sandbox` - Control manual (default)
  - `demo` - Auto-play completo (figuras + temas + colisiones)
  - `demo-lite` - Auto-play simple (solo física/figuras)
  - `logo` - Modo logo (easter egg con convergencia)

Ejemplos de uso:
```bash
# Arrancar en modo DEMO
./vibe3_physics --mode demo
./vibe3_physics -m demo

# Arrancar en DEMO_LITE (solo física)
./vibe3_physics -m demo-lite

# Arrancar directo en LOGO
./vibe3_physics --mode logo

# Combinar con otros argumentos
./vibe3_physics -w 1920 -h 1080 --mode demo
./vibe3_physics -F -m demo-lite  # Fullscreen + DEMO_LITE
```

Implementación:
1. main.cpp: Parsing de argumento --mode con validación
2. engine.h: Nuevo parámetro `initial_mode` en initialize()
3. engine.cpp: Aplicación del modo vía StateManager::setState()

Si no se especifica --mode, se usa SANDBOX (comportamiento actual).
El modo se aplica después de inicializar StateManager, garantizando
que todos los componentes estén listos antes del cambio de estado.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 19:00:16 +02:00
7609b9ef5c feat: Animaciones de logos sincronizadas con retraso + easing en alpha
Implementa sistema de sincronización elegante entre logos con efecto de
"seguimiento" y fade suavizado para eliminar desincronización visual.

Cambios principales:

1. **Animación sincronizada**: Ambos logos usan la MISMA animación
   - Eliminadas 4 variables independientes (logo1/logo2 × entry/exit)
   - Una sola variable `current_animation_` compartida
   - Misma animación para entrada y salida (simetría)

2. **Retraso de Logo 2**: 0.25 segundos detrás de Logo 1
   - Logo 1 empieza en t=0.00s
   - Logo 2 empieza en t=0.25s
   - Efecto visual de "eco" o "seguimiento"

3. **Alpha independiente con retraso**:
   - `logo1_alpha_` y `logo2_alpha_` separados
   - Logo 2 aparece/desaparece más tarde visualmente

4. **Easing en alpha** (NUEVO):
   - Aplicado `easeInOutQuad()` al fade de alpha
   - Elimina problema de "logo deformado esperando a desvanecerse"
   - Sincronización visual perfecta entre animación y fade
   - Curva suave: lento al inicio, rápido en medio, lento al final

Comportamiento resultante:

FADE_IN:
- t=0.00s: Logo 1 empieza (alpha con easing)
- t=0.25s: Logo 2 empieza (alpha con easing + retraso)
- t=0.50s: Logo 1 completamente visible
- t=0.75s: Logo 2 completamente visible

FADE_OUT:
- t=0.00s: Logo 1 empieza a desaparecer (misma animación)
- t=0.25s: Logo 2 empieza a desaparecer
- t=0.50s: Logo 1 completamente invisible
- t=0.75s: Logo 2 completamente invisible

Archivos modificados:
- source/defines.h: +APPLOGO_LOGO2_DELAY
- source/app_logo.h: Reestructuración de variables de animación/alpha
- source/app_logo.cpp: Implementación de retraso + easing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 12:33:09 +02:00
ad3f5a00e4 feat: Sistema de pre-escalado de logos con stb_image_resize2
Implementa pre-escalado de alta calidad para eliminar artefactos de
escalado dinámico de SDL y mejorar la nitidez visual de los logos.

Características:
- 4 texturas pre-escaladas (2 logos × 2 resoluciones: base + nativa)
- Detección automática de resolución nativa del monitor
- Switching dinámico entre texturas al cambiar resolución (F4)
- Renderizado 1:1 sin escalado adicional (máxima calidad)
- Algoritmo Mitchell en espacio sRGB (balance calidad/velocidad)
- Todo en RAM, sin archivos temporales

Archivos nuevos:
- source/external/stb_image_resize2.h: Biblioteca de escalado stb
- source/logo_scaler.h/cpp: Clase helper para pre-escalado

Cambios en AppLogo:
- Reemplazadas shared_ptr<Texture> por SDL_Texture* raw pointers
- initialize(): Pre-escala logos a 2 resoluciones al inicio
- updateScreenSize(): Cambia entre texturas según resolución
- render(): Simplificado, siempre usa renderWithGeometry()
- ~AppLogo(): Libera 4 texturas SDL manualmente

El sistema detecta la resolución nativa al inicio y crea versiones
optimizadas. Al presionar F4, cambia automáticamente a la textura
nativa para calidad perfecta en fullscreen.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 10:36:26 +02:00
c91cb1ca56 feat: Sistema dual de logos con animaciones independientes + ajuste de tamaño/posición
Implementación de sistema de 2 logos superpuestos con animaciones completamente independientes:

**Nuevas características:**
- Dos logos superpuestos (logo1.png + logo2.png) con animaciones independientes
- 4 tipos de animación: ZOOM_ONLY, ELASTIC_STICK, ROTATE_SPIRAL, BOUNCE_SQUASH
- Aleatorización independiente para entrada y salida de cada logo
- 256 combinaciones posibles (4×4 entrada × 4×4 salida)

**Ajuste de tamaño y posición:**
- Nueva constante APPLOGO_HEIGHT_PERCENT (40%) - altura del logo respecto a pantalla
- Nueva constante APPLOGO_PADDING_PERCENT (10%) - padding desde esquina inferior-derecha
- Logo anclado a esquina en lugar de centrado en cuadrante
- Valores fácilmente ajustables mediante constantes en defines.h

**Cambios técnicos:**
- Variables duplicadas para logo1 y logo2 (scale, squash, stretch, rotation)
- Variables compartidas para sincronización (state, timer, alpha)
- renderWithGeometry() acepta parámetro logo_index (1 o 2)
- Logo1 renderizado primero (fondo), Logo2 encima (overlay)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 10:01:32 +02:00
8d608357b4 feat: Animación elástica tipo pegatina para AppLogo
Implementación de deformación elástica con vértices para el logo:

FADE IN (0.5s):
- Scale: 120% → 100% con easing elástico (bounce)
- Squash Y: 0.6 → 1.0 con easing back (aplastamiento)
- Stretch X: compensación automática
- Efecto: logo se "pega" aplastándose y rebotando

FADE OUT (0.5s):
- Scale: 100% → 120% (aceleración cuadrática)
- Squash Y: 1.0 → 1.3 (estiramiento vertical)
- Stretch X: 1.0 → 0.8 (compresión horizontal)
- Rotación: 0° → ~5.7° (torsión sutil)
- Efecto: logo se "despega" estirándose y girando

Características técnicas:
- Enum AppLogoAnimationType (ZOOM_ONLY / ELASTIC_STICK)
- Renderizado con SDL_RenderGeometry para deformaciones
- Funciones de easing: easeOutElastic() y easeOutBack()
- Transformación de vértices con rotación y escala 2D
- Actualmente fijo en ELASTIC_STICK para testing

Limpieza adicional:
- Eliminado dbgtxt.h (no utilizado)
- Removidos SDL_Log de debug en HelpOverlay
- Comentada variable no usada en ShapeManager

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 01:46:56 +02:00
f73a133756 feat: Sistema de logo periódico con fade in/out
- Nuevo sistema AppLogo que muestra el logo cada 20 segundos por 5 segundos
- Fade in/out suave de 0.5 segundos con alpha blending
- Máquina de estados: HIDDEN → FADE_IN → VISIBLE → FADE_OUT
- Logo posicionado en cuadrante inferior derecho (1/4 de pantalla)
- Añadido método setAlpha() a Texture para control de transparencia
- Habilitado SDL_BLENDMODE_BLEND en todas las texturas
- Filtrado LINEAR para suavizado del logo escalado
- Desactivado automáticamente en modo SANDBOX

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 01:31:29 +02:00
de23327861 fix: Mantener gravedad OFF al cambiar escenario en modo BOIDS
Problema:
- Al cambiar de escenario (teclas 1-8) en modo BOIDS, la gravedad
  se reseteaba a 720 en lugar de mantenerse en 0
- SceneManager::changeScenario() reinicializa bolas con gravedad default
- Esto rompía el invariante: "modo BOIDS = gravedad OFF siempre"

Solución:
- Añadido check en Engine::changeScenario() para forzar gravedad OFF
  después del cambio de escenario si estamos en modo BOIDS
- Mantiene consistencia con el comportamiento de SHAPE mode

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 00:14:08 +02:00
f6402084eb feat: Bordes como obstáculos + Variables BOIDS ajustables + Fix tecla G
**1. Bordes como obstáculos (no más wrapping):**
- Implementada fuerza de repulsión cuando boids se acercan a bordes
- Nueva regla: Boundary Avoidance (evitar bordes)
- Fuerza proporcional a cercanía (0% en margen, 100% en colisión)
- Constantes: BOID_BOUNDARY_MARGIN (50px), BOID_BOUNDARY_WEIGHT (7200 px/s²)

**2. Variables ajustables en runtime:**
- Añadidas 11 variables miembro en BoidManager (inicializadas con defines.h)
- Permite modificar comportamiento sin recompilar
- Variables: radios (separation/alignment/cohesion), weights, speeds, boundary
- Base para futuras herramientas de debug/tweaking visual

**3. Fix tecla G (BOIDS → PHYSICS):**
- Corregido: toggleBoidsMode() ahora acepta parámetro force_gravity_on
- handleGravityToggle() pasa explícitamente false para preservar inercia
- Transición BOIDS→PHYSICS ahora mantiene gravedad OFF correctamente

**Implementación:**
- defines.h: +2 constantes (BOUNDARY_MARGIN, BOUNDARY_WEIGHT)
- boid_manager.h: +11 variables miembro ajustables
- boid_manager.cpp:
  - Constructor inicializa variables
  - Todas las funciones usan variables en lugar de constantes
  - applyBoundaries() completamente reescrito (repulsión vs wrapping)
- engine.h/cpp: toggleBoidsMode() con parámetro opcional

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 20:09:33 +02:00
9909d4c12d feat: Convertir BOIDS a sistema time-based (independiente de framerate)
- Conversión completa de físicas BOIDS de frame-based a time-based
- Velocidades: ×60 (px/frame → px/s)
- Aceleraciones (Separation, Cohesion): ×3600 (px/frame² → px/s²)
- Steering proporcional (Alignment): ×60
- Límites de velocidad: ×60

Constantes actualizadas en defines.h:
- BOID_SEPARATION_WEIGHT: 1.5 → 5400.0 (aceleración)
- BOID_COHESION_WEIGHT: 0.001 → 3.6 (aceleración)
- BOID_ALIGNMENT_WEIGHT: 1.0 → 60.0 (steering)
- BOID_MAX_SPEED: 2.5 → 150.0 px/s
- BOID_MIN_SPEED: 0.3 → 18.0 px/s
- BOID_MAX_FORCE: 0.05 → 3.0 px/s

Física ahora consistente en 60Hz, 144Hz, 240Hz screens.
Transiciones BOIDS↔PHYSICS preservan velocidad correctamente.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 20:05:49 +02:00
a929346463 fix: SHAPE mode - regenerar figuras al cambiar tamaño con F4
Al entrar o salir del modo fullscreen real (F4), el área de juego cambia
de tamaño. Si estábamos en modo SHAPE, las bolas aparecían centradas pero
sin formar la figura.

PROBLEMA RAÍZ:
- changeScenario() recrea las bolas en nuevas posiciones
- NO se regeneraban los targets de la figura (puntos 3D)
- NO se reactivaba la atracción física hacia los targets
- Resultado: bolas centradas sin formar figura

SOLUCIÓN:
Después de changeScenario() en ambas transiciones de F4:
1. Llamar a generateShape() (Engine, no ShapeManager)
2. Reactivar enableShapeAttraction(true) en todas las bolas

Esto sigue el mismo patrón usado en Engine::changeScenario() (línea 515).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 09:34:20 +02:00
c4075f68db fix: Debug HUD usa viewport físico en F3 (coordenadas reales)
Problema:
- En modo F3 (letterbox/integer scale), el debug HUD se pintaba
  fuera del área de juego (en las barras negras laterales)
- SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay
  presentación lógica activa
- printAbsolute() trabaja en píxeles FÍSICOS
- Mismatch de coordenadas causaba alineación derecha incorrecta

Solución:
- Nuevo helper getPhysicalViewport() que:
  1. Guarda estado de presentación lógica
  2. Deshabilita presentación lógica temporalmente
  3. Obtiene viewport en coordenadas físicas
  4. Restaura presentación lógica
  5. Retorna viewport físico

- UIManager::renderDebugHUD() ahora usa physical_viewport.w
  para cálculo de alineación derecha (9 referencias actualizadas)

Resultado:
- Debug HUD alineado correctamente en F3 letterbox
- Debug HUD alineado correctamente en F4 integer scale
- Modo ventana sigue funcionando correctamente

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 08:08:04 +02:00
399650f8da fix: Notifier usa viewport físico en F3 (coordenadas reales)
Problema:
- En modo F3 (letterbox/integer scale), las notificaciones se pintaban
  fuera del área de juego (en las barras negras)
- SDL_GetRenderViewport() devuelve coordenadas LÓGICAS cuando hay
  presentación lógica activa
- printAbsolute() trabaja en píxeles FÍSICOS
- Mismatch de coordenadas causaba centrado incorrecto

Solución:
- Nuevo helper getPhysicalViewport() que:
  1. Guarda estado de presentación lógica
  2. Deshabilita presentación lógica temporalmente
  3. Obtiene viewport en coordenadas físicas
  4. Restaura presentación lógica
  5. Retorna viewport físico

- Notifier::render() ahora usa physical_viewport.w para centrado

Resultado:
- Notificaciones centradas correctamente en F3 letterbox
- Notificaciones centradas correctamente en F4 integer scale
- Modo ventana sigue funcionando correctamente

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 08:08:01 +02:00
9b8afa1219 fix: HUD de debug alineado correcto en viewport (F3 letterbox)
Problema:
- Columna derecha del HUD (FPS, info de pelota) se alineaba usando dimensión física
- En modo letterbox (F3 INTEGER/LETTERBOX) aparecía en barras negras o fuera de pantalla
- Mismo issue que tenían Notifier y Help Overlay

Causa:
- ui_manager.cpp:renderDebugHUD() usaba `physical_window_width_` para alinear a la derecha
- En F3 letterbox: viewport visible < ventana física
- Ejemplo: ventana 1920px, viewport 1280px con offset 320px
- Cálculo: fps_x = 1920 - width - margin
- printAbsolute() aplicaba offset: 1920 - width + 320 = fuera de pantalla
- Resultado: texto del HUD invisible o en barras negras

Solución:
- Obtener viewport con SDL_GetRenderViewport() al inicio de renderDebugHUD()
- Reemplazar TODAS las referencias a `physical_window_width_` con `viewport.w`
- Coordenadas relativas al viewport, printAbsolute() aplica offset automáticamente

Código modificado:
- ui_manager.cpp:208-211 - Obtención de viewport
- ui_manager.cpp:315, 326, 333, 340, 347, 353, 360, 366, 375 - Alineación con viewport.w

Líneas afectadas (9 totales):
- FPS counter
- Posición X/Y primera pelota
- Velocidad X/Y
- Fuerza de gravedad
- Estado superficie
- Coeficiente de rebote (loss)
- Dirección de gravedad
- Convergencia (LOGO mode)

Resultado:
 HUD de debug alineado correctamente al borde derecho del viewport
 Columna derecha visible dentro del área de juego
 No aparece en barras negras en F3
 Funciona correctamente en ventana, F3 y F4

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 07:47:50 +02:00
5b674c8ea6 fix: Notifier centrado correcto en viewport (F3 letterbox)
Problema:
- Notificaciones se centraban usando dimensión física de ventana
- En modo letterbox (F3 INTEGER/LETTERBOX) aparecían en barras negras
- Mismo issue que tenía Help Overlay

Causa:
- notifier.cpp:165 usaba `window_width_` para calcular centrado
- En F3 letterbox: viewport visible < ventana física
- Ejemplo: ventana 1920px, viewport 1280px con offset 320px
- Resultado: notificación descentrada fuera del área visible

Solución:
- Obtener viewport con SDL_GetRenderViewport() antes de calcular posición
- Usar `viewport.w` en lugar de `window_width_` para centrado
- Coordenadas relativas al viewport, printAbsolute() aplica offset automáticamente

Código modificado:
- notifier.cpp:162-170 - Centrado usando viewport dimensions

Resultado:
 Notificaciones centradas en área visible (viewport)
 No aparecen en barras negras en F3
 Funciona correctamente en ventana, F3 y F4

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 07:47:10 +02:00
7fac103c51 fix: Help Overlay - ambas columnas respetan padding inferior
Problema:
- Solo columna 0 verificaba si cabía más texto antes de escribir
- Columna 1 (derecha) escribía fuera del overlay si no cabía
- En ventanas de 600px altura, columna 1 se desbordaba

Solución:
- Eliminada restricción `&& current_column == 0` del check de padding
- Ahora AMBAS columnas verifican si caben antes de escribir
- Si columna 1 está llena: omitir texto restante (continue)
- Si columna 0 está llena: cambiar a columna 1

Comportamiento preferido por el usuario:
"prefiero que 'falte texto' a que el texto se escriba por fuera del overlay"

Resultado:
 Columna 0 cambia a columna 1 cuando se llena
 Columna 1 omite texto que no cabe
 Overlay nunca muestra texto fuera de sus límites
 Funciona correctamente en ventanas pequeñas (600px)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 07:33:17 +02:00
bcceb94c9e fix: Help Overlay centrado correcto en modo F3 letterbox
Problemas resueltos:
- En modo F3 (letterbox), el overlay se centraba en pantalla física
  en lugar de en el viewport visible, quedando desplazado
- Al salir de F3 a ventana, el overlay seguía roto
- Padding inferior no se respetaba correctamente

Cambios implementados:
1. render() ahora usa SDL_GetRenderViewport() para obtener área visible
2. Centrado calculado dentro del viewport (con offset de barras negras)
3. toggleFullscreen() restaura tamaño de ventana al salir de F3
4. Padding check movido ANTES de escribir línea (>= en lugar de >)
5. Debug logging añadido para diagnóstico de dimensiones

Resultado:
 Overlay centrado correctamente en F3 letterbox
 Overlay se regenera correctamente al salir de F3
 Padding inferior respetado en columna 0

Pendiente:
- Columna 2 (índice 1) todavía no respeta padding inferior
- Verificar que F4 (real fullscreen) siga funcionando correctamente

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 07:30:31 +02:00
1b3d32ba84 fix: Help Overlay - fullscreen resize roto y padding inferior
Correcciones críticas para overlay en fullscreen y padding:

1. Fullscreen/resize roto CORREGIDO:
   - Problema: orden incorrecto de actualizaciones causaba mezcla de
     dimensiones antiguas (800x600) con font nuevo (24px)
   - Solución: nuevo método updateAll() que actualiza font Y dimensiones
     de forma atómica
   - Flujo correcto: dimensiones físicas → font → recalcular box
   - Antes: overlay gigante y descuadrado al cambiar fullscreen
   - Ahora: overlay se reposiciona y escala correctamente

2. Padding inferior inexistente CORREGIDO:
   - Problema: calculateTextDimensions() usaba num_lines/2 asumiendo
     división perfecta entre columnas
   - Problema 2: rebuildCachedTexture() no verificaba límite inferior
     en columna 1
   - Solución: contar líneas REALES en cada columna y usar el máximo
   - Fórmula correcta: line_height*2 + max_column_lines*line_height + padding*2
   - Ahora: padding inferior respetado siempre

3. Implementación técnica:
   - HelpOverlay::updateAll(font, width, height) nuevo método unificado
   - UIManager llama updateAll() en lugar de reinitializeFontSize() +
     updatePhysicalWindowSize() separadamente
   - Elimina race condition entre actualización de font y dimensiones

Resultado:
- F3/F4 (fullscreen) funciona correctamente
- Resize ventana (F1/F2) funciona correctamente
- Padding inferior respetado en ambas columnas
- Sin overlays gigantes o descuadrados

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 21:32:32 +02:00
7c0a60f140 fix: Help Overlay - corregir solapamiento de columnas y layout
Tres correcciones importantes para el Help Overlay:

1. Solapamiento de columnas corregido:
   - Añadidos column1_width_ y column2_width_ para anchos reales
   - calculateTextDimensions() ahora incluye encabezados en cálculo
   - rebuildCachedTexture() usa anchos reales de columnas
   - Columna 2 empieza en padding + column1_width_ + padding
   - Elimina cálculo erróneo column_width = (box_width_ - padding*3)/2

2. Layout en alta resolución corregido:
   - Eliminado ancho mínimo forzado del 90% de dimensión menor
   - box_width_ ahora usa directamente text_width (justo lo necesario)
   - Antes: 1920x1080 → min 972px aunque contenido necesite 600px
   - Ahora: box ajustado al contenido sin espacio vacío extra

3. Fullscreen/resize corregido:
   - reinitializeFontSize() ya NO llama a calculateBoxDimensions()
   - Evita recalcular con physical_width_ y physical_height_ antiguos
   - Confía en updatePhysicalWindowSize() que se llama después
   - Antes: textura cacheada creada con dimensiones incorrectas
   - Ahora: textura siempre creada con dimensiones correctas

Resultado:
- Columnas no se montan entre sí
- Box ajustado al contenido sin espacio vacío derecha
- Cambios fullscreen/ventana funcionan correctamente
- Overlay se recalcula apropiadamente en todos los casos

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 21:22:12 +02:00
250b1a640d feat: Dynamic text scaling based on physical window size
Sistema de escalado dinámico de texto con 3 tamaños según área de ventana:

1. TextRenderer improvements:
   - Añadido reinitialize(int new_font_size) para cambiar tamaño en runtime
   - Almacena font_path_ para permitir recarga de fuente
   - Cierra fuente anterior y abre nueva con diferente tamaño
   - Verifica si tamaño es igual antes de reinicializar (optimización)

2. UIManager - Font size calculation:
   - Añadido calculateFontSize() con stepped scaling por área:
     * SMALL (< 800x600): 14px
     * MEDIUM (800x600 a 1920x1080): 18px
     * LARGE (> 1920x1080): 24px
   - Tracking de current_font_size_ para detectar cambios
   - Inicialización con tamaño dinámico en initialize()
   - Reinitialización automática en updatePhysicalWindowSize()

3. UIManager - Propagation:
   - Reinitializa 3 TextRenderer instances cuando cambia tamaño
   - Propaga nuevo tamaño a HelpOverlay
   - Detecta cambios solo cuando área cruza umbrales (eficiencia)

4. HelpOverlay integration:
   - Acepta font_size como parámetro en initialize()
   - Añadido reinitializeFontSize() para cambios dinámicos
   - Recalcula dimensiones del box cuando cambia fuente
   - Marca textura para rebuild completo tras cambio

Resultado:
- Ventanas pequeñas: texto 14px (más espacio para contenido)
- Ventanas medianas: texto 18px (tamaño original, óptimo)
- Ventanas grandes: texto 24px (mejor legibilidad)
- Cambios automáticos al redimensionar ventana (F1/F2/F3/F4)
- Sin impacto en performance (solo recalcula al cruzar umbrales)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 17:41:19 +02:00
795fa33e50 feat: Help Overlay - dynamic width + render-to-texture caching
Mejoras de rendimiento y usabilidad del Help Overlay:

1. Anchura dinámica basada en contenido:
   - Ya no es siempre cuadrado (box_size_)
   - Calcula ancho real según texto más largo por columna
   - Mantiene mínimo del 90% dimensión menor como antes
   - Nueva función calculateTextDimensions()

2. Render-to-texture caching para optimización:
   - Renderiza overlay completo a textura una sola vez
   - Detecta cambios de color con umbral (threshold 5/255)
   - Soporta temas dinámicos con LERP sin rebuild constante
   - Regenera solo cuando colores cambian o ventana redimensiona

3. Impacto en performance:
   - Antes: 1200 FPS → 200 FPS con overlay activo
   - Después: 1200 FPS → 1000-1200 FPS (casi sin impacto)
   - Temas estáticos: 1 render total (~∞x más rápido)
   - Temas dinámicos: regenera cada ~20-30 frames (~25x más rápido)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 17:36:53 +02:00
e7dc8f6d13 feat: añadir cambio de dirección de gravedad en modo LOGO
El modo LOGO ahora incluye cambios automáticos de dirección de
gravedad como parte de sus variaciones, aumentando la diversidad
visual de la demostración.

Cambios:
- Nueva acción en modo LOGO (PHYSICS): cambiar dirección gravedad (16%)
- Rebalanceo de probabilidades existentes:
  • PHYSICS → SHAPE: 60% → 50%
  • Gravedad ON: 20% → 18%
  • Gravedad OFF: 20% → 16%
  • Dirección gravedad: nuevo 16%
- Al cambiar dirección, se fuerza gravedad ON para visibilidad

Antes el modo LOGO solo alternaba entre figura/física y gravedad
on/off, pero nunca cambiaba la dirección. Ahora tiene las mismas
capacidades de variación que los modos DEMO y DEMO_LITE.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 17:11:22 +02:00
9cabbd867f fix: SHAPE mode - regenerar figuras automáticamente al cambiar escenario
PROBLEMA RESUELTO:
En modo SHAPE (figuras 3D), al cambiar el número de pelotas (teclas 1-8),
las nuevas pelotas aparecían en pantalla pero NO formaban la figura hasta
pulsar de nuevo la tecla de figura (Q/W/E/R/T).

CAUSA RAÍZ:
1. changeScenario() creaba pelotas nuevas en centro de pantalla
2. generateShape() generaba puntos target de la figura
3. PERO las pelotas nuevas no tenían shape_attraction_active=true
4. Sin atracción activa, las pelotas no se movían hacia sus targets

CAMBIOS IMPLEMENTADOS:

1. Ball class (ball.h/ball.cpp):
   - Constructor ahora acepta parámetro Y explícito
   - Eliminado hardcodeo Y=0.0f en inicialización de pos_

2. SceneManager (scene_manager.cpp):
   - PHYSICS mode: Y = 0.0f (parte superior, comportamiento original)
   - SHAPE mode: Y = screen_height_/2.0f (centro vertical) 
   - BOIDS mode: Y = rand() (posición Y aleatoria)
   - Ball constructor llamado con parámetro Y según modo

3. Engine (engine.cpp:514-521):
   - Tras generateShape(), activar enableShapeAttraction(true) en todas
     las pelotas nuevas
   - Garantiza que las pelotas converjan inmediatamente hacia figura

RESULTADO:
 Cambiar escenario (1-8) en modo SHAPE regenera automáticamente la figura
 No requiere pulsar tecla de figura de nuevo
 Transición suave e inmediata hacia nueva configuración

ARCHIVOS MODIFICADOS:
- source/ball.h: Constructor acepta parámetro Y
- source/ball.cpp: Usar Y en lugar de hardcode 0.0f
- source/scene/scene_manager.cpp: Inicializar Y según SimulationMode
- source/engine.cpp: Activar shape attraction tras changeScenario()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 16:48:52 +02:00
8c2a8857fc fix: Preservar SimulationMode y mejorar Debug HUD
CAMBIOS:
- Debug HUD reorganizado en layout de 2 columnas (LEFT/RIGHT, sin centro)
- Añadidos getters públicos en Engine para info de sistema
- changeScenario() ahora preserva el SimulationMode actual
- Inicialización de pelotas según modo (PHYSICS/SHAPE/BOIDS)
- Eliminada duplicación de logo_entered_manually_ (ahora en StateManager)

ARCHIVOS MODIFICADOS:
- engine.h: Añadidos 8 getters públicos para UIManager
- engine.cpp: changeScenario() pasa current_mode_ a SceneManager
- scene_manager.h: changeScenario() acepta parámetro SimulationMode
- scene_manager.cpp: Inicialización según modo (RULES.md líneas 23-26)
- ui_manager.h: render() acepta Engine* y renderDebugHUD() actualizado
- ui_manager.cpp: Debug HUD con columnas LEFT (sistema) y RIGHT (física)

REGLAS.md IMPLEMENTADO:
 Líneas 23-26: Inicialización diferenciada por modo
  - PHYSICS: Top, 75% distribución central en X, velocidades aleatorias
  - SHAPE: Centro de pantalla, sin velocidad inicial
  - BOIDS: Posiciones y velocidades aleatorias
 Líneas 88-96: Debug HUD con información de sistema completa

BUGS CORREGIDOS:
- Fix: Cambiar escenario (1-8) en FIGURE ya no resetea a PHYSICS 
- Fix: Las pelotas se inicializan correctamente según el modo activo
- Fix: AppMode movido de centro a izquierda en Debug HUD

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 09:52:33 +02:00
3d26bfc6fa commit marrano 2025-10-12 21:30:32 +02:00
adfa315a43 fix: ui_manager.h estava sent ignorat per .gitignore 2025-10-12 15:04:24 +02:00
18a8812ad7 Help Overlay: implementación preliminar 2025-10-12 07:02:22 +02:00
35f29340db Docs: Actualizar BOIDS_ROADMAP con Fase 2 completada
Marcada Fase 2 como completada con detalles de implementación:
- Tiempo real: 2 horas (estimado: 4-6 horas)
- 206 líneas de código añadidas
- SpatialGrid genérico reutilizable
- Pendiente: Medición de rendimiento real

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 05:47:30 +02:00
abbda0f30b FASE 2: Spatial Hash Grid - Optimización O(n²) → O(n) para boids
Implementado sistema genérico de particionamiento espacial reutilizable
que reduce drásticamente la complejidad del algoritmo de boids.

**MEJORA DE RENDIMIENTO ESPERADA:**
- Sin grid: 1000 boids = 1M comparaciones (1000²)
- Con grid: 1000 boids ≈ 9K comparaciones (~9 vecinos/celda)
- **Speedup teórico: ~100x en casos típicos**

**COMPONENTES IMPLEMENTADOS:**

1. **SpatialGrid genérico (spatial_grid.h/.cpp):**
   - Divide espacio 2D en celdas de 100x100px
   - Hash map para O(1) lookup de celdas
   - queryRadius(): Busca solo en celdas adyacentes (máx 9 celdas)
   - Reutilizable para colisiones ball-to-ball en física (futuro)

2. **Integración en BoidManager:**
   - Grid poblado al inicio de cada frame (O(n))
   - 3 reglas de Reynolds ahora usan queryRadius() en lugar de iterar TODOS
   - Separación/Alineación/Cohesión: O(n) total en lugar de O(n²)

3. **Configuración (defines.h):**
   - BOID_GRID_CELL_SIZE = 100.0f (≥ BOID_COHESION_RADIUS)

**CAMBIOS TÉCNICOS:**
- boid_manager.h: Añadido miembro spatial_grid_
- boid_manager.cpp: update() poblа grid, 3 reglas usan queryRadius()
- spatial_grid.cpp: 89 líneas de implementación genérica
- spatial_grid.h: 74 líneas con documentación exhaustiva

**PRÓXIMOS PASOS:**
- Medir rendimiento real con 1K, 5K, 10K boids
- Comparar FPS antes/después
- Validar que comportamiento es idéntico

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 05:46:34 +02:00
6aacb86d6a Fix: Mejoras de UX en modo boids (auto-exit + screen size)
Implementadas 2 mejoras críticas para modo boids:

**1. Auto-exit de boids al activar gravedad (G/cursores):**
   - handleGravityToggle(): Sale a PHYSICS si está en BOIDS
   - handleGravityDirectionChange(): Sale a PHYSICS y aplica dirección
   - Razón: La gravedad es conceptualmente incompatible con boids
   - UX esperada: Usuario pulsa G → vuelve automáticamente a física

**2. Update screen size en F4 (real fullscreen):**
   - toggleRealFullscreen() ahora llama a boid_manager_->updateScreenSize()
   - Corrige bug: Boids no respetaban nuevas dimensiones tras F4
   - Wrapping boundaries ahora se actualizan correctamente

Cambios:
- engine.cpp: Añadida comprobación de BOIDS en métodos de gravedad
- engine.cpp: Actualización de boid_manager en F4 (línea 420)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 05:36:44 +02:00
0873d80765 Boids Fase 1.4: FIX CRÍTICO - Normalizar fuerza de cohesión
BUG CRÍTICO ENCONTRADO:
La fuerza de cohesión NO estaba normalizada, causando atracción
tipo "gravedad" que hacía que los boids colapsaran a puntos.

CAUSA RAÍZ:
```cpp
// ANTES (INCORRECTO):
float steer_x = (center_of_mass_x - center_x) * WEIGHT * delta_time;
// Si center_of_mass está a 100px → fuerza = 100 * 0.5 * 0.016 = 0.8
// ¡FUERZA PROPORCIONAL A DISTANCIA! Como una gravedad newtoniana
```

SOLUCIÓN IMPLEMENTADA:
```cpp
// DESPUÉS (CORRECTO):
float distance = sqrt(dx*dx + dy*dy);
float steer_x = (dx / distance) * WEIGHT * delta_time;
// Fuerza siempre normalizada = 1.0 * WEIGHT * delta_time
// Independiente de distancia (comportamiento Reynolds correcto)
```

CAMBIOS:

1. boid_manager.cpp::applyCohesion() - Fase 1.4
   - Normalizar dirección hacia centro de masa
   - Fuerza constante independiente de distancia
   - Check de división por cero (distance > 0.1f)

2. defines.h - Ajuste de parámetros tras normalización
   - BOID_COHESION_WEIGHT: 0.5 → 0.001 (1000x menor)
     * Ahora que está normalizado, el valor anterior era gigantesco
   - BOID_MAX_SPEED: 3.0 → 2.5 (reducida para evitar velocidades extremas)
   - BOID_MAX_FORCE: 0.5 → 0.05 (reducida 10x)
   - BOID_MIN_SPEED: 0.5 → 0.3 (reducida)
   - Radios restaurados a valores originales (30/50/80)

RESULTADO ESPERADO:
 Sin colapso a puntos (cohesión normalizada correctamente)
 Movimiento orgánico sin "órbitas" artificiales
 Velocidades controladas y naturales
 Balance correcto entre las 3 fuerzas

TESTING:
Por favor probar con 100 y 1000 boids:
- ¿Se mantienen dispersos sin colapsar?
- ¿Las órbitas han desaparecido?
- ¿El movimiento es más natural?

Estado: Compilación exitosa
Rama: boids_development

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 22:13:40 +02:00
b73e77e9bc Boids Fase 1: Corregir bug de clustering crítico
PROBLEMA RESUELTO:
Los boids colapsaban al mismo punto dentro de cada grupo, haciendo
el sistema visualmente inutilizable.

CAMBIOS IMPLEMENTADOS:

1. BOIDS_ROADMAP.md creado (NEW FILE)
   - Roadmap completo de 6 fases para mejora de boids
   - Diagnóstico detallado de problemas actuales
   - Plan de implementación con métricas de éxito
   - Fase 1 (crítica): Fix clustering
   - Fase 2 (alto impacto): Spatial Hash Grid O(n²)→O(n)
   - Fases 3-6: Mejoras visuales, comportamientos avanzados

2. defines.h - Rebalanceo de parámetros (Fase 1.1)
   - BOID_SEPARATION_RADIUS: 30→25px
   - BOID_COHESION_RADIUS: 80→60px (REDUCIDO 25%)
   - BOID_SEPARATION_WEIGHT: 1.5→3.0 (TRIPLICADO)
   - BOID_COHESION_WEIGHT: 0.8→0.5 (REDUCIDO 37%)
   - BOID_MAX_FORCE: 0.1→0.5 (QUINTUPLICADO)
   - BOID_MIN_SPEED: 0.5 (NUEVO - evita boids estáticos)

3. boid_manager.cpp - Mejoras físicas
   - Fase 1.2: Velocidad mínima en limitSpeed()
     * Evita boids completamente estáticos
     * Mantiene movimiento continuo
   - Fase 1.3: Fuerza de separación proporcional a cercanía
     * Antes: dividir por distance² (muy débil)
     * Ahora: proporcional a (RADIUS - distance) / RADIUS
     * Resultado: 100% fuerza en colisión, 0% en radio máximo

RESULTADO ESPERADO:
 Separación domina sobre cohesión (peso 3.0 vs 0.5)
 Boids mantienen distancia personal (~10-15px)
 Sin colapso a puntos únicos
 Movimiento continuo sin boids estáticos

PRÓXIMOS PASOS:
- Testing manual con 100, 1000 boids
- Validar comportamiento disperso sin clustering
- Fase 2: Spatial Hash Grid para rendimiento O(n)

Estado: Compilación exitosa, listo para testing
Rama: boids_development

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 22:04:20 +02:00
132 changed files with 31316 additions and 2544 deletions

22
.gitignore vendored
View File

@@ -12,7 +12,6 @@ vibe3_physics.exe
*.lib
*.so
*.dylib
*.dll
# Archivos de compilación y enlazado
*.d
@@ -26,6 +25,7 @@ Build/
BUILD/
cmake-build-*/
.cmake/
.cache/
# Archivos generados por CMake
CMakeFiles/
@@ -57,7 +57,6 @@ Makefile
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qm
.qmake.stash
@@ -94,4 +93,21 @@ Thumbs.db
*.temp
# Claude Code
.claude/
.claude/
# Archivos de recursos empaquetados
resources.pack
# Archivos de distribución (resultados de release)
*.zip
*.dmg
*.tar.gz
*.AppImage
# Carpetas temporales de empaquetado
vibe3_release/
Frameworks/
# Binarios de herramientas
tools/pack_resources
tools/*.exe

577
CLAUDE.md
View File

@@ -1,577 +0,0 @@
# Claude Code Session - ViBe3 Physics
## Estado del Proyecto
**Proyecto:** ViBe3 Physics - Simulador de sprites con físicas avanzadas
**Objetivo:** Implementar nuevas físicas experimentales expandiendo sobre el sistema de delta time
**Base:** Migrado desde vibe1_delta con sistema delta time ya implementado
## Progreso Actual
### ✅ Completado
#### 1. **Migración y Setup Inicial**
- ✅ Renombrado vibe1_delta → vibe3_physics en todos los archivos
- ✅ Carpeta resources → data
- ✅ Actualizado CMakeLists.txt, .gitignore, defines.h, README.md
- ✅ Añadido .claude/ al .gitignore
- ✅ Sistema de compilación CMake funcionando
#### 2. **Sistema de Físicas Base (Heredado)**
-**Delta time implementado** - Física independiente del framerate
- ✅ Contador FPS en tiempo real (esquina superior derecha, amarillo)
- ✅ Control V-Sync dinámico con tecla "V" (ON/OFF)
- ✅ Display V-Sync (esquina superior izquierda, cian)
-**Sistema de temas visuales** - 15 temas (9 estáticos + 6 dinámicos con animación)
-**Batch rendering optimizado** - Maneja hasta 100,000 sprites
#### 3. **NUEVA CARACTERÍSTICA: Gravedad Direccional** 🎯
-**Enum GravityDirection** (UP/DOWN/LEFT/RIGHT) en defines.h
-**Ball class actualizada** para física multi-direccional
-**Detección de superficie inteligente** - Adaptada a cada dirección
-**Fricción direccional** - Se aplica en la superficie correcta
-**Controles de cursor** - Cambio dinámico de gravedad
-**Debug display actualizado** - Muestra dirección actual
#### 4. **NUEVA CARACTERÍSTICA: Coeficientes de Rebote Variables** ⚡
-**Rango ampliado** - De 0.60-0.89 a 0.30-0.95 (+120% variabilidad)
-**Comportamientos diversos** - Desde pelotas super rebotonas a muy amortiguadas
-**Debug display** - Muestra coeficiente LOSS de primera pelota
-**Física realista** - Elimina sincronización entre pelotas
#### 5. **🎯 NUEVA CARACTERÍSTICA: Modo RotoBall (Esfera 3D Rotante)** 🌐
-**Fibonacci Sphere Algorithm** - Distribución uniforme de puntos en esfera 3D
-**Rotación dual (X/Y)** - Efecto visual dinámico estilo demoscene
-**Profundidad Z simulada** - Color mod según distancia (oscuro=lejos, brillante=cerca)
-**Física de atracción con resorte** - Sistema de fuerzas con conservación de momento
-**Transición física realista** - Pelotas atraídas a esfera rotante con aceleración
-**Amortiguación variable** - Mayor damping cerca del punto (estabilización)
-**Sin sprites adicionales** - Usa SDL_SetTextureColorMod para profundidad
-**Proyección ortográfica** - Coordenadas 3D → 2D en tiempo real
-**Conservación de inercia** - Al salir mantienen velocidad tangencial
-**Compatible con temas** - Mantiene paleta de colores activa
-**Performance optimizado** - Funciona con 1-100,000 pelotas
### 📋 Controles Actuales
| Tecla | Acción |
|-------|--------|
| **↑** | **Gravedad hacia ARRIBA** |
| **↓** | **Gravedad hacia ABAJO** |
| **←** | **Gravedad hacia IZQUIERDA** |
| **→** | **Gravedad hacia DERECHA** |
| **C** | **🌐 MODO ROTOBALL - Toggle esfera 3D rotante** |
| V | Alternar V-Sync ON/OFF |
| H | **Toggle debug display (FPS, V-Sync, física, gravedad, modo)** |
| **Numpad Enter** | **Toggle página de temas (Página 1 ↔ Página 2)** |
| **Numpad 1-9, 0** | **Acceso directo a temas según página activa** (ver tablas abajo) |
| B | Ciclar entre TODOS los temas de colores (15 temas) - Adelante |
| Shift+B | Ciclar entre TODOS los temas de colores - Atrás |
| 1-8 | Cambiar número de pelotas (1 a 100,000) |
| ESPACIO | Impulsar pelotas hacia arriba |
| G | Alternar gravedad ON/OFF (mantiene dirección) |
| ESC | Salir |
### 🎨 Temas de Colores (15 Temas Disponibles - Sistema de 2 Páginas)
**IMPORTANTE:** Usa **Numpad Enter** para cambiar entre Página 1 y Página 2
#### **Página 1** (Temas Estáticos + 1 Dinámico)
| Tecla | Tema | Tipo | Descripción |
|-------|------|------|-------------|
| Numpad 1 | ATARDECER | Estático | Naranjas, rojos, amarillos, rosas |
| Numpad 2 | OCÉANO | Estático | Azules, turquesas, blancos |
| Numpad 3 | NEÓN | Estático | Cian, magenta, verde lima, amarillo vibrante |
| Numpad 4 | BOSQUE | Estático | Verdes, marrones, amarillos otoño |
| Numpad 5 | RGB | Estático | Círculo cromático 24 colores (fondo blanco) |
| Numpad 6 | MONOCROMO | Estático | Fondo negro degradado, sprites blancos |
| Numpad 7 | LAVANDA | Estático | Degradado violeta-azul, pelotas amarillo dorado |
| Numpad 8 | CARMESÍ | Estático | Fondo negro-rojo, pelotas rojas uniformes |
| Numpad 9 | ESMERALDA | Estático | Fondo negro-verde, pelotas verdes uniformes |
| Numpad 0 | AMANECER | **Dinámico** | Noche → Alba → Día (loop 12s) |
#### **Página 2** (Temas Dinámicos Animados)
| Tecla | Tema | Tipo | Descripción |
|-------|------|------|-------------|
| Numpad 1 | OLAS OCEÁNICAS | **Dinámico** | Azul oscuro ↔ Turquesa (loop 8s) |
| Numpad 2 | PULSO NEÓN | **Dinámico** | Negro ↔ Neón brillante (ping-pong 3s) |
| Numpad 3 | FUEGO | **Dinámico** | Brasas → Llamas → Inferno (loop 10s) |
| Numpad 4 | AURORA | **Dinámico** | Verde → Violeta → Cian (loop 14s) |
| Numpad 5 | VOLCÁN | **Dinámico** | Ceniza → Erupción → Lava (loop 12s) |
| Numpad 6-9, 0 | (sin asignar) | - | Sin función en Página 2 |
### 🎯 Debug Display (Tecla H)
Cuando está activado muestra:
```
FPS: 75 # Esquina superior derecha (amarillo)
VSYNC ON # Esquina superior izquierda (cian)
GRAV 720 # Magnitud gravedad (magenta)
VY -145 # Velocidad Y primera pelota (magenta)
SURFACE YES # En superficie (magenta)
LOSS 0.73 # Coeficiente rebote primera pelota (magenta)
GRAVITY DOWN # Dirección actual (amarillo)
THEME SUNSET # Tema activo (amarillo claro)
MODE PHYSICS # Modo simulación actual (verde claro) - PHYSICS/ROTOBALL
```
## Arquitectura Actual
```
vibe3_physics/
├── source/
│ ├── main.cpp # Bucle principal + controles + debug
│ ├── ball.h/.cpp # Clase Ball con física direccional
│ ├── defines.h # Constantes + enum GravityDirection
│ └── external/ # Utilidades externas
│ ├── texture.h/.cpp # Gestión texturas + nearest filter
│ ├── sprite.h/.cpp # Sistema sprites
│ ├── dbgtxt.h # Debug text + nearest filter
│ └── stb_image.h # Carga imágenes
├── data/ # Recursos (antes resources/)
│ └── ball.png # Textura pelota 10x10px
├── CMakeLists.txt # Build system
└── CLAUDE.md # Este archivo de seguimiento
```
## Sistema de Gravedad Direccional
### 🔧 Implementación Técnica
#### Enum y Estados
```cpp
enum class GravityDirection {
DOWN, // ↓ Gravedad hacia abajo (por defecto)
UP, // ↑ Gravedad hacia arriba
LEFT, // ← Gravedad hacia la izquierda
RIGHT // → Gravedad hacia la derecha
};
```
#### Lógica de Física por Dirección
- **DOWN**: Pelotas caen hacia abajo, fricción en suelo inferior
- **UP**: Pelotas "caen" hacia arriba, fricción en techo
- **LEFT**: Pelotas "caen" hacia izquierda, fricción en pared izquierda
- **RIGHT**: Pelotas "caen" hacia derecha, fricción en pared derecha
#### Cambios en Ball Class
- `on_floor_``on_surface_` (más genérico)
- `gravity_direction_` (nuevo miembro)
- `setGravityDirection()` (nueva función)
- `update()` completamente reescrito para lógica direccional
## Lecciones Aprendidas de ViBe2 Modules
### ✅ Éxitos de Modularización
- **C++20 modules** son viables para código propio
- **CMake + Ninja** funciona bien para modules
- **Separación clara** de responsabilidades mejora arquitectura
### ❌ Limitaciones Encontradas
- **SDL3 + modules** generan conflictos irresolubles
- **Bibliotecas externas** requieren includes tradicionales
- **Enfoque híbrido** (modules propios + includes externos) es más práctico
### 🎯 Decisión para ViBe3 Physics
- **Headers tradicionales** (.h/.cpp) por compatibilidad
- **Enfoque en características** antes que arquitectura
- **Organización por clases** en lugar de modules inicialmente
## Sistema de Coeficientes de Rebote Variables
### 🔧 Implementación Técnica
#### Problema Anterior
```cpp
// Sistema ANTIGUO - Poca variabilidad
loss_ = ((rand() % 30) * 0.01f) + 0.6f; // 0.60 - 0.89 (diferencia: 0.29)
```
**Resultado**: Pelotas con comportamientos muy similares → Sincronización visible
#### Solución Implementada
```cpp
// Sistema NUEVO - Alta variabilidad
loss_ = ((rand() % 66) * 0.01f) + 0.30f; // 0.30 - 0.95 (diferencia: 0.65)
```
### 🎯 Tipos de Comportamiento
#### Categorías de Materiales
- **🏀 Super Rebotona** (0.85-0.95): Casi no pierde energía, rebota muchas veces
- **⚽ Normal** (0.65-0.85): Comportamiento estándar equilibrado
- **🎾 Amortiguada** (0.45-0.65): Pierde energía moderada, se estabiliza
- **🏐 Muy Amortiguada** (0.30-0.45): Se para rápidamente, pocas rebotes
### ✅ Beneficios Conseguidos
- **+120% variabilidad** en coeficientes de rebote
- **Eliminación de sincronización** entre pelotas
- **Comportamientos diversos** visibles inmediatamente
- **Física más realista** con materiales diferentes
- **Debug display** para monitoreo en tiempo real
## 🚀 Próximos Pasos - Físicas Avanzadas
### Ideas Pendientes de Implementación
#### 1. **Colisiones Entre Partículas**
- Detección de colisión ball-to-ball
- Física de rebotes entre pelotas
- Conservación de momentum
#### 2. **Materiales y Propiedades**
- Diferentes coeficientes de rebote por pelota
- Fricción variable por material
- Densidad y masa como propiedades
#### 3. **Fuerzas Externas**
- **Viento** - Fuerza horizontal constante
- **Campos magnéticos** - Atracción/repulsión a puntos
- **Turbulencia** - Fuerzas aleatorias localizadas
#### 4. **Interactividad Avanzada**
- Click para aplicar fuerzas puntuales
- Arrastrar para crear campos de fuerza
- Herramientas de "pincel" de física
#### 5. **Visualización Avanzada**
- **Trails** - Estelas de movimiento
- **Heatmaps** - Visualización de velocidad/energía
- **Vectores de fuerza** - Visualizar gravedad y fuerzas
#### 6. **Optimizaciones**
- Spatial partitioning para colisiones
- Level-of-detail para muchas partículas
- GPU compute shaders para física masiva
### 🎮 Controles Futuros Sugeridos
```
Mouse Click: Aplicar fuerza puntual
Mouse Drag: Crear campo de fuerza
Mouse Wheel: Ajustar intensidad
R: Reset todas las pelotas
P: Pausa/Resume física
M: Modo materiales
W: Toggle viento
```
## 🌐 Implementación Técnica: Modo RotoBall
### Algoritmo Fibonacci Sphere
Distribución uniforme de puntos en una esfera usando la secuencia de Fibonacci:
```cpp
const float golden_ratio = (1.0f + sqrtf(5.0f)) / 2.0f;
const float angle_increment = PI * 2.0f * golden_ratio;
for (int i = 0; i < num_points; i++) {
float t = static_cast<float>(i) / static_cast<float>(num_points);
float phi = acosf(1.0f - 2.0f * t); // Latitud: 0 a π
float theta = angle_increment * i; // Longitud: 0 a 2π * golden_ratio
// Coordenadas esféricas → cartesianas
float x = cosf(theta) * sinf(phi) * radius;
float y = sinf(theta) * sinf(phi) * radius;
float z = cosf(phi) * radius;
}
```
**Ventajas:**
- Distribución uniforme sin clustering en polos
- O(1) cálculo por punto (no requiere iteraciones)
- Visualmente perfecto para demoscene effects
### Rotación 3D (Matrices de Rotación)
```cpp
// Rotación en eje Y (horizontal)
float cos_y = cosf(angle_y);
float sin_y = sinf(angle_y);
float x_rot = x * cos_y - z * sin_y;
float z_rot = x * sin_y + z * cos_y;
// Rotación en eje X (vertical)
float cos_x = cosf(angle_x);
float sin_x = sinf(angle_x);
float y_rot = y * cos_x - z_rot * sin_x;
float z_final = y * sin_x + z_rot * cos_x;
```
**Velocidades:**
- Eje Y: 1.5 rad/s (rotación principal horizontal)
- Eje X: 0.8 rad/s (rotación secundaria vertical)
- Ratio Y/X ≈ 2:1 para efecto visual dinámico
### Proyección 3D → 2D
**Proyección Ortográfica:**
```cpp
float screen_x = center_x + x_rotated;
float screen_y = center_y + y_rotated;
```
**Profundidad Z (Color Modulation):**
```cpp
// Normalizar Z de [-radius, +radius] a [0, 1]
float z_normalized = (z_final + radius) / (2.0f * radius);
// Mapear a rango de brillo [MIN_BRIGHTNESS, MAX_BRIGHTNESS]
float brightness_factor = (MIN + z_normalized * (MAX - MIN)) / 255.0f;
// Aplicar a color RGB
int r_mod = color.r * brightness_factor;
int g_mod = color.g * brightness_factor;
int b_mod = color.b * brightness_factor;
```
**Efecto visual:**
- Z cerca (+radius): Brillo máximo (255) → Color original
- Z lejos (-radius): Brillo mínimo (50) → Color oscuro
- Simula profundidad sin sprites adicionales
### Transición Suave (Interpolación)
```cpp
// Progress de 0.0 a 1.0 en ROTOBALL_TRANSITION_TIME (1.5s)
transition_progress += delta_time / ROTOBALL_TRANSITION_TIME;
// Lerp desde posición actual a posición de esfera
float lerp_x = current_x + (target_sphere_x - current_x) * progress;
float lerp_y = current_y + (target_sphere_y - current_y) * progress;
```
**Características:**
- Independiente del framerate (usa delta_time)
- Suave y orgánico
- Sin pop visual
### Performance
- **Batch rendering**: Una sola llamada `SDL_RenderGeometry` para todos los puntos
- **Recalculación**: Fibonacci sphere recalculada cada frame (O(n) predecible)
- **Sin malloc**: Usa datos ya almacenados en Ball objects
- **Color mod**: CPU-side, sin overhead GPU adicional
**Rendimiento medido:**
- 100 pelotas: >300 FPS
- 1,000 pelotas: >200 FPS
- 10,000 pelotas: >100 FPS
- 100,000 pelotas: >60 FPS (mismo que modo física)
---
## 🔬 Sistema de Física con Atracción (Spring Force)
### Mejora Implementada: Transición Física Realista
**Problema anterior:** Interpolación lineal artificial (lerp) sin física real
**Solución:** Sistema de resorte (Hooke's Law) con conservación de momento
### Ecuaciones Implementadas
#### Fuerza de Resorte (Ley de Hooke)
```cpp
F_spring = k * (target - position)
```
- `k = 300.0`: Constante de rigidez del resorte (N/m)
- Mayor k = atracción más fuerte
#### Fuerza de Amortiguación (Damping)
```cpp
F_damping = c * velocity
F_total = F_spring - F_damping
```
- `c_base = 15.0`: Amortiguación lejos del punto
- `c_near = 50.0`: Amortiguación cerca (estabilización)
- Evita oscilaciones infinitas
#### Aplicación de Fuerzas
```cpp
acceleration = F_total / mass // Asumiendo mass = 1
velocity += acceleration * deltaTime
position += velocity * deltaTime
```
### Comportamiento Físico
**Al activar RotoBall (tecla C):**
1. Esfera comienza a rotar inmediatamente
2. Cada pelota mantiene su velocidad actual (`vx`, `vy`)
3. Se aplica fuerza de atracción hacia punto móvil en esfera
4. Las pelotas se aceleran hacia sus destinos
5. Amortiguación las estabiliza al llegar
**Durante RotoBall:**
- Punto destino rota constantemente (actualización cada frame)
- Fuerza se recalcula hacia posición rotada
- Pelotas "persiguen" su punto mientras este se mueve
- Efecto: Convergencia con ligera oscilación orbital
**Al desactivar RotoBall (tecla C):**
1. Atracción se desactiva (`enableRotoBallAttraction(false)`)
2. Pelotas conservan velocidad tangencial actual
3. Gravedad vuelve a aplicarse
4. Transición suave a física normal
### Constantes Físicas Ajustables
```cpp
// En defines.h (VALORES ACTUALES - Amortiguamiento crítico)
ROTOBALL_SPRING_K = 300.0f; // Rigidez resorte
ROTOBALL_DAMPING_BASE = 35.0f; // Amortiguación lejos (crítico ≈ 2*√k*m)
ROTOBALL_DAMPING_NEAR = 80.0f; // Amortiguación cerca (absorción rápida)
ROTOBALL_NEAR_THRESHOLD = 5.0f; // Distancia "cerca" (px)
ROTOBALL_MAX_FORCE = 1000.0f; // Límite fuerza (seguridad)
```
**Changelog de Ajustes:**
- **v1:** `DAMPING_BASE=15.0, NEAR=50.0` → Oscilación visible (subdamped)
- **v2:** `DAMPING_BASE=35.0, NEAR=80.0`**Absorción rápida sin oscilación**
### Ajustes Recomendados
**Si siguen oscilando (poco probable):**
```cpp
ROTOBALL_DAMPING_BASE = 50.0f; // Amortiguamiento super crítico
ROTOBALL_DAMPING_NEAR = 100.0f; // Absorción instantánea
```
**Si llegan muy lento:**
```cpp
ROTOBALL_SPRING_K = 400.0f; // Más fuerza
ROTOBALL_DAMPING_BASE = 40.0f; // Compensar con más damping
```
**Si quieres más "rebote" visual:**
```cpp
ROTOBALL_DAMPING_BASE = 25.0f; // Menos amortiguación
ROTOBALL_DAMPING_NEAR = 60.0f; // Ligera oscilación
```
### Ventajas del Sistema
**Física realista**: Conservación de momento angular
**Transición orgánica**: Aceleración natural, no artificial
**Inercia preservada**: Al salir conservan velocidad
**Estabilización automática**: Damping evita oscilaciones infinitas
**Performance**: O(1) por pelota, muy eficiente
---
## 🎨 Z-Sorting (Painter's Algorithm)
### Problema de Renderizado 3D
**Antes del Z-sorting:**
- Pelotas renderizadas en orden fijo del vector: `Ball[0] → Ball[1] → ... → Ball[N]`
- Orden aleatorio respecto a profundidad Z
- **Problema:** Pelotas oscuras (fondo) pintadas sobre claras (frente)
- Resultado: Inversión de profundidad visual incorrecta
**Después del Z-sorting:**
- Pelotas ordenadas por `depth_brightness` antes de renderizar
- Painter's Algorithm: **Fondo primero, frente último**
- Pelotas oscuras (Z bajo) renderizadas primero
- Pelotas claras (Z alto) renderizadas último (encima)
- **Resultado:** Oclusión 3D correcta ✅
### Implementación (engine.cpp::render())
```cpp
if (current_mode_ == SimulationMode::ROTOBALL) {
// 1. Crear vector de índices
std::vector<size_t> render_order;
for (size_t i = 0; i < balls_.size(); i++) {
render_order.push_back(i);
}
// 2. Ordenar por depth_brightness (menor primero = fondo primero)
std::sort(render_order.begin(), render_order.end(),
[this](size_t a, size_t b) {
return balls_[a]->getDepthBrightness() < balls_[b]->getDepthBrightness();
});
// 3. Renderizar en orden de profundidad
for (size_t idx : render_order) {
// Renderizar balls_[idx]...
}
}
```
### Complejidad y Performance
| Operación | Complejidad | Tiempo (estimado) |
|-----------|-------------|-------------------|
| Crear índices | O(n) | ~0.001ms (1K pelotas) |
| std::sort | O(n log n) | ~0.01ms (1K pelotas) |
| Renderizar | O(n) | ~variable |
| **Total** | **O(n log n)** | **~0.15ms (10K pelotas)** |
**Impacto en FPS:**
- 100 pelotas: Imperceptible (<0.001ms)
- 1,000 pelotas: Imperceptible (~0.01ms)
- 10,000 pelotas: Leve (~0.15ms, ~1-2 FPS)
- 100,000 pelotas: Moderado (~2ms, ~10-15 FPS)
### Optimizaciones Aplicadas
**Solo en modo RotoBall**: Modo física no tiene overhead
**Vector de índices**: `balls_` no se modifica (física estable)
**Reserve() usado**: Evita realocaciones
**Lambda eficiente**: Acceso directo sin copias
### Resultado Visual
**Profundidad correcta**: Fondo detrás, frente delante
**Oclusión apropiada**: Pelotas claras cubren oscuras
**Efecto 3D realista**: Percepción de profundidad correcta
**Sin artefactos visuales**: Ordenamiento estable cada frame
## Métricas del Proyecto
### ✅ Logros Actuales
- **Compilación exitosa** con CMake
- **Commit inicial** creado (dec8d43)
- **17 archivos** versionados
- **9,767 líneas** de código
- **Física direccional** 100% funcional
- **Coeficientes variables** implementados
### 🎯 Objetivos Cumplidos
- ✅ Migración limpia desde vibe1_delta
- ✅ Sistema de gravedad direccional implementado
- ✅ Coeficientes de rebote variables (+120% diversidad)
-**Modo RotoBall (esfera 3D rotante) implementado**
-**Fibonacci sphere algorithm funcionando**
-**Profundidad Z con color modulation**
- ✅ Debug display completo y funcional
- ✅ Controles intuitivos con teclas de cursor
- ✅ Eliminación de sincronización entre pelotas
---
## Comandos Útiles
### Compilación
```bash
mkdir -p build && cd build && cmake .. && cmake --build .
```
### Ejecución
```bash
./vibe3_physics.exe # Windows
./vibe3_physics # Linux/macOS
```
### Git
```bash
git status # Ver cambios
git add . # Añadir archivos
git commit -m "..." # Crear commit
```
---
*Archivo de seguimiento para sesiones Claude Code - ViBe3 Physics*
*Actualizado: Implementación de gravedad direccional completada*

View File

@@ -48,6 +48,9 @@ endif()
# Incluir directorios de SDL3 y SDL3_ttf
include_directories(${SDL3_INCLUDE_DIRS} ${SDL3_ttf_INCLUDE_DIRS})
# Incluir directorio source/ para poder usar includes desde la raíz del proyecto
include_directories(${CMAKE_SOURCE_DIR}/source)
# Añadir el ejecutable reutilizando el nombre del proyecto
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
@@ -56,3 +59,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAK
# Enlazar las bibliotecas necesarias
target_link_libraries(${PROJECT_NAME} ${LINK_LIBS})
# Tool: pack_resources
add_executable(pack_resources tools/pack_resources.cpp source/resource_pack.cpp)
target_include_directories(pack_resources PRIVATE ${CMAKE_SOURCE_DIR}/source)
set_target_properties(pack_resources PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tools")

193
Makefile
View File

@@ -42,64 +42,69 @@ endif
# Nombres para los ficheros de lanzamiento
WINDOWS_RELEASE := $(TARGET_NAME)-$(VERSION)-win32-x64.zip
MACOS_INTEL_RELEASE := $(TARGET_FILE)-$(VERSION)-macos-intel.dmg
MACOS_APPLE_SILICON_RELEASE := $(TARGET_FILE)-$(VERSION)-macos-apple-silicon.dmg
MACOS_INTEL_RELEASE := $(TARGET_NAME)-$(VERSION)-macos-intel.dmg
MACOS_APPLE_SILICON_RELEASE := $(TARGET_NAME)-$(VERSION)-macos-apple-silicon.dmg
LINUX_RELEASE := $(TARGET_FILE)-$(VERSION)-linux.tar.gz
RASPI_RELEASE := $(TARGET_FILE)-$(VERSION)-raspberry.tar.gz
# Lista completa de archivos fuente (basada en estructura de ViBe3)
APP_SOURCES := \
source/ball.cpp \
source/engine.cpp \
source/main.cpp \
source/resource_pack.cpp \
source/external/mouse.cpp \
source/external/sprite.cpp \
source/external/texture.cpp \
source/shapes/atom_shape.cpp \
source/shapes/cube_shape.cpp \
source/shapes/cylinder_shape.cpp \
source/shapes/helix_shape.cpp \
source/shapes/icosahedron_shape.cpp \
source/shapes/png_shape.cpp \
source/shapes/sphere_shape.cpp \
source/shapes/torus_shape.cpp \
source/shapes/wave_grid_shape.cpp
# Lista completa de archivos fuente (detección automática con wildcards, como CMakeLists.txt)
APP_SOURCES := $(wildcard source/*.cpp) \
$(wildcard source/external/*.cpp) \
$(wildcard source/shapes/*.cpp) \
$(wildcard source/themes/*.cpp) \
$(wildcard source/state/*.cpp) \
$(wildcard source/input/*.cpp) \
$(wildcard source/scene/*.cpp) \
$(wildcard source/shapes_mgr/*.cpp) \
$(wildcard source/boids_mgr/*.cpp) \
$(wildcard source/text/*.cpp) \
$(wildcard source/ui/*.cpp)
# Excluir archivos antiguos si existen
APP_SOURCES := $(filter-out source/main_old.cpp, $(APP_SOURCES))
# Includes
INCLUDES := -Isource -Isource/external
# Variables según el sistema operativo
CXXFLAGS_BASE := -std=c++20 -Wall
CXXFLAGS := $(CXXFLAGS_BASE) -Os -ffunction-sections -fdata-sections
CXXFLAGS_DEBUG := $(CXXFLAGS_BASE) -g -D_DEBUG
LDFLAGS :=
ifeq ($(OS),Windows_NT)
FixPath = $(subst /,\\,$1)
CXXFLAGS := -std=c++20 -Wall -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -static-libstdc++ -static-libgcc -Wl,-Bstatic -lpthread -Wl,-Bdynamic -Wl,-subsystem,windows -DWINDOWS_BUILD
CXXFLAGS_DEBUG := -std=c++20 -Wall -g -D_DEBUG -DWINDOWS_BUILD
LDFLAGS := -lmingw32 -lws2_32 -lSDL3 -lopengl32
RM := del /Q
CXXFLAGS += -DWINDOWS_BUILD
CXXFLAGS_DEBUG += -DWINDOWS_BUILD
LDFLAGS += -Wl,--gc-sections -static-libstdc++ -static-libgcc \
-Wl,-Bstatic -lpthread -Wl,-Bdynamic -Wl,-subsystem,windows \
-lmingw32 -lws2_32 -lSDL3 -lSDL3_ttf
RMFILE := del /Q
RMDIR := rmdir /S /Q
MKDIR := mkdir
else
FixPath = $1
CXXFLAGS := -std=c++20 -Wall -Os -ffunction-sections -fdata-sections
CXXFLAGS_DEBUG := -std=c++20 -Wall -g -D_DEBUG
LDFLAGS := -lSDL3
LDFLAGS += -lSDL3 -lSDL3_ttf
RMFILE := rm -f
RMDIR := rm -rdf
RMDIR := rm -rf
MKDIR := mkdir -p
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
CXXFLAGS += -DLINUX_BUILD
LDFLAGS += -lGL
CXXFLAGS += -DLINUX_BUILD
CXXFLAGS_DEBUG += -DLINUX_BUILD
endif
ifeq ($(UNAME_S),Darwin)
CXXFLAGS += -Wno-deprecated -DMACOS_BUILD
CXXFLAGS_DEBUG += -Wno-deprecated -DMACOS_BUILD
LDFLAGS += -framework OpenGL
# Configurar arquitectura (por defecto arm64, como en CMake)
CXXFLAGS += -arch arm64
CXXFLAGS_DEBUG += -arch arm64
CXXFLAGS += -DMACOS_BUILD -arch arm64
CXXFLAGS_DEBUG += -DMACOS_BUILD -arch arm64
# Si quieres binarios universales:
# CXXFLAGS += -arch x86_64
# CXXFLAGS_DEBUG += -arch x86_64
# Y frameworks si hacen falta:
# LDFLAGS += -framework Cocoa -framework IOKit
endif
endif
# Reglas para herramienta de empaquetado y resources.pack
$(PACK_TOOL): $(PACK_SOURCES)
@echo "Compilando herramienta de empaquetado..."
@@ -108,11 +113,21 @@ $(PACK_TOOL): $(PACK_SOURCES)
pack_tool: $(PACK_TOOL)
resources.pack: $(PACK_TOOL)
# Detectar todos los archivos en data/ como dependencias (regenera si cualquiera cambia)
DATA_FILES := $(shell find data -type f 2>/dev/null)
resources.pack: $(PACK_TOOL) $(DATA_FILES)
@echo "Generando resources.pack desde directorio data/..."
$(PACK_TOOL) data resources.pack
@echo "✓ resources.pack generado exitosamente"
# Target para forzar regeneración de resources.pack (usado por releases)
.PHONY: force_resource_pack
force_resource_pack: $(PACK_TOOL)
@echo "Regenerando resources.pack para release..."
$(PACK_TOOL) data resources.pack
@echo "✓ resources.pack regenerado exitosamente"
# Reglas para compilación
windows:
@echo off
@@ -131,20 +146,20 @@ windows_debug:
@echo Compilando version debug para Windows: "$(APP_NAME)_debug.exe"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(WIN_TARGET_FILE)_debug.exe"
windows_release: resources.pack
windows_release: force_resource_pack
@echo "Creando release para Windows - Version: $(VERSION)"
# Crea carpeta temporal 'RELEASE_FOLDER'
@rm -rf "$(RELEASE_FOLDER)"
@mkdir -p "$(RELEASE_FOLDER)"
@if exist "$(RELEASE_FOLDER)" rmdir /S /Q "$(RELEASE_FOLDER)"
@mkdir "$(RELEASE_FOLDER)"
# Copia el archivo 'resources.pack'
@cp -f "resources.pack" "$(RELEASE_FOLDER)/"
@copy /Y "resources.pack" "$(RELEASE_FOLDER)\" >nul
# Copia los ficheros que estan en la raíz del proyecto
@cp -f "LICENSE" "$(RELEASE_FOLDER)/" 2>/dev/null || echo "LICENSE not found (optional)"
@cp -f "README.md" "$(RELEASE_FOLDER)/"
@cp -f release/*.dll "$(RELEASE_FOLDER)/" 2>/dev/null || echo "No DLL files found (optional)"
@copy /Y "LICENSE" "$(RELEASE_FOLDER)\" >nul 2>&1 || echo LICENSE not found (optional)
@copy /Y "README.md" "$(RELEASE_FOLDER)\" >nul
@copy /Y release\dll\*.dll "$(RELEASE_FOLDER)\" >nul 2>&1 || echo DLLs copied successfully
# Compila
@windres release/vibe3.rc -O coff -o $(RESOURCE_FILE)
@@ -152,12 +167,12 @@ windows_release: resources.pack
@strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
# Crea el fichero .zip
@rm -f "$(WINDOWS_RELEASE)"
@if exist "$(WINDOWS_RELEASE)" del /Q "$(WINDOWS_RELEASE)"
@powershell.exe -Command "Compress-Archive -Path '$(RELEASE_FOLDER)/*' -DestinationPath '$(WINDOWS_RELEASE)' -Force"
@echo "Release creado: $(WINDOWS_RELEASE)"
# Elimina la carpeta temporal 'RELEASE_FOLDER'
@rm -rf "$(RELEASE_FOLDER)"
@rmdir /S /Q "$(RELEASE_FOLDER)"
macos:
@echo "Compilando para macOS: $(TARGET_NAME)"
@@ -167,15 +182,24 @@ macos_debug:
@echo "Compilando version debug para macOS: $(TARGET_NAME)_debug"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
macos_release: resources.pack
macos_release: force_resource_pack
@echo "Creando release para macOS - Version: $(VERSION)"
# Verificar e instalar create-dmg si es necesario
@which create-dmg > /dev/null || (echo "Instalando create-dmg..." && brew install create-dmg)
# Elimina datos de compilaciones anteriores
$(RMDIR) "$(RELEASE_FOLDER)"
$(RMDIR) Frameworks
$(RMFILE) tmp.dmg
$(RMFILE) "$(MACOS_INTEL_RELEASE)"
$(RMFILE) "$(MACOS_APPLE_SILICON_RELEASE)"
# Limpia archivos temporales de create-dmg y desmonta volúmenes
@echo "Limpiando archivos temporales y volúmenes montados..."
@rm -f rw.*.dmg 2>/dev/null || true
@hdiutil detach "/Volumes/$(APP_NAME)" 2>/dev/null || true
@hdiutil detach "/Volumes/ViBe3 Physics" 2>/dev/null || true
# Crea la carpeta temporal para hacer el trabajo y las carpetas obligatorias para crear una app de macos
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS"
@@ -185,40 +209,76 @@ macos_release: resources.pack
# Copia carpetas y ficheros
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp -R release/frameworks/SDL3.xcframework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
cp -R release/frameworks/SDL3_ttf.xcframework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
cp -R release/frameworks/SDL3.xcframework Frameworks
cp -R release/frameworks/SDL3_ttf.xcframework Frameworks
cp release/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp release/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)"
# Crea enlaces
ln -s /Applications "$(RELEASE_FOLDER)"/Applications
# NOTA: create-dmg crea automáticamente el enlace a /Applications con --app-drop-link
# No es necesario crearlo manualmente aquí
# Compila la versión para procesadores Intel
ifdef ENABLE_MACOS_X86_64
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos10.15
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos12
# Firma la aplicación
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
# Empaqueta el .dmg de la versión Intel
hdiutil create tmp.dmg -ov -volname "$(APP_NAME)" -fs HFS+ -srcfolder "$(RELEASE_FOLDER)"
hdiutil convert tmp.dmg -format UDZO -o "$(MACOS_INTEL_RELEASE)"
$(RMFILE) tmp.dmg
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
# Empaqueta el .dmg de la versión Intel con create-dmg
@echo "Creando DMG Intel con iconos de 96x96..."
@create-dmg \
--volname "$(APP_NAME)" \
--window-pos 200 120 \
--window-size 720 300 \
--icon-size 96 \
--text-size 12 \
--icon "$(APP_NAME).app" 278 102 \
--icon "LICENSE" 441 102 \
--icon "README.md" 604 102 \
--app-drop-link 115 102 \
--hide-extension "$(APP_NAME).app" \
"$(MACOS_INTEL_RELEASE)" \
"$(RELEASE_FOLDER)"
@if [ -f "$(MACOS_INTEL_RELEASE)" ]; then \
echo "✓ Release Intel creado exitosamente: $(MACOS_INTEL_RELEASE)"; \
else \
echo "✗ Error: No se pudo crear el DMG Intel"; \
exit 1; \
fi
@rm -f rw.*.dmg 2>/dev/null || true
endif
# Compila la versión para procesadores Apple Silicon
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DSDL_DISABLE_IMMINTRIN_H $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DSDL_DISABLE_IMMINTRIN_H $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos12
# Firma la aplicación
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
# Empaqueta el .dmg de la versión Apple Silicon
hdiutil create tmp.dmg -ov -volname "$(APP_NAME)" -fs HFS+ -srcfolder "$(RELEASE_FOLDER)"
hdiutil convert tmp.dmg -format UDZO -o "$(MACOS_APPLE_SILICON_RELEASE)"
$(RMFILE) tmp.dmg
@echo "Release Apple Silicon creado: $(MACOS_APPLE_SILICON_RELEASE)"
# Empaqueta el .dmg de la versión Apple Silicon con create-dmg
@echo "Creando DMG Apple Silicon con iconos de 96x96..."
@create-dmg \
--volname "$(APP_NAME)" \
--window-pos 200 120 \
--window-size 720 300 \
--icon-size 96 \
--text-size 12 \
--icon "$(APP_NAME).app" 278 102 \
--icon "LICENSE" 441 102 \
--icon "README.md" 604 102 \
--app-drop-link 115 102 \
--hide-extension "$(APP_NAME).app" \
"$(MACOS_APPLE_SILICON_RELEASE)" \
"$(RELEASE_FOLDER)"
@if [ -f "$(MACOS_APPLE_SILICON_RELEASE)" ]; then \
echo "✓ Release Apple Silicon creado exitosamente: $(MACOS_APPLE_SILICON_RELEASE)"; \
else \
echo "✗ Error: No se pudo crear el DMG Apple Silicon"; \
exit 1; \
fi
@rm -f rw.*.dmg 2>/dev/null || true
# Elimina las carpetas temporales
$(RMDIR) Frameworks
@@ -233,7 +293,7 @@ linux_debug:
@echo "Compilando version debug para Linux: $(TARGET_NAME)_debug"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
linux_release: resources.pack
linux_release: force_resource_pack
@echo "Creando release para Linux - Version: $(VERSION)"
# Elimina carpetas previas
$(RMDIR) "$(RELEASE_FOLDER)"
@@ -258,7 +318,7 @@ linux_release: resources.pack
# Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)"
linux_release_desktop: resources.pack
linux_release_desktop: force_resource_pack
@echo "Creando release con integracion desktop para Linux - Version: $(VERSION)"
# Elimina carpetas previas
$(RMDIR) "$(RELEASE_FOLDER)"
@@ -362,7 +422,7 @@ raspi_debug:
@echo "Compilando version debug para Raspberry Pi: $(TARGET_NAME)_debug"
$(CXX) $(APP_SOURCES) $(INCLUDES) -DVERBOSE -DDEBUG $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
raspi_release: resources.pack
raspi_release: force_resource_pack
@echo "Creando release para Raspberry Pi - Version: $(VERSION)"
# Elimina carpetas previas
$(RMDIR) "$(RELEASE_FOLDER)"
@@ -387,7 +447,7 @@ raspi_release: resources.pack
# Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)"
anbernic: resources.pack
anbernic: force_resource_pack
@echo "Compilando para Anbernic: $(TARGET_NAME)"
# Elimina carpetas previas
$(RMDIR) "$(RELEASE_FOLDER)"_anbernic
@@ -426,6 +486,7 @@ help:
@echo " macos_release - Crear release completo para macOS (.dmg)"
@echo " pack_tool - Compilar herramienta de empaquetado"
@echo " resources.pack - Generar pack de recursos desde data/"
@echo " force_resource_pack - Regenerar resources.pack (usado por releases)"
@echo " show_version - Mostrar version actual ($(VERSION))"
@echo " help - Mostrar esta ayuda"

1079
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -1,218 +0,0 @@
# Plan de Refactorización - ViBe3 Physics Engine
## Objetivo
Aplicar el **Principio de Responsabilidad Única (SRP)** al motor Engine para:
- Mejorar mantenibilidad del código
- Facilitar extensión de funcionalidades
- Reducir acoplamiento entre sistemas
- Hacer el código más testeable
## Métricas Iniciales (Pre-refactorización)
- **engine.cpp**: 2341 líneas
- **engine.h**: 196 líneas con 40+ miembros privados
- **Responsabilidades mezcladas**: 7 subsistemas en una sola clase
## Progreso de Refactorización
### ✅ FASE 1: InputHandler (COMPLETADA)
**Fecha**: 10/01/2025
**Commit**: (pendiente)
**Impacto**: ~430 líneas extraídas del `handleEvents()`
**Archivos creados**:
- `source/input/input_handler.h`
- `source/input/input_handler.cpp`
**Métodos públicos agregados a Engine (24 total)**:
```cpp
// Gravedad y física
void pushBallsAwayFromGravity();
void handleGravityToggle();
void handleGravityDirectionChange(GravityDirection, const char*);
// Display y depuración
void toggleVSync();
void toggleDebug();
// Figuras 3D
void toggleShapeMode();
void activateShape(ShapeType, const char*);
void handleShapeScaleChange(bool);
void resetShapeScale();
void toggleDepthZoom();
// Temas de colores
void cycleTheme(bool);
void switchThemeByNumpad(int);
void toggleThemePage();
void pauseDynamicTheme();
// Sprites/Texturas
void switchTexture();
// Escenarios
void changeScenario(int, const char*);
// Zoom y fullscreen
void handleZoomIn();
void handleZoomOut();
void toggleFullscreen();
void toggleRealFullscreen();
void toggleIntegerScaling();
// Modos de aplicación
void toggleDemoMode();
void toggleDemoLiteMode();
void toggleLogoMode();
```
**Cambios internos**:
- Métodos internos renombrados con sufijo `Internal`:
- `toggleShapeMode()``toggleShapeModeInternal()`
- `activateShape()``activateShapeInternal()`
- `switchTexture()``switchTextureInternal()`
- Eliminado método `handleEvents()` (420 líneas)
- Bucle `run()` simplificado a 12 líneas
**Beneficios**:
- ✅ Engine desacoplado de eventos SDL
- ✅ InputHandler stateless (fácilmente testeable)
- ✅ Clara separación entre detección de input y ejecución de lógica
- ✅ Compilación exitosa sin errores
---
### 🔄 FASE 2: SceneManager (PENDIENTE)
**Impacto estimado**: ~500 líneas + `std::vector<Ball>` movido
**Responsabilidad**: Crear, actualizar y gestionar todas las `Ball`
**Miembros a mover**:
- `std::vector<std::unique_ptr<Ball>> balls_`
- `GravityDirection current_gravity_`
- `int scenario_`
**Métodos a mover**:
- `initBalls()`
- `pushBallsAwayFromGravity()`
- `switchBallsGravity()`
- `enableBallsGravityIfDisabled()`
- `forceBallsGravityOn() / Off()`
- `changeGravityDirection()`
- `updateBallSizes()`
---
### 🔄 FASE 3: UIManager (PENDIENTE)
**Impacto estimado**: ~300 líneas + rendering de texto movido
**Responsabilidad**: Renderizar y actualizar interfaz de usuario
**Miembros a mover**:
- `Notifier notifier_`
- `TextRenderer text_renderer_debug_`
- `bool show_debug_`
- Variables FPS (`fps_frame_count_`, `fps_current_`, `fps_text_`, `vsync_text_`)
**Métodos a mover**:
- `showNotificationForAction()`
- Renderizado de FPS, debug info, gravedad, tema, modo
---
### 🔄 FASE 4: StateManager (PENDIENTE)
**Impacto estimado**: ~600 líneas de lógica compleja
**Responsabilidad**: Gestionar máquina de estados (DEMO/LOGO/SANDBOX)
**Miembros a mover**:
- `AppMode current_app_mode_, previous_app_mode_`
- Variables DEMO (`demo_timer_`, `demo_next_action_time_`)
- Variables LOGO (todas las relacionadas con logo mode)
**Métodos a mover**:
- `setState()`
- `updateDemoMode()`
- `performDemoAction()`
- `randomizeOnDemoStart()`
- `enterLogoMode() / exitLogoMode()`
---
### 🔄 FASE 5: ShapeManager (PENDIENTE)
**Impacto estimado**: ~400 líneas + lógica de shapes
**Responsabilidad**: Crear, actualizar y renderizar figuras 3D polimórficas
**Miembros a mover**:
- `SimulationMode current_mode_`
- `ShapeType current_shape_type_, last_shape_type_`
- `std::unique_ptr<Shape> active_shape_`
- `float shape_scale_factor_`
- `bool depth_zoom_enabled_`
**Métodos a mover**:
- `toggleShapeModeInternal()`
- `activateShapeInternal()`
- `updateShape()`
- `generateShape()`
- `clampShapeScale()`
---
### 🔄 FASE 6: Limpieza y Consolidación Final (PENDIENTE)
**Impacto esperado**: Engine reducido a ~400 líneas (coordinador)
**Tareas**:
1. Limpiar `engine.h` / `engine.cpp` de código legacy
2. Verificar que todos los sistemas están correctamente integrados
3. Documentar interfaz pública de Engine
4. Actualizar `CLAUDE.md` con nueva arquitectura
5. Verificar compilación y funcionamiento completo
---
## Arquitectura Final Esperada
```cpp
class Engine {
private:
// SDL Core
SDL_Window* window_;
SDL_Renderer* renderer_;
// Componentes (composición)
std::unique_ptr<InputHandler> input_handler_;
std::unique_ptr<SceneManager> scene_manager_;
std::unique_ptr<UIManager> ui_manager_;
std::unique_ptr<StateManager> state_manager_;
std::unique_ptr<ShapeManager> shape_manager_;
std::unique_ptr<ThemeManager> theme_manager_;
// Estado mínimo
bool should_exit_;
float delta_time_;
public:
void run() {
while (!should_exit_) {
calculateDeltaTime();
input_handler_->process(*this);
update();
render();
}
}
};
```
## Notas
- Cada fase incluye su propio **commit atómico**
- Las fases son **secuenciales** (cada una depende de la anterior)
- Se preserva **100% de funcionalidad** en cada fase
- Compilación verificada después de cada commit
---
*Documento de seguimiento para refactorización ViBe3 Physics*
*Última actualización: 2025-01-10 - Fase 1 completada*

View File

@@ -1,184 +0,0 @@
# Engine Refactoring Summary
## Overview
Successful refactoring of `engine.cpp` (2341 → 1759 lines, -25%) following Single Responsibility Principle using facade/delegation pattern.
## Completed Phases
### Phase 1: InputHandler ✅
- **Lines extracted:** ~420 lines
- **Files created:**
- `source/input/input_handler.h`
- `source/input/input_handler.cpp`
- **Responsibility:** SDL event handling, keyboard/mouse input processing
- **Commit:** 7629c14
### Phase 2: SceneManager ✅
- **Lines extracted:** ~500 lines
- **Files created:**
- `source/scene/scene_manager.h`
- `source/scene/scene_manager.cpp`
- **Responsibility:** Ball physics, collision detection, gravity management, scenarios
- **Commit:** 71aea6e
### Phase 3: UIManager ✅
- **Lines extracted:** ~300 lines
- **Files created:**
- `source/ui/ui_manager.h`
- `source/ui/ui_manager.cpp`
- **Responsibility:** HUD rendering, FPS display, debug info, notifications
- **Commit:** e655c64
- **Note:** Moved AppMode enum to defines.h for global access
### Phase 4: StateManager ✅
- **Approach:** Facade/delegation pattern
- **Files created:**
- `source/state/state_manager.h`
- `source/state/state_manager.cpp`
- **Responsibility:** Application state machine (SANDBOX/DEMO/DEMO_LITE/LOGO)
- **Commits:** e2a60e4, e4636c8
- **Note:** StateManager maintains state, Engine keeps complex logic temporarily
### Phase 5: ShapeManager ✅
- **Approach:** Facade pattern (structure only)
- **Files created:**
- `source/shapes_mgr/shape_manager.h`
- `source/shapes_mgr/shape_manager.cpp`
- **Responsibility:** 3D shape management (sphere, cube, PNG shapes, etc.)
- **Commit:** 8be4c55
- **Note:** Stub implementation, full migration deferred
### Phase 6: Consolidation ✅
- **Result:** Engine acts as coordinator between components
- **Final metrics:**
- engine.cpp: 2341 → 1759 lines (-582 lines, -25%)
- engine.h: 237 → 205 lines (-32 lines, -13%)
## Architecture Pattern
**Facade/Delegation Hybrid:**
- Components maintain state and provide interfaces
- Engine delegates calls to components
- Complex logic remains in Engine temporarily (pragmatic approach)
- Allows future incremental migration without breaking functionality
## Component Composition
```cpp
class Engine {
private:
std::unique_ptr<InputHandler> input_handler_; // Input management
std::unique_ptr<SceneManager> scene_manager_; // Ball physics
std::unique_ptr<ShapeManager> shape_manager_; // 3D shapes
std::unique_ptr<StateManager> state_manager_; // App modes
std::unique_ptr<UIManager> ui_manager_; // UI/HUD
std::unique_ptr<ThemeManager> theme_manager_; // Color themes (pre-existing)
};
```
## Key Decisions
1. **Token Budget Constraint:** After Phase 3, pivoted from "full migration" to "facade pattern" to stay within 200k token budget
2. **Incremental Refactoring:** Each phase:
- Has atomic commit
- Compiles successfully
- Preserves 100% functionality
- Can be reviewed independently
3. **Pragmatic Approach:** Prioritized:
- Structural improvements over perfection
- Compilation success over complete migration
- Interface clarity over implementation relocation
## Benefits Achieved
**Separation of Concerns:** Clear component boundaries
**Testability:** Components can be unit tested independently
**Maintainability:** Smaller, focused files easier to navigate
**Extensibility:** New features can target specific components
**Readability:** Engine.cpp 25% smaller, easier to understand
**Compilation Speed:** Smaller translation units compile faster
## Future Work
### Deferred Migrations (Optional)
1. Complete StateManager logic migration (~600 lines)
2. Complete ShapeManager logic migration (~400 lines)
3. Remove duplicate state members from Engine
4. Extract ThemeManager to separate component (currently inline)
### Architectural Improvements
1. Consider event bus for component communication
2. Add observer pattern for state change notifications
3. Implement proper dependency injection
4. Add component lifecycle management
## Metrics
| Metric | Before | After | Change |
|--------|--------|-------|--------|
| engine.cpp | 2341 lines | 1759 lines | -582 (-25%) |
| engine.h | 237 lines | 205 lines | -32 (-13%) |
| Components | 1 (Engine) | 6 (Engine + 5 managers) | +5 |
| Files | 2 | 12 | +10 |
| Separation of concerns | ❌ Monolithic | ✅ Modular | ✅ |
## Post-Refactor Bug Fix
### Critical Crash: Nullptr Dereference (Commit 0fe2efc)
**Problem Discovered:**
- Refactor compiled successfully but crashed immediately at runtime
- Stack trace: `UIManager::updatePhysicalWindowSize()``Engine::updatePhysicalWindowSize()``Engine::initialize()`
- Root cause: `Engine::initialize()` line 228 called `updatePhysicalWindowSize()` BEFORE creating `ui_manager_` at line 232
**Solution Implemented:**
```cpp
// BEFORE (crashed):
updatePhysicalWindowSize(); // Calls ui_manager_->updatePhysicalWindowSize() → nullptr dereference
ui_manager_ = std::make_unique<UIManager>();
// AFTER (fixed):
int window_w = 0, window_h = 0;
SDL_GetWindowSizeInPixels(window_, &window_w, &window_h);
physical_window_width_ = window_w;
physical_window_height_ = window_h;
ui_manager_ = std::make_unique<UIManager>();
ui_manager_->initialize(renderer_, theme_manager_.get(), physical_window_width_, physical_window_height_);
```
**Additional Documentation:**
- Added comments to `engine.h` explaining pragmatic state duplication (Engine ↔ StateManager)
- Documented facade pattern stubs in `shape_manager.cpp` with rationale for each method
- Clarified future migration paths
**Verification:**
- ✅ Compilation successful
- ✅ Application runs without crashes
- ✅ All resources load correctly
- ✅ Initialization order corrected
## Verification
All phases verified with:
- ✅ Successful compilation (CMake + MinGW)
- ✅ No linker errors
- ✅ All components initialized correctly
- ✅ Engine runs as coordinator
- ✅ No runtime crashes (post-fix verification)
- ✅ Application executes successfully with all features functional
## Conclusion
Refactoring completed successfully within constraints:
- ✅ All 6 phases done
- ✅ 25% code reduction in engine.cpp
- ✅ Clean component architecture
- ✅ 100% functional preservation
- ✅ Critical crash bug fixed (commit 0fe2efc)
- ✅ Comprehensive documentation added
- ✅ Token budget respected (~65k / 200k used)
**Status:** COMPLETED AND VERIFIED ✅

View File

@@ -1,339 +0,0 @@
# ROADMAP - ViBe3 Physics
## Estado Actual ✅
### Figuras 3D (8/8 Completadas)
- ✅ Q - SPHERE (Esfera Fibonacci)
- ✅ W - WAVE_GRID (Malla ondeante) - ⚠️ Necesita mejora de movimiento
- ✅ E - HELIX (Espiral helicoidal)
- ✅ R - TORUS (Toroide/donut)
- ✅ T - CUBE (Cubo rotante)
- ✅ Y - CYLINDER (Cilindro) - ⚠️ Necesita rotación multi-eje
- ✅ U - ICOSAHEDRON (Icosaedro D20)
- ✅ I - ATOM (Núcleo + órbitas)
### Temas Visuales (7/7 Completadas)
- ✅ SUNSET (Atardecer)
- ✅ OCEAN (Océano)
- ✅ NEON (Neón vibrante)
- ✅ FOREST (Bosque)
- ✅ RGB (Círculo cromático matemático)
- ✅ MONOCHROME (Monocromo - blanco puro)
- ✅ LAVENDER (Lavanda - degradado violeta-azul, pelotas doradas)
### Sistemas de Presentación
- ✅ Transiciones LERP entre temas (0.5s suaves)
- ✅ Carga dinámica de texturas desde data/balls/
- ✅ Hot-swap de sprites con tecla N (cicla entre todas las texturas)
- ✅ PNG_SHAPE (O) - Logo "JAILGAMES" con rotación legible
---
## Mejoras de Presentación 🎨
### 1. ✅ Mejorar Animaciones de Figuras 3D
**Descripción:** Añadir movimientos más dinámicos e interesantes a algunas figuras
**Prioridad:** Media
**Estado:** ✅ COMPLETADO
**Detalles:**
#### CYLINDER (Y):
-**Rotación principal en eje Y** (spin horizontal continuo)
-**Tumbling ocasional en eje X** cada 3-5 segundos
- ✅ Transiciones suaves con ease-in-out (1.5s duración)
- ✅ Efecto visual: cilindro "se da una vuelta" ocasionalmente
#### WAVE_GRID (W):
-**Vista frontal paralela a pantalla** (sin rotación confusa)
-**Pivoteo sutil en ejes X e Y**
- ✅ Esquinas se mueven adelante/atrás según posición
- ✅ Movimiento ondulatorio + pivoteo = efecto "océano"
- ✅ Velocidades lentas (0.3-0.5 rad/s) para organicidad
### 2. ✅ Modo DEMO (Auto-play)
**Descripción:** Modo demostración automática con acciones aleatorias
**Prioridad:** Alta
**Estado:** ✅ COMPLETADO
**Detalles:**
- ✅ Toggle con tecla `D`
- ✅ Timer que ejecuta acciones cada 3-8 segundos (configurable)
- ✅ Acciones: gravedad, figuras, temas, escenarios, impulso, profundidad, escala, sprite
- ✅ Secuencia pseudo-aleatoria con pesos configurables (defines.h)
- ✅ Totalmente interactivo - usuario puede seguir usando controles
- ✅ Indicador visual "DEMO MODE" centrado en pantalla (naranja)
- ✅ Eliminado sistema auto-restart antiguo (ya no necesario)
### 3. ✅ Resolución Lógica Configurable
**Descripción:** Especificar resolución lógica por parámetros de línea de comandos
**Prioridad:** Media
**Estado:** ✅ COMPLETADO
**Detalles:**
- ✅ Parámetros `-w/--width <px>` y `-h/--height <px>`
- ✅ Parámetro `-f/--fullscreen` para pantalla completa
- ✅ Defaults: 1280x720 en ventana (si no se especifica)
- ✅ Validación: mínimo 640x480
- ✅ Help text con `--help`
- Ejemplo: `./vibe3_physics -w 1920 -h 1080 -f`
### 4. ✅ Implementar Modo Logo (Easter Egg)
**Descripción:** Modo especial que muestra el logo JAILGAMES como "marca de agua"
**Prioridad:** Alta (característica distintiva)
**Estado:** ✅ COMPLETADO
**Detalles:**
#### ✅ Configuración Modo Logo:
-**Figura:** Solo PNG_SHAPE (logo JAILGAMES)
-**Textura:** Siempre "tiny" (pelota más pequeña)
-**Tema:** Siempre MONOCHROME (blanco puro)
-**Escala:** 120% (figuras más grandes que normal)
-**Pelotas mínimas:** 500
-**Tecla manual:** K (activa/desactiva modo logo)
#### ✅ Comportamiento en Modo Logo:
- ✅ Alterna entre modo SHAPE y modo PHYSICS (como DEMO)
- ✅ Mantiene configuración fija (no cambia tema/textura/escala)
- ✅ Es como un "DEMO específico del logo"
#### ✅ Integración con DEMO LITE:
-**Requisitos para salto automático:**
- Mínimo 500 pelotas
- Tema MONOCHROME activo
- Si se cumplen → cambia automáticamente textura a "tiny" y escala a 120%
-**Duración:** Menos tiempo que DEMO normal (es un "recordatorio")
-**Después:** Vuelve a DEMO LITE normal
#### ✅ Integración con DEMO:
-**Requisitos:** Mínimo 500 pelotas
-**Acción:** Cambia automáticamente a: MONOCHROME + tiny + escala 120%
-**Duración:** Menos tiempo que acciones normales
-**Después:** Vuelve a DEMO normal
#### ✅ Proporción temporal sugerida:
- ✅ DEMO/DEMO_LITE normal: 80-90% del tiempo
- ✅ Modo Logo: 10-20% del tiempo (aparición ocasional como "easter egg")
### 5. ⏳ Mejorar Sistema de Vértices PNG_SHAPE
**Descripción:** Con 50 pelotas no activa modo vértices correctamente
**Prioridad:** Baja (mejora visual)
**Estimación:** 1 hora
**Detalles:**
- **Comportamiento actual:** Con 50 pelotas usa filas alternas en bordes
- **Comportamiento deseado:** Activar modo VÉRTICES (extremos izq/der de cada fila)
- **Problema:** Condición `num_points < 150` no es suficientemente agresiva
- **Solución propuesta:**
- Ajustar umbrales de activación de vértices
- Mejorar algoritmo extractCornerVertices() para detectar puntos clave
- Considerar densidad de píxeles en decisión (no solo cantidad absoluta)
### 5. 🐛 Corregir Escalado de Pelotas en Reposo
**Descripción:** Las pelotas cambian de tamaño cuando están quietas (bug visual)
**Prioridad:** Alta (bug visible)
**Estimación:** 30 minutos
**Detalles:**
- **Síntoma:** Pelotas en reposo (velocidad ≈ 0) se escalan incorrectamente
- **Posible causa:**
- Scale factor calculado desde velocidad o energía
- División por cero o valor muy pequeño
- Interpolación incorrecta en modo transición
- **Investigar:** Ball::render(), scale calculations, depth brightness
- **Solución esperada:** Tamaño constante independiente de velocidad
### 6. ✅ Sistema de Release
**Descripción:** Empaquetado para distribución standalone
**Prioridad:** Baja
**Estimación:** 30 minutos
**Estado:** ✅ COMPLETADO
**Detalles:**
- ✅ Carpeta `release/` con recursos
- ✅ ResourcePack sistema de empaquetado binario (VBE3 format)
- ✅ Tool `pack_resources` para generar resources.pack
- ✅ SDL3.dll incluido en release
- ✅ Carga híbrida: resources.pack con fallback a data/
- ✅ Target `make windows_release` en Makefile
- ✅ ZIP generado: vibe3_physics-YYYY-MM-DD-win32-x64.zip
### 7. ⏳ Logo/Autor Sobreimpreso (Watermark)
**Descripción:** Mostrar logo JAILGAMES en esquina con animación periódica
**Prioridad:** Media
**Estimación:** 2 horas
**Detalles:**
- **Posición:** Esquina inferior derecha (o configurable)
- **Aparición:** Cada X segundos (ej: cada 30-60s)
- **Animación entrada:** Fade-in + slide desde fuera de pantalla
- **Duración visible:** 3-5 segundos
- **Animación salida:** Fade-out + slide hacia fuera
- **Rendering:** Textura PNG con alpha blending
- **Configuración:**
- Intervalo de aparición (LOGO_WATERMARK_INTERVAL)
- Duración visible (LOGO_WATERMARK_DURATION)
- Tamaño relativo a pantalla (ej: 10-15% ancho)
- Opacidad máxima (ej: 70-80% alpha)
- **Integración:** No interfiere con debug display ni modos DEMO/LOGO
- **Asset:** Reutilizar data/jailgames_logo.png existente
### 8. ⏳ Mejorar Sistema de Renderizado de Texto
**Descripción:** Actualizar tipografía y mejorar clase dbgtxt para mejor legibilidad
**Prioridad:** Media
**Estimación:** 3-4 horas
**Detalles:**
- **Problemas actuales:**
- Fuente bitmap actual poco legible en resoluciones altas
- Sistema dbgtxt limitado (solo fuente fija)
- Sin suavizado (aliasing visible)
- Tamaño no escala con resolución
- **Soluciones propuestas:**
- **Opción A - SDL_ttf:** Usar fuentes TrueType (.ttf)
- Mayor calidad y escalabilidad
- Antialiasing nativo
- Soporte Unicode completo
- Requiere añadir dependencia SDL3_ttf
- **Opción B - Bitmap mejorada:** Nueva fuente bitmap de mayor calidad
- Sin dependencias adicionales
- Textura PNG con caracteres ASCII
- Escalado nearest-neighbor para estética pixel-art
- Más control sobre aspecto retro
- **Mejoras clase dbgtxt:**
- Soporte múltiples tamaños (pequeño/normal/grande)
- Sombra/outline configurable para mejor contraste
- Alineación (izquierda/centro/derecha)
- Color y alpha por texto individual
- Medición de ancho de texto (para centrado dinámico)
- **Assets necesarios:**
- Si TTF: Fuente .ttf embebida (ej: Roboto Mono, Source Code Pro)
- Si Bitmap: Nueva textura font_atlas.png de mayor resolución
- **Retrocompatibilidad:** Mantener API actual de dbgtxt
### 9. ⏳ Temas Dinámicos (Color Generativo)
**Descripción:** Sistema de generación procedural de temas de colores
**Prioridad:** Baja
**Estimación:** 4-6 horas
**Detalles:**
- **Objetivos:**
- Gradiente de fondo variable (color base + variaciones automáticas)
- Color de pelotas variable (monocromático, complementario, análogo, etc.)
- Generación algorítmica de paletas armónicas
- **Implementación propuesta:**
- **HSV Color Space:** Generar paletas en espacio HSV para control intuitivo
- Hue (matiz): 0-360° para variación de color
- Saturation (saturación): 0-100% para intensidad
- Value (brillo): 0-100% para luminosidad
- **Esquemas de color:**
- Monocromático (un matiz + variaciones de saturación/brillo)
- Complementario (matiz opuesto en rueda de color)
- Análogo (matices adyacentes ±30°)
- Triádico (3 matices equidistantes 120°)
- Tetrádico (4 matices en cuadrado/rectángulo)
- **Parámetros configurables:**
- Base hue (0-360°): Color principal del tema
- Saturation range (0-100%): Rango de intensidad
- Value range (0-100%): Rango de brillo
- Esquema de armonía: mono/complementario/análogo/etc.
- Gradiente de fondo: Automático según base hue
- **Controles de usuario:**
- Tecla G: Generar nuevo tema aleatorio
- Tecla Shift+G: Ciclar esquemas de armonía
- Guardar temas generados favoritos (slot 8-12)
- **Algoritmos:**
- **Gradiente de fondo:** Base hue → Variación análoga oscura (fondo inferior)
- **Color de pelotas:** Según esquema elegido (complementario para contraste máximo)
- **Conversión HSV → RGB:** Algoritmo estándar para SDL rendering
- **Ejemplos de temas generados:**
- Tema "Cyberpunk": Base cyan (180°) + Complementario magenta (300°)
- Tema "Autumn": Base naranja (30°) + Análogo rojo-amarillo
- Tema "Ocean Deep": Base azul (240°) + Monocromático variaciones
- Tema "Toxic": Base verde lima (120°) + Complementario púrpura
- **Persistencia:**
- Guardar temas generados en config.ini (opcional)
- Teclas 8-9-0 para slots de temas custom
- **Compatibilidad:**
- No reemplaza temas existentes (1-7)
- Modo adicional de selección de tema
---
## Futuras Mejoras (Ideas)
### Performance
- [ ] Spatial partitioning para colisiones ball-to-ball
- [ ] Level-of-detail para 100K+ pelotas
- [ ] GPU compute shaders para física masiva
### Efectos Visuales
- [ ] Trails (estelas de movimiento)
- [ ] Heatmaps de velocidad/energía
- [ ] Bloom/glow para sprites
### Física Avanzada
- [ ] Colisiones entre partículas
- [ ] Viento (fuerza horizontal)
- [ ] Campos magnéticos (atracción/repulsión)
- [ ] Turbulencia
### Shapes PNG
- [ ] **Voxelización 3D para PNG_SHAPE** (Enfoque B)
- Actualmente: Extrusión 2D simple (píxeles → capas Z)
- Futuro: Voxelización real con detección de interior/exterior
- Permite formas 3D más complejas desde imágenes
- Rotación volumétrica en vez de extrusión plana
### Interactividad
- [ ] Mouse: click para aplicar fuerzas
- [ ] Mouse: drag para crear campos
- [ ] Mouse wheel: ajustar intensidad
---
## Historial de Cambios
### 2025-10-04 (Sesión 5) - PNG Shape + Texturas Dinámicas + CLI
-**PNG_SHAPE implementado** - Tecla O para activar logo "JAILGAMES"
- ✅ Carga de PNG 1-bit con stb_image
- ✅ Extrusión 2D (Enfoque A) - píxeles → capas Z
- ✅ Detección de bordes vs relleno completo (configurable)
- ✅ Tamaño 80% pantalla (como otras figuras)
- ✅ Rotación "legible" - De frente con volteretas ocasionales (3-8s idle)
- ✅ Fix: Z forzado a máximo cuando está de frente (texto brillante)
- ✅ Excluido de DEMO/DEMO_LITE (logo especial)
-**Sistema de texturas dinámicas** - Carga automática desde data/balls/
- ✅ Tecla N cicla entre todas las texturas PNG encontradas
- ✅ Orden alfabético con normal.png primero por defecto
- ✅ Display dinámico del nombre de textura (uppercase)
-**Física mejorada SHAPE** - Constantes separadas de ROTOBALL
- ✅ Pegajosidad 2.67x mayor (SPRING_K=800 vs 300)
- ✅ Pelotas se adhieren mejor durante rotaciones rápidas
-**Parámetros de línea de comandos** - `-w/-h/-f/--help`
- ✅ Resolución configurable (mínimo 640x480)
- 📝 Preparado para voxelización 3D (Enfoque B) en futuro
### 2025-10-04 (Sesión 4) - Modo DEMO + Mejoras Animaciones
-**Implementado Modo DEMO (auto-play)** - Tecla D para toggle
- ✅ Sistema de acciones aleatorias cada 3-8 segundos (configurable)
- ✅ 8 tipos de acciones con pesos de probabilidad ajustables
- ✅ Totalmente interactivo - usuario puede seguir controlando
- ✅ Display visual "DEMO MODE" centrado en naranja
-**Mejoras animaciones 3D**: tumbling en cilindro + pivoteo en wave grid
- ✅ CYLINDER: tumbling ocasional en eje X cada 3-5s con ease-in-out
- ✅ WAVE_GRID: pivoteo sutil paralelo a pantalla (efecto océano)
- ❌ Eliminado sistema auto-restart antiguo (ya no necesario)
### 2025-10-04 (Sesión 3)
- ✅ Implementado tema MONOCHROME (6º tema)
- ✅ Sistema LERP para transiciones suaves de temas (0.5s)
- ✅ Hot-swap de sprites con tecla N (ball.png ↔ ball_small.png)
- ✅ Tamaño dinámico de pelotas desde texture->getWidth()
- ✅ Ajuste de posiciones inteligente al cambiar sprite
- 📝 Añadidas mejoras propuestas para CYLINDER y WAVE_GRID
### 2025-10-03 (Sesión 2)
- ✅ Implementadas 8 figuras 3D (SPHERE, WAVE_GRID, HELIX, TORUS, CUBE, CYLINDER, ICOSAHEDRON, ATOM)
- ✅ Sistema polimórfico de shapes con herencia virtual
### 2025-10-02 (Sesión 1)
- ✅ Migración desde vibe1_delta
- ✅ Sistema de gravedad direccional (4 direcciones)
- ✅ Coeficientes de rebote variables (0.30-0.95)
- ✅ 5 temas de colores iniciales
---
**Última actualización:** 2025-10-04

BIN
data/logo/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
data/logo/logo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

View File

@@ -29,7 +29,7 @@
<key>CSResourcesFileMapped</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<string>12.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSHumanReadableCopyright</key>

BIN
release/dll/SDL3.dll Normal file

Binary file not shown.

BIN
release/dll/SDL3_ttf.dll Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>SDL3_ttf.framework/SDL3_ttf</string>
<key>LibraryIdentifier</key>
<string>tvos-arm64</string>
<key>LibraryPath</key>
<string>SDL3_ttf.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>tvos</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>SDL3_ttf.framework/SDL3_ttf</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>SDL3_ttf.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>SDL3_ttf.framework/SDL3_ttf</string>
<key>LibraryIdentifier</key>
<string>tvos-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>SDL3_ttf.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>tvos</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>SDL3_ttf.framework/SDL3_ttf</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>SDL3_ttf.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>SDL3_ttf.framework/Versions/A/SDL3_ttf</string>
<key>LibraryIdentifier</key>
<string>macos-arm64_x86_64</string>
<key>LibraryPath</key>
<string>SDL3_ttf.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>macos</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@@ -0,0 +1,181 @@
/*
SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts
Copyright (C) 2001-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_textengine.h
*
* Definitions for implementations of the TTF_TextEngine interface.
*/
#ifndef SDL_TTF_TEXTENGINE_H_
#define SDL_TTF_TEXTENGINE_H_
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* A font atlas draw command.
*
* \since This enum is available since SDL_ttf 3.0.0.
*/
typedef enum TTF_DrawCommand
{
TTF_DRAW_COMMAND_NOOP,
TTF_DRAW_COMMAND_FILL,
TTF_DRAW_COMMAND_COPY
} TTF_DrawCommand;
/**
* A filled rectangle draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_FillOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_FILL */
SDL_Rect rect; /**< The rectangle to fill, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
} TTF_FillOperation;
/**
* A texture copy draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_CopyOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_COPY */
int text_offset; /**< The offset in the text corresponding to this glyph.
There may be multiple glyphs with the same text offset
and the next text offset might be several Unicode codepoints
later. In this case the glyphs and codepoints are grouped
together and the group bounding box is the union of the dst
rectangles for the corresponding glyphs. */
TTF_Font *glyph_font; /**< The font containing the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
Uint32 glyph_index; /**< The glyph index of the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
SDL_Rect src; /**< The area within the glyph to be drawn */
SDL_Rect dst; /**< The drawing coordinates of the glyph, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
void *reserved;
} TTF_CopyOperation;
/**
* A text engine draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*/
typedef union TTF_DrawOperation
{
TTF_DrawCommand cmd;
TTF_FillOperation fill;
TTF_CopyOperation copy;
} TTF_DrawOperation;
/* Private data in TTF_Text, to assist in text measurement and layout */
typedef struct TTF_TextLayout TTF_TextLayout;
/* Private data in TTF_Text, available to implementations */
struct TTF_TextData
{
TTF_Font *font; /**< The font used by this text, read-only. */
SDL_FColor color; /**< The color of the text, read-only. */
bool needs_layout_update; /**< True if the layout needs to be updated */
TTF_TextLayout *layout; /**< Cached layout information, read-only. */
int x; /**< The x offset of the upper left corner of this text, in pixels, read-only. */
int y; /**< The y offset of the upper left corner of this text, in pixels, read-only. */
int w; /**< The width of this text, in pixels, read-only. */
int h; /**< The height of this text, in pixels, read-only. */
int num_ops; /**< The number of drawing operations to render this text, read-only. */
TTF_DrawOperation *ops; /**< The drawing operations used to render this text, read-only. */
int num_clusters; /**< The number of substrings representing clusters of glyphs in the string, read-only */
TTF_SubString *clusters; /**< Substrings representing clusters of glyphs in the string, read-only */
SDL_PropertiesID props; /**< Custom properties associated with this text, read-only. This field is created as-needed using TTF_GetTextProperties() and the properties may be then set and read normally */
bool needs_engine_update; /**< True if the engine text needs to be updated */
TTF_TextEngine *engine; /**< The engine used to render this text, read-only. */
void *engine_text; /**< The implementation-specific representation of this text */
};
/**
* A text engine interface.
*
* This structure should be initialized using SDL_INIT_INTERFACE()
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa SDL_INIT_INTERFACE
*/
struct TTF_TextEngine
{
Uint32 version; /**< The version of this interface */
void *userdata; /**< User data pointer passed to callbacks */
/* Create a text representation from draw instructions.
*
* All fields of `text` except `internal->engine_text` will already be filled out.
*
* This function should set the `internal->engine_text` field to a non-NULL value.
*
* \param userdata the userdata pointer in this interface.
* \param text the text object being created.
*/
bool (SDLCALL *CreateText)(void *userdata, TTF_Text *text);
/**
* Destroy a text representation.
*/
void (SDLCALL *DestroyText)(void *userdata, TTF_Text *text);
};
/* Check the size of TTF_TextEngine
*
* If this assert fails, either the compiler is padding to an unexpected size,
* or the interface has been updated and this should be updated to match and
* the code using this interface should be updated to handle the old version.
*/
SDL_COMPILE_TIME_ASSERT(TTF_TextEngine_SIZE,
(sizeof(void *) == 4 && sizeof(TTF_TextEngine) == 16) ||
(sizeof(void *) == 8 && sizeof(TTF_TextEngine) == 32));
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_TTF_TEXTENGINE_H_ */

View File

@@ -0,0 +1,35 @@
# Using this package
This package contains SDL_ttf built for Xcode.
To use this package in Xcode, drag `SDL3_ttf.framework` into your project.
# Documentation
An API reference and additional documentation is available at:
https://wiki.libsdl.org/SDL3_ttf
# Discussions
## Discord
You can join the official Discord server at:
https://discord.com/invite/BwpFGBWsv8
## Forums/mailing lists
You can join SDL development discussions at:
https://discourse.libsdl.org/
Once you sign up, you can use the forum through the website or as a mailing list from your email client.
## Announcement list
You can sign up for the low traffic announcement list at:
https://www.libsdl.org/mailing-list.php

View File

@@ -0,0 +1,17 @@
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -0,0 +1,23 @@
SDL_ttf 3.0
This library is a wrapper around the FreeType and Harfbuzz libraries, allowing you to use TrueType fonts to render text in SDL applications.
The latest version of this library is available from GitHub:
https://github.com/libsdl-org/SDL_ttf/releases
Installation instructions and a quick introduction is available in
[INSTALL.md](INSTALL.md)
This library is distributed under the terms of the zlib license,
available in [LICENSE.txt](LICENSE.txt).
This library also uses the following libraries:
- FreeType, licensed under the [FTL](https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT)
- HarfBuzz, licensed under the [MIT license](https://github.com/harfbuzz/harfbuzz/blob/main/COPYING)
- PlutoSVG, licensed under the [MIT license](https://github.com/sammycage/plutosvg/blob/master/LICENSE)
- PlutoVG, licensed under the [MIT license](https://github.com/sammycage/plutovg/blob/master/LICENSE)
Enjoy!
Sam Lantinga (slouken@libsdl.org)

View File

@@ -0,0 +1,181 @@
/*
SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts
Copyright (C) 2001-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_textengine.h
*
* Definitions for implementations of the TTF_TextEngine interface.
*/
#ifndef SDL_TTF_TEXTENGINE_H_
#define SDL_TTF_TEXTENGINE_H_
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* A font atlas draw command.
*
* \since This enum is available since SDL_ttf 3.0.0.
*/
typedef enum TTF_DrawCommand
{
TTF_DRAW_COMMAND_NOOP,
TTF_DRAW_COMMAND_FILL,
TTF_DRAW_COMMAND_COPY
} TTF_DrawCommand;
/**
* A filled rectangle draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_FillOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_FILL */
SDL_Rect rect; /**< The rectangle to fill, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
} TTF_FillOperation;
/**
* A texture copy draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_CopyOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_COPY */
int text_offset; /**< The offset in the text corresponding to this glyph.
There may be multiple glyphs with the same text offset
and the next text offset might be several Unicode codepoints
later. In this case the glyphs and codepoints are grouped
together and the group bounding box is the union of the dst
rectangles for the corresponding glyphs. */
TTF_Font *glyph_font; /**< The font containing the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
Uint32 glyph_index; /**< The glyph index of the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
SDL_Rect src; /**< The area within the glyph to be drawn */
SDL_Rect dst; /**< The drawing coordinates of the glyph, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
void *reserved;
} TTF_CopyOperation;
/**
* A text engine draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*/
typedef union TTF_DrawOperation
{
TTF_DrawCommand cmd;
TTF_FillOperation fill;
TTF_CopyOperation copy;
} TTF_DrawOperation;
/* Private data in TTF_Text, to assist in text measurement and layout */
typedef struct TTF_TextLayout TTF_TextLayout;
/* Private data in TTF_Text, available to implementations */
struct TTF_TextData
{
TTF_Font *font; /**< The font used by this text, read-only. */
SDL_FColor color; /**< The color of the text, read-only. */
bool needs_layout_update; /**< True if the layout needs to be updated */
TTF_TextLayout *layout; /**< Cached layout information, read-only. */
int x; /**< The x offset of the upper left corner of this text, in pixels, read-only. */
int y; /**< The y offset of the upper left corner of this text, in pixels, read-only. */
int w; /**< The width of this text, in pixels, read-only. */
int h; /**< The height of this text, in pixels, read-only. */
int num_ops; /**< The number of drawing operations to render this text, read-only. */
TTF_DrawOperation *ops; /**< The drawing operations used to render this text, read-only. */
int num_clusters; /**< The number of substrings representing clusters of glyphs in the string, read-only */
TTF_SubString *clusters; /**< Substrings representing clusters of glyphs in the string, read-only */
SDL_PropertiesID props; /**< Custom properties associated with this text, read-only. This field is created as-needed using TTF_GetTextProperties() and the properties may be then set and read normally */
bool needs_engine_update; /**< True if the engine text needs to be updated */
TTF_TextEngine *engine; /**< The engine used to render this text, read-only. */
void *engine_text; /**< The implementation-specific representation of this text */
};
/**
* A text engine interface.
*
* This structure should be initialized using SDL_INIT_INTERFACE()
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa SDL_INIT_INTERFACE
*/
struct TTF_TextEngine
{
Uint32 version; /**< The version of this interface */
void *userdata; /**< User data pointer passed to callbacks */
/* Create a text representation from draw instructions.
*
* All fields of `text` except `internal->engine_text` will already be filled out.
*
* This function should set the `internal->engine_text` field to a non-NULL value.
*
* \param userdata the userdata pointer in this interface.
* \param text the text object being created.
*/
bool (SDLCALL *CreateText)(void *userdata, TTF_Text *text);
/**
* Destroy a text representation.
*/
void (SDLCALL *DestroyText)(void *userdata, TTF_Text *text);
};
/* Check the size of TTF_TextEngine
*
* If this assert fails, either the compiler is padding to an unexpected size,
* or the interface has been updated and this should be updated to match and
* the code using this interface should be updated to handle the old version.
*/
SDL_COMPILE_TIME_ASSERT(TTF_TextEngine_SIZE,
(sizeof(void *) == 4 && sizeof(TTF_TextEngine) == 16) ||
(sizeof(void *) == 8 && sizeof(TTF_TextEngine) == 32));
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_TTF_TEXTENGINE_H_ */

View File

@@ -0,0 +1,35 @@
# Using this package
This package contains SDL_ttf built for Xcode.
To use this package in Xcode, drag `SDL3_ttf.framework` into your project.
# Documentation
An API reference and additional documentation is available at:
https://wiki.libsdl.org/SDL3_ttf
# Discussions
## Discord
You can join the official Discord server at:
https://discord.com/invite/BwpFGBWsv8
## Forums/mailing lists
You can join SDL development discussions at:
https://discourse.libsdl.org/
Once you sign up, you can use the forum through the website or as a mailing list from your email client.
## Announcement list
You can sign up for the low traffic announcement list at:
https://www.libsdl.org/mailing-list.php

View File

@@ -0,0 +1,17 @@
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -0,0 +1,23 @@
SDL_ttf 3.0
This library is a wrapper around the FreeType and Harfbuzz libraries, allowing you to use TrueType fonts to render text in SDL applications.
The latest version of this library is available from GitHub:
https://github.com/libsdl-org/SDL_ttf/releases
Installation instructions and a quick introduction is available in
[INSTALL.md](INSTALL.md)
This library is distributed under the terms of the zlib license,
available in [LICENSE.txt](LICENSE.txt).
This library also uses the following libraries:
- FreeType, licensed under the [FTL](https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT)
- HarfBuzz, licensed under the [MIT license](https://github.com/harfbuzz/harfbuzz/blob/main/COPYING)
- PlutoSVG, licensed under the [MIT license](https://github.com/sammycage/plutosvg/blob/master/LICENSE)
- PlutoVG, licensed under the [MIT license](https://github.com/sammycage/plutovg/blob/master/LICENSE)
Enjoy!
Sam Lantinga (slouken@libsdl.org)

View File

@@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>CMake/SDL3_ttfConfig.cmake</key>
<data>
V6UpWQTvr/puOrlm1sgAs6fktNA=
</data>
<key>CMake/SDL3_ttfConfigVersion.cmake</key>
<data>
WW2xmNHZyYr9y3/8uAylJuutcPw=
</data>
<key>Headers/SDL_textengine.h</key>
<data>
7QAtKpC/pLIq6TK3F59Ax1hg3tc=
</data>
<key>Headers/SDL_ttf.h</key>
<data>
90S4SFzJy1lUuMotaCRWpTbzRa4=
</data>
<key>INSTALL.md</key>
<data>
3kA+9HE5dF7+nyypVt5YOfU+Uho=
</data>
<key>Info.plist</key>
<data>
9F72T63xvwi9u+kC0p9N011q3IM=
</data>
<key>LICENSE.txt</key>
<data>
dp6e8JHkl0CrYD+oe2IXZfWB/iw=
</data>
<key>README.md</key>
<data>
lm034L4zWKPElKb9O2dmehurfFQ=
</data>
</dict>
<key>files2</key>
<dict>
<key>CMake/SDL3_ttfConfig.cmake</key>
<dict>
<key>hash2</key>
<data>
VpwUT/D8TjpLXBguVImWqsMkqni9HXiIzx91C92Krqc=
</data>
</dict>
<key>CMake/SDL3_ttfConfigVersion.cmake</key>
<dict>
<key>hash2</key>
<data>
tb1RnDTj72GQOzcXp6FPtiqW8tSD886UyUY09c1Ms/U=
</data>
</dict>
<key>Headers/SDL_textengine.h</key>
<dict>
<key>hash2</key>
<data>
Uk27FTzsWoYySpKM1gkwCB/svSxscGViuMzca93gLP8=
</data>
</dict>
<key>Headers/SDL_ttf.h</key>
<dict>
<key>hash2</key>
<data>
6bsCCUp3Uc3tCp+0Xxw7Tt01+UV8bra5YN1dFjpRBL0=
</data>
</dict>
<key>INSTALL.md</key>
<dict>
<key>hash2</key>
<data>
Jq9GEmdnFRmUTNnYYZZ+5mFqqrMelD86Gthhyi2kGJQ=
</data>
</dict>
<key>LICENSE.txt</key>
<dict>
<key>hash2</key>
<data>
eCbsoKD35ZHzjdhE4geiAKrIGlmyDYoww6+MYoKvE+Y=
</data>
</dict>
<key>README.md</key>
<dict>
<key>hash2</key>
<data>
6aipppbEU7MEd3x9OHnKqAGyFXVYiSAL8X8lm271U00=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1 @@
Versions/Current/Headers

View File

@@ -0,0 +1 @@
Versions/Current/Resources

View File

@@ -0,0 +1 @@
Versions/Current/SDL3_ttf

View File

@@ -0,0 +1,181 @@
/*
SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts
Copyright (C) 2001-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_textengine.h
*
* Definitions for implementations of the TTF_TextEngine interface.
*/
#ifndef SDL_TTF_TEXTENGINE_H_
#define SDL_TTF_TEXTENGINE_H_
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* A font atlas draw command.
*
* \since This enum is available since SDL_ttf 3.0.0.
*/
typedef enum TTF_DrawCommand
{
TTF_DRAW_COMMAND_NOOP,
TTF_DRAW_COMMAND_FILL,
TTF_DRAW_COMMAND_COPY
} TTF_DrawCommand;
/**
* A filled rectangle draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_FillOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_FILL */
SDL_Rect rect; /**< The rectangle to fill, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
} TTF_FillOperation;
/**
* A texture copy draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_CopyOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_COPY */
int text_offset; /**< The offset in the text corresponding to this glyph.
There may be multiple glyphs with the same text offset
and the next text offset might be several Unicode codepoints
later. In this case the glyphs and codepoints are grouped
together and the group bounding box is the union of the dst
rectangles for the corresponding glyphs. */
TTF_Font *glyph_font; /**< The font containing the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
Uint32 glyph_index; /**< The glyph index of the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
SDL_Rect src; /**< The area within the glyph to be drawn */
SDL_Rect dst; /**< The drawing coordinates of the glyph, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
void *reserved;
} TTF_CopyOperation;
/**
* A text engine draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*/
typedef union TTF_DrawOperation
{
TTF_DrawCommand cmd;
TTF_FillOperation fill;
TTF_CopyOperation copy;
} TTF_DrawOperation;
/* Private data in TTF_Text, to assist in text measurement and layout */
typedef struct TTF_TextLayout TTF_TextLayout;
/* Private data in TTF_Text, available to implementations */
struct TTF_TextData
{
TTF_Font *font; /**< The font used by this text, read-only. */
SDL_FColor color; /**< The color of the text, read-only. */
bool needs_layout_update; /**< True if the layout needs to be updated */
TTF_TextLayout *layout; /**< Cached layout information, read-only. */
int x; /**< The x offset of the upper left corner of this text, in pixels, read-only. */
int y; /**< The y offset of the upper left corner of this text, in pixels, read-only. */
int w; /**< The width of this text, in pixels, read-only. */
int h; /**< The height of this text, in pixels, read-only. */
int num_ops; /**< The number of drawing operations to render this text, read-only. */
TTF_DrawOperation *ops; /**< The drawing operations used to render this text, read-only. */
int num_clusters; /**< The number of substrings representing clusters of glyphs in the string, read-only */
TTF_SubString *clusters; /**< Substrings representing clusters of glyphs in the string, read-only */
SDL_PropertiesID props; /**< Custom properties associated with this text, read-only. This field is created as-needed using TTF_GetTextProperties() and the properties may be then set and read normally */
bool needs_engine_update; /**< True if the engine text needs to be updated */
TTF_TextEngine *engine; /**< The engine used to render this text, read-only. */
void *engine_text; /**< The implementation-specific representation of this text */
};
/**
* A text engine interface.
*
* This structure should be initialized using SDL_INIT_INTERFACE()
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa SDL_INIT_INTERFACE
*/
struct TTF_TextEngine
{
Uint32 version; /**< The version of this interface */
void *userdata; /**< User data pointer passed to callbacks */
/* Create a text representation from draw instructions.
*
* All fields of `text` except `internal->engine_text` will already be filled out.
*
* This function should set the `internal->engine_text` field to a non-NULL value.
*
* \param userdata the userdata pointer in this interface.
* \param text the text object being created.
*/
bool (SDLCALL *CreateText)(void *userdata, TTF_Text *text);
/**
* Destroy a text representation.
*/
void (SDLCALL *DestroyText)(void *userdata, TTF_Text *text);
};
/* Check the size of TTF_TextEngine
*
* If this assert fails, either the compiler is padding to an unexpected size,
* or the interface has been updated and this should be updated to match and
* the code using this interface should be updated to handle the old version.
*/
SDL_COMPILE_TIME_ASSERT(TTF_TextEngine_SIZE,
(sizeof(void *) == 4 && sizeof(TTF_TextEngine) == 16) ||
(sizeof(void *) == 8 && sizeof(TTF_TextEngine) == 32));
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_TTF_TEXTENGINE_H_ */

View File

@@ -0,0 +1,35 @@
# Using this package
This package contains SDL_ttf built for Xcode.
To use this package in Xcode, drag `SDL3_ttf.framework` into your project.
# Documentation
An API reference and additional documentation is available at:
https://wiki.libsdl.org/SDL3_ttf
# Discussions
## Discord
You can join the official Discord server at:
https://discord.com/invite/BwpFGBWsv8
## Forums/mailing lists
You can join SDL development discussions at:
https://discourse.libsdl.org/
Once you sign up, you can use the forum through the website or as a mailing list from your email client.
## Announcement list
You can sign up for the low traffic announcement list at:
https://www.libsdl.org/mailing-list.php

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>23H420</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>SDL3_ttf</string>
<key>CFBundleIdentifier</key>
<string>org.libsdl.SDL3-ttf</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>SDL3_ttf</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.2.2</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>3.2.2</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string></string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>14.5</string>
<key>DTSDKBuild</key>
<string>23F73</string>
<key>DTSDKName</key>
<string>macosx14.5</string>
<key>DTXcode</key>
<string>1540</string>
<key>DTXcodeBuild</key>
<string>15F31d</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
</dict>
</plist>

View File

@@ -0,0 +1,17 @@
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -0,0 +1,23 @@
SDL_ttf 3.0
This library is a wrapper around the FreeType and Harfbuzz libraries, allowing you to use TrueType fonts to render text in SDL applications.
The latest version of this library is available from GitHub:
https://github.com/libsdl-org/SDL_ttf/releases
Installation instructions and a quick introduction is available in
[INSTALL.md](INSTALL.md)
This library is distributed under the terms of the zlib license,
available in [LICENSE.txt](LICENSE.txt).
This library also uses the following libraries:
- FreeType, licensed under the [FTL](https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT)
- HarfBuzz, licensed under the [MIT license](https://github.com/harfbuzz/harfbuzz/blob/main/COPYING)
- PlutoSVG, licensed under the [MIT license](https://github.com/sammycage/plutosvg/blob/master/LICENSE)
- PlutoVG, licensed under the [MIT license](https://github.com/sammycage/plutovg/blob/master/LICENSE)
Enjoy!
Sam Lantinga (slouken@libsdl.org)

View File

@@ -0,0 +1,197 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Resources/CMake/SDL3_ttfConfig.cmake</key>
<data>
V6UpWQTvr/puOrlm1sgAs6fktNA=
</data>
<key>Resources/CMake/SDL3_ttfConfigVersion.cmake</key>
<data>
WW2xmNHZyYr9y3/8uAylJuutcPw=
</data>
<key>Resources/INSTALL.md</key>
<data>
3kA+9HE5dF7+nyypVt5YOfU+Uho=
</data>
<key>Resources/Info.plist</key>
<data>
Q+NCd9YwE/D/Y4ptVnrhOldXz6U=
</data>
<key>Resources/LICENSE.txt</key>
<data>
dp6e8JHkl0CrYD+oe2IXZfWB/iw=
</data>
<key>Resources/README.md</key>
<data>
lm034L4zWKPElKb9O2dmehurfFQ=
</data>
</dict>
<key>files2</key>
<dict>
<key>Headers/SDL_textengine.h</key>
<dict>
<key>hash2</key>
<data>
Uk27FTzsWoYySpKM1gkwCB/svSxscGViuMzca93gLP8=
</data>
</dict>
<key>Headers/SDL_ttf.h</key>
<dict>
<key>hash2</key>
<data>
6bsCCUp3Uc3tCp+0Xxw7Tt01+UV8bra5YN1dFjpRBL0=
</data>
</dict>
<key>Resources/CMake/SDL3_ttfConfig.cmake</key>
<dict>
<key>hash2</key>
<data>
VpwUT/D8TjpLXBguVImWqsMkqni9HXiIzx91C92Krqc=
</data>
</dict>
<key>Resources/CMake/SDL3_ttfConfigVersion.cmake</key>
<dict>
<key>hash2</key>
<data>
tb1RnDTj72GQOzcXp6FPtiqW8tSD886UyUY09c1Ms/U=
</data>
</dict>
<key>Resources/INSTALL.md</key>
<dict>
<key>hash2</key>
<data>
Jq9GEmdnFRmUTNnYYZZ+5mFqqrMelD86Gthhyi2kGJQ=
</data>
</dict>
<key>Resources/Info.plist</key>
<dict>
<key>hash2</key>
<data>
LwUSgLeBsUUT/M3w+W5AAfTziViNTWX1o7Ly+x3J2u0=
</data>
</dict>
<key>Resources/LICENSE.txt</key>
<dict>
<key>hash2</key>
<data>
eCbsoKD35ZHzjdhE4geiAKrIGlmyDYoww6+MYoKvE+Y=
</data>
</dict>
<key>Resources/README.md</key>
<dict>
<key>hash2</key>
<data>
6aipppbEU7MEd3x9OHnKqAGyFXVYiSAL8X8lm271U00=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^Resources/</key>
<true/>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,181 @@
/*
SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts
Copyright (C) 2001-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_textengine.h
*
* Definitions for implementations of the TTF_TextEngine interface.
*/
#ifndef SDL_TTF_TEXTENGINE_H_
#define SDL_TTF_TEXTENGINE_H_
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* A font atlas draw command.
*
* \since This enum is available since SDL_ttf 3.0.0.
*/
typedef enum TTF_DrawCommand
{
TTF_DRAW_COMMAND_NOOP,
TTF_DRAW_COMMAND_FILL,
TTF_DRAW_COMMAND_COPY
} TTF_DrawCommand;
/**
* A filled rectangle draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_FillOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_FILL */
SDL_Rect rect; /**< The rectangle to fill, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
} TTF_FillOperation;
/**
* A texture copy draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_CopyOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_COPY */
int text_offset; /**< The offset in the text corresponding to this glyph.
There may be multiple glyphs with the same text offset
and the next text offset might be several Unicode codepoints
later. In this case the glyphs and codepoints are grouped
together and the group bounding box is the union of the dst
rectangles for the corresponding glyphs. */
TTF_Font *glyph_font; /**< The font containing the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
Uint32 glyph_index; /**< The glyph index of the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
SDL_Rect src; /**< The area within the glyph to be drawn */
SDL_Rect dst; /**< The drawing coordinates of the glyph, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
void *reserved;
} TTF_CopyOperation;
/**
* A text engine draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*/
typedef union TTF_DrawOperation
{
TTF_DrawCommand cmd;
TTF_FillOperation fill;
TTF_CopyOperation copy;
} TTF_DrawOperation;
/* Private data in TTF_Text, to assist in text measurement and layout */
typedef struct TTF_TextLayout TTF_TextLayout;
/* Private data in TTF_Text, available to implementations */
struct TTF_TextData
{
TTF_Font *font; /**< The font used by this text, read-only. */
SDL_FColor color; /**< The color of the text, read-only. */
bool needs_layout_update; /**< True if the layout needs to be updated */
TTF_TextLayout *layout; /**< Cached layout information, read-only. */
int x; /**< The x offset of the upper left corner of this text, in pixels, read-only. */
int y; /**< The y offset of the upper left corner of this text, in pixels, read-only. */
int w; /**< The width of this text, in pixels, read-only. */
int h; /**< The height of this text, in pixels, read-only. */
int num_ops; /**< The number of drawing operations to render this text, read-only. */
TTF_DrawOperation *ops; /**< The drawing operations used to render this text, read-only. */
int num_clusters; /**< The number of substrings representing clusters of glyphs in the string, read-only */
TTF_SubString *clusters; /**< Substrings representing clusters of glyphs in the string, read-only */
SDL_PropertiesID props; /**< Custom properties associated with this text, read-only. This field is created as-needed using TTF_GetTextProperties() and the properties may be then set and read normally */
bool needs_engine_update; /**< True if the engine text needs to be updated */
TTF_TextEngine *engine; /**< The engine used to render this text, read-only. */
void *engine_text; /**< The implementation-specific representation of this text */
};
/**
* A text engine interface.
*
* This structure should be initialized using SDL_INIT_INTERFACE()
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa SDL_INIT_INTERFACE
*/
struct TTF_TextEngine
{
Uint32 version; /**< The version of this interface */
void *userdata; /**< User data pointer passed to callbacks */
/* Create a text representation from draw instructions.
*
* All fields of `text` except `internal->engine_text` will already be filled out.
*
* This function should set the `internal->engine_text` field to a non-NULL value.
*
* \param userdata the userdata pointer in this interface.
* \param text the text object being created.
*/
bool (SDLCALL *CreateText)(void *userdata, TTF_Text *text);
/**
* Destroy a text representation.
*/
void (SDLCALL *DestroyText)(void *userdata, TTF_Text *text);
};
/* Check the size of TTF_TextEngine
*
* If this assert fails, either the compiler is padding to an unexpected size,
* or the interface has been updated and this should be updated to match and
* the code using this interface should be updated to handle the old version.
*/
SDL_COMPILE_TIME_ASSERT(TTF_TextEngine_SIZE,
(sizeof(void *) == 4 && sizeof(TTF_TextEngine) == 16) ||
(sizeof(void *) == 8 && sizeof(TTF_TextEngine) == 32));
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_TTF_TEXTENGINE_H_ */

View File

@@ -0,0 +1,35 @@
# Using this package
This package contains SDL_ttf built for Xcode.
To use this package in Xcode, drag `SDL3_ttf.framework` into your project.
# Documentation
An API reference and additional documentation is available at:
https://wiki.libsdl.org/SDL3_ttf
# Discussions
## Discord
You can join the official Discord server at:
https://discord.com/invite/BwpFGBWsv8
## Forums/mailing lists
You can join SDL development discussions at:
https://discourse.libsdl.org/
Once you sign up, you can use the forum through the website or as a mailing list from your email client.
## Announcement list
You can sign up for the low traffic announcement list at:
https://www.libsdl.org/mailing-list.php

View File

@@ -0,0 +1,17 @@
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -0,0 +1,23 @@
SDL_ttf 3.0
This library is a wrapper around the FreeType and Harfbuzz libraries, allowing you to use TrueType fonts to render text in SDL applications.
The latest version of this library is available from GitHub:
https://github.com/libsdl-org/SDL_ttf/releases
Installation instructions and a quick introduction is available in
[INSTALL.md](INSTALL.md)
This library is distributed under the terms of the zlib license,
available in [LICENSE.txt](LICENSE.txt).
This library also uses the following libraries:
- FreeType, licensed under the [FTL](https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT)
- HarfBuzz, licensed under the [MIT license](https://github.com/harfbuzz/harfbuzz/blob/main/COPYING)
- PlutoSVG, licensed under the [MIT license](https://github.com/sammycage/plutosvg/blob/master/LICENSE)
- PlutoVG, licensed under the [MIT license](https://github.com/sammycage/plutovg/blob/master/LICENSE)
Enjoy!
Sam Lantinga (slouken@libsdl.org)

View File

@@ -0,0 +1,181 @@
/*
SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts
Copyright (C) 2001-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_textengine.h
*
* Definitions for implementations of the TTF_TextEngine interface.
*/
#ifndef SDL_TTF_TEXTENGINE_H_
#define SDL_TTF_TEXTENGINE_H_
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* A font atlas draw command.
*
* \since This enum is available since SDL_ttf 3.0.0.
*/
typedef enum TTF_DrawCommand
{
TTF_DRAW_COMMAND_NOOP,
TTF_DRAW_COMMAND_FILL,
TTF_DRAW_COMMAND_COPY
} TTF_DrawCommand;
/**
* A filled rectangle draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_FillOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_FILL */
SDL_Rect rect; /**< The rectangle to fill, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
} TTF_FillOperation;
/**
* A texture copy draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa TTF_DrawOperation
*/
typedef struct TTF_CopyOperation
{
TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_COPY */
int text_offset; /**< The offset in the text corresponding to this glyph.
There may be multiple glyphs with the same text offset
and the next text offset might be several Unicode codepoints
later. In this case the glyphs and codepoints are grouped
together and the group bounding box is the union of the dst
rectangles for the corresponding glyphs. */
TTF_Font *glyph_font; /**< The font containing the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
Uint32 glyph_index; /**< The glyph index of the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
SDL_Rect src; /**< The area within the glyph to be drawn */
SDL_Rect dst; /**< The drawing coordinates of the glyph, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
void *reserved;
} TTF_CopyOperation;
/**
* A text engine draw operation.
*
* \since This struct is available since SDL_ttf 3.0.0.
*/
typedef union TTF_DrawOperation
{
TTF_DrawCommand cmd;
TTF_FillOperation fill;
TTF_CopyOperation copy;
} TTF_DrawOperation;
/* Private data in TTF_Text, to assist in text measurement and layout */
typedef struct TTF_TextLayout TTF_TextLayout;
/* Private data in TTF_Text, available to implementations */
struct TTF_TextData
{
TTF_Font *font; /**< The font used by this text, read-only. */
SDL_FColor color; /**< The color of the text, read-only. */
bool needs_layout_update; /**< True if the layout needs to be updated */
TTF_TextLayout *layout; /**< Cached layout information, read-only. */
int x; /**< The x offset of the upper left corner of this text, in pixels, read-only. */
int y; /**< The y offset of the upper left corner of this text, in pixels, read-only. */
int w; /**< The width of this text, in pixels, read-only. */
int h; /**< The height of this text, in pixels, read-only. */
int num_ops; /**< The number of drawing operations to render this text, read-only. */
TTF_DrawOperation *ops; /**< The drawing operations used to render this text, read-only. */
int num_clusters; /**< The number of substrings representing clusters of glyphs in the string, read-only */
TTF_SubString *clusters; /**< Substrings representing clusters of glyphs in the string, read-only */
SDL_PropertiesID props; /**< Custom properties associated with this text, read-only. This field is created as-needed using TTF_GetTextProperties() and the properties may be then set and read normally */
bool needs_engine_update; /**< True if the engine text needs to be updated */
TTF_TextEngine *engine; /**< The engine used to render this text, read-only. */
void *engine_text; /**< The implementation-specific representation of this text */
};
/**
* A text engine interface.
*
* This structure should be initialized using SDL_INIT_INTERFACE()
*
* \since This struct is available since SDL_ttf 3.0.0.
*
* \sa SDL_INIT_INTERFACE
*/
struct TTF_TextEngine
{
Uint32 version; /**< The version of this interface */
void *userdata; /**< User data pointer passed to callbacks */
/* Create a text representation from draw instructions.
*
* All fields of `text` except `internal->engine_text` will already be filled out.
*
* This function should set the `internal->engine_text` field to a non-NULL value.
*
* \param userdata the userdata pointer in this interface.
* \param text the text object being created.
*/
bool (SDLCALL *CreateText)(void *userdata, TTF_Text *text);
/**
* Destroy a text representation.
*/
void (SDLCALL *DestroyText)(void *userdata, TTF_Text *text);
};
/* Check the size of TTF_TextEngine
*
* If this assert fails, either the compiler is padding to an unexpected size,
* or the interface has been updated and this should be updated to match and
* the code using this interface should be updated to handle the old version.
*/
SDL_COMPILE_TIME_ASSERT(TTF_TextEngine_SIZE,
(sizeof(void *) == 4 && sizeof(TTF_TextEngine) == 16) ||
(sizeof(void *) == 8 && sizeof(TTF_TextEngine) == 32));
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_TTF_TEXTENGINE_H_ */

View File

@@ -0,0 +1,35 @@
# Using this package
This package contains SDL_ttf built for Xcode.
To use this package in Xcode, drag `SDL3_ttf.framework` into your project.
# Documentation
An API reference and additional documentation is available at:
https://wiki.libsdl.org/SDL3_ttf
# Discussions
## Discord
You can join the official Discord server at:
https://discord.com/invite/BwpFGBWsv8
## Forums/mailing lists
You can join SDL development discussions at:
https://discourse.libsdl.org/
Once you sign up, you can use the forum through the website or as a mailing list from your email client.
## Announcement list
You can sign up for the low traffic announcement list at:
https://www.libsdl.org/mailing-list.php

View File

@@ -0,0 +1,17 @@
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -0,0 +1,23 @@
SDL_ttf 3.0
This library is a wrapper around the FreeType and Harfbuzz libraries, allowing you to use TrueType fonts to render text in SDL applications.
The latest version of this library is available from GitHub:
https://github.com/libsdl-org/SDL_ttf/releases
Installation instructions and a quick introduction is available in
[INSTALL.md](INSTALL.md)
This library is distributed under the terms of the zlib license,
available in [LICENSE.txt](LICENSE.txt).
This library also uses the following libraries:
- FreeType, licensed under the [FTL](https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/docs/FTL.TXT)
- HarfBuzz, licensed under the [MIT license](https://github.com/harfbuzz/harfbuzz/blob/main/COPYING)
- PlutoSVG, licensed under the [MIT license](https://github.com/sammycage/plutosvg/blob/master/LICENSE)
- PlutoVG, licensed under the [MIT license](https://github.com/sammycage/plutovg/blob/master/LICENSE)
Enjoy!
Sam Lantinga (slouken@libsdl.org)

View File

@@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>CMake/SDL3_ttfConfig.cmake</key>
<data>
V6UpWQTvr/puOrlm1sgAs6fktNA=
</data>
<key>CMake/SDL3_ttfConfigVersion.cmake</key>
<data>
WW2xmNHZyYr9y3/8uAylJuutcPw=
</data>
<key>Headers/SDL_textengine.h</key>
<data>
7QAtKpC/pLIq6TK3F59Ax1hg3tc=
</data>
<key>Headers/SDL_ttf.h</key>
<data>
90S4SFzJy1lUuMotaCRWpTbzRa4=
</data>
<key>INSTALL.md</key>
<data>
3kA+9HE5dF7+nyypVt5YOfU+Uho=
</data>
<key>Info.plist</key>
<data>
+nBymgIjvQrMA5f3CqiBB03/KB0=
</data>
<key>LICENSE.txt</key>
<data>
dp6e8JHkl0CrYD+oe2IXZfWB/iw=
</data>
<key>README.md</key>
<data>
lm034L4zWKPElKb9O2dmehurfFQ=
</data>
</dict>
<key>files2</key>
<dict>
<key>CMake/SDL3_ttfConfig.cmake</key>
<dict>
<key>hash2</key>
<data>
VpwUT/D8TjpLXBguVImWqsMkqni9HXiIzx91C92Krqc=
</data>
</dict>
<key>CMake/SDL3_ttfConfigVersion.cmake</key>
<dict>
<key>hash2</key>
<data>
tb1RnDTj72GQOzcXp6FPtiqW8tSD886UyUY09c1Ms/U=
</data>
</dict>
<key>Headers/SDL_textengine.h</key>
<dict>
<key>hash2</key>
<data>
Uk27FTzsWoYySpKM1gkwCB/svSxscGViuMzca93gLP8=
</data>
</dict>
<key>Headers/SDL_ttf.h</key>
<dict>
<key>hash2</key>
<data>
6bsCCUp3Uc3tCp+0Xxw7Tt01+UV8bra5YN1dFjpRBL0=
</data>
</dict>
<key>INSTALL.md</key>
<dict>
<key>hash2</key>
<data>
Jq9GEmdnFRmUTNnYYZZ+5mFqqrMelD86Gthhyi2kGJQ=
</data>
</dict>
<key>LICENSE.txt</key>
<dict>
<key>hash2</key>
<data>
eCbsoKD35ZHzjdhE4geiAKrIGlmyDYoww6+MYoKvE+Y=
</data>
</dict>
<key>README.md</key>
<dict>
<key>hash2</key>
<data>
6aipppbEU7MEd3x9OHnKqAGyFXVYiSAL8X8lm271U00=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

BIN
release/vibe3.res Normal file

Binary file not shown.

Binary file not shown.

687
source/app_logo.cpp Normal file
View File

@@ -0,0 +1,687 @@
#include "app_logo.hpp"
#include <SDL3/SDL_render.h> // for SDL_DestroyTexture, SDL_RenderGeometry, SDL_SetTextureAlphaMod
#include <cmath> // for powf, sinf, cosf
#include <cstdlib> // for free()
#include <iostream> // for std::cout
#include "logo_scaler.hpp" // for LogoScaler
#include "defines.hpp" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory
// ============================================================================
// Destructor - Liberar las 4 texturas SDL
// ============================================================================
AppLogo::~AppLogo() {
if (logo1_base_texture_) {
SDL_DestroyTexture(logo1_base_texture_);
logo1_base_texture_ = nullptr;
}
if (logo1_native_texture_) {
SDL_DestroyTexture(logo1_native_texture_);
logo1_native_texture_ = nullptr;
}
if (logo2_base_texture_) {
SDL_DestroyTexture(logo2_base_texture_);
logo2_base_texture_ = nullptr;
}
if (logo2_native_texture_) {
SDL_DestroyTexture(logo2_native_texture_);
logo2_native_texture_ = nullptr;
}
}
// ============================================================================
// Inicialización - Pre-escalar logos a 2 resoluciones (base y nativa)
// ============================================================================
bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) {
renderer_ = renderer;
base_screen_width_ = screen_width;
base_screen_height_ = screen_height;
screen_width_ = screen_width;
screen_height_ = screen_height;
std::string resources_dir = getResourcesDirectory();
// ========================================================================
// 1. Detectar resolución nativa del monitor
// ========================================================================
if (!LogoScaler::detectNativeResolution(native_screen_width_, native_screen_height_)) {
std::cout << "No se pudo detectar resolución nativa, usando solo base" << std::endl;
// Fallback: usar resolución base como nativa
native_screen_width_ = screen_width;
native_screen_height_ = screen_height;
}
// ========================================================================
// 2. Calcular alturas finales para ambas resoluciones
// ========================================================================
int logo_base_target_height = static_cast<int>(base_screen_height_ * APPLOGO_HEIGHT_PERCENT);
int logo_native_target_height = static_cast<int>(native_screen_height_ * APPLOGO_HEIGHT_PERCENT);
std::cout << "Pre-escalando logos:" << std::endl;
std::cout << " Base: " << base_screen_width_ << "x" << base_screen_height_
<< " (altura logo: " << logo_base_target_height << "px)" << std::endl;
std::cout << " Nativa: " << native_screen_width_ << "x" << native_screen_height_
<< " (altura logo: " << logo_native_target_height << "px)" << std::endl;
// ========================================================================
// 3. Cargar y escalar LOGO1 (data/logo/logo.png) a 2 versiones
// ========================================================================
std::string logo1_path = resources_dir + "/data/logo/logo.png";
// 3a. Versión BASE de logo1
unsigned char* logo1_base_data = LogoScaler::loadAndScale(
logo1_path,
0, // width calculado automáticamente por aspect ratio
logo_base_target_height,
logo1_base_width_,
logo1_base_height_
);
if (logo1_base_data == nullptr) {
std::cout << "Error: No se pudo escalar logo1 (base)" << std::endl;
return false;
}
logo1_base_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo1_base_data, logo1_base_width_, logo1_base_height_
);
free(logo1_base_data); // Liberar buffer temporal
if (logo1_base_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo1 (base)" << std::endl;
return false;
}
// Habilitar alpha blending
SDL_SetTextureBlendMode(logo1_base_texture_, SDL_BLENDMODE_BLEND);
// 3b. Versión NATIVA de logo1
unsigned char* logo1_native_data = LogoScaler::loadAndScale(
logo1_path,
0, // width calculado automáticamente
logo_native_target_height,
logo1_native_width_,
logo1_native_height_
);
if (logo1_native_data == nullptr) {
std::cout << "Error: No se pudo escalar logo1 (nativa)" << std::endl;
return false;
}
logo1_native_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo1_native_data, logo1_native_width_, logo1_native_height_
);
free(logo1_native_data);
if (logo1_native_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo1 (nativa)" << std::endl;
return false;
}
SDL_SetTextureBlendMode(logo1_native_texture_, SDL_BLENDMODE_BLEND);
// ========================================================================
// 4. Cargar y escalar LOGO2 (data/logo/logo2.png) a 2 versiones
// ========================================================================
std::string logo2_path = resources_dir + "/data/logo/logo2.png";
// 4a. Versión BASE de logo2
unsigned char* logo2_base_data = LogoScaler::loadAndScale(
logo2_path,
0,
logo_base_target_height,
logo2_base_width_,
logo2_base_height_
);
if (logo2_base_data == nullptr) {
std::cout << "Error: No se pudo escalar logo2 (base)" << std::endl;
return false;
}
logo2_base_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo2_base_data, logo2_base_width_, logo2_base_height_
);
free(logo2_base_data);
if (logo2_base_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo2 (base)" << std::endl;
return false;
}
SDL_SetTextureBlendMode(logo2_base_texture_, SDL_BLENDMODE_BLEND);
// 4b. Versión NATIVA de logo2
unsigned char* logo2_native_data = LogoScaler::loadAndScale(
logo2_path,
0,
logo_native_target_height,
logo2_native_width_,
logo2_native_height_
);
if (logo2_native_data == nullptr) {
std::cout << "Error: No se pudo escalar logo2 (nativa)" << std::endl;
return false;
}
logo2_native_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo2_native_data, logo2_native_width_, logo2_native_height_
);
free(logo2_native_data);
if (logo2_native_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo2 (nativa)" << std::endl;
return false;
}
SDL_SetTextureBlendMode(logo2_native_texture_, SDL_BLENDMODE_BLEND);
// ========================================================================
// 5. Inicialmente usar texturas BASE (la resolución de inicio)
// ========================================================================
logo1_current_texture_ = logo1_base_texture_;
logo1_current_width_ = logo1_base_width_;
logo1_current_height_ = logo1_base_height_;
logo2_current_texture_ = logo2_base_texture_;
logo2_current_width_ = logo2_base_width_;
logo2_current_height_ = logo2_base_height_;
std::cout << "Logos pre-escalados exitosamente (4 texturas creadas)" << std::endl;
return true;
}
void AppLogo::update(float delta_time, AppMode current_mode) {
// Si estamos en SANDBOX, resetear y no hacer nada (logo desactivado)
if (current_mode == AppMode::SANDBOX) {
state_ = AppLogoState::HIDDEN;
timer_ = 0.0f;
logo1_alpha_ = 0;
logo2_alpha_ = 0;
return;
}
// Máquina de estados con fade in/out
timer_ += delta_time;
switch (state_) {
case AppLogoState::HIDDEN:
// Esperando el intervalo de espera
if (timer_ >= APPLOGO_DISPLAY_INTERVAL) {
state_ = AppLogoState::FADE_IN;
timer_ = 0.0f;
logo1_alpha_ = 0;
logo2_alpha_ = 0;
// Elegir UNA animación aleatoria (misma para ambos logos, misma entrada y salida)
current_animation_ = getRandomAnimation();
}
break;
case AppLogoState::FADE_IN:
// Fade in: alpha de 0 a 255, con Logo 2 retrasado 0.25s
{
// Calcular progreso de cada logo (Logo 2 con retraso)
float fade_progress_logo1 = timer_ / APPLOGO_ANIMATION_DURATION;
float fade_progress_logo2 = std::max(0.0f, (timer_ - APPLOGO_LOGO2_DELAY) / APPLOGO_ANIMATION_DURATION);
// Verificar si fade in completado (cuando logo2 también termina)
if (fade_progress_logo2 >= 1.0f) {
// Fade in completado para ambos logos
state_ = AppLogoState::VISIBLE;
timer_ = 0.0f;
logo1_alpha_ = 255;
logo2_alpha_ = 255;
// Resetear variables de ambos logos
logo1_scale_ = 1.0f;
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
logo1_rotation_ = 0.0f;
logo2_scale_ = 1.0f;
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
logo2_rotation_ = 0.0f;
} else {
// Interpolar alpha con retraso de forma LINEAL (sin easing)
logo1_alpha_ = static_cast<int>(std::min(1.0f, fade_progress_logo1) * 255.0f);
logo2_alpha_ = static_cast<int>(std::min(1.0f, fade_progress_logo2) * 255.0f);
// ================================================================
// Aplicar MISMA animación (current_animation_) a ambos logos
// con sus respectivos progresos
// ================================================================
switch (current_animation_) {
case AppLogoAnimationType::ZOOM_ONLY:
logo1_scale_ = 1.2f - (std::min(1.0f, fade_progress_logo1) * 0.2f);
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
logo1_rotation_ = 0.0f;
logo2_scale_ = 1.2f - (std::min(1.0f, fade_progress_logo2) * 0.2f);
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
logo2_rotation_ = 0.0f;
break;
case AppLogoAnimationType::ELASTIC_STICK:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
float elastic_t1 = easeOutElastic(prog1);
logo1_scale_ = 1.2f - (elastic_t1 * 0.2f);
float squash_t1 = easeOutBack(prog1);
logo1_squash_y_ = 0.6f + (squash_t1 * 0.4f);
logo1_stretch_x_ = 1.0f + (1.0f - logo1_squash_y_) * 0.5f;
logo1_rotation_ = 0.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
float elastic_t2 = easeOutElastic(prog2);
logo2_scale_ = 1.2f - (elastic_t2 * 0.2f);
float squash_t2 = easeOutBack(prog2);
logo2_squash_y_ = 0.6f + (squash_t2 * 0.4f);
logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f;
logo2_rotation_ = 0.0f;
}
break;
case AppLogoAnimationType::ROTATE_SPIRAL:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
float ease_t1 = easeInOutQuad(prog1);
logo1_scale_ = 0.3f + (ease_t1 * 0.7f);
logo1_rotation_ = (1.0f - prog1) * 6.28f;
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
float ease_t2 = easeInOutQuad(prog2);
logo2_scale_ = 0.3f + (ease_t2 * 0.7f);
logo2_rotation_ = (1.0f - prog2) * 6.28f;
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
}
break;
case AppLogoAnimationType::BOUNCE_SQUASH:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
float bounce_t1 = easeOutBounce(prog1);
logo1_scale_ = 1.0f;
float squash_amount1 = (1.0f - bounce_t1) * 0.3f;
logo1_squash_y_ = 1.0f - squash_amount1;
logo1_stretch_x_ = 1.0f + squash_amount1 * 0.5f;
logo1_rotation_ = 0.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
float bounce_t2 = easeOutBounce(prog2);
logo2_scale_ = 1.0f;
float squash_amount2 = (1.0f - bounce_t2) * 0.3f;
logo2_squash_y_ = 1.0f - squash_amount2;
logo2_stretch_x_ = 1.0f + squash_amount2 * 0.5f;
logo2_rotation_ = 0.0f;
}
break;
}
}
}
break;
case AppLogoState::VISIBLE:
// Logo completamente visible, esperando duración
if (timer_ >= APPLOGO_DISPLAY_DURATION) {
state_ = AppLogoState::FADE_OUT;
timer_ = 0.0f;
logo1_alpha_ = 255;
logo2_alpha_ = 255;
// NO elegir nueva animación - reutilizar current_animation_ (simetría entrada/salida)
}
break;
case AppLogoState::FADE_OUT:
// Fade out: alpha de 255 a 0, con Logo 2 retrasado 0.25s (misma animación que entrada)
{
// Calcular progreso de cada logo (Logo 2 con retraso)
float fade_progress_logo1 = timer_ / APPLOGO_ANIMATION_DURATION;
float fade_progress_logo2 = std::max(0.0f, (timer_ - APPLOGO_LOGO2_DELAY) / APPLOGO_ANIMATION_DURATION);
// Verificar si fade out completado (cuando logo2 también termina)
if (fade_progress_logo2 >= 1.0f) {
// Fade out completado, volver a HIDDEN
state_ = AppLogoState::HIDDEN;
timer_ = 0.0f;
logo1_alpha_ = 0;
logo2_alpha_ = 0;
// Resetear variables de ambos logos
logo1_scale_ = 1.0f;
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
logo1_rotation_ = 0.0f;
logo2_scale_ = 1.0f;
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
logo2_rotation_ = 0.0f;
} else {
// Interpolar alpha con retraso de forma LINEAL (255 → 0, sin easing)
logo1_alpha_ = static_cast<int>((1.0f - std::min(1.0f, fade_progress_logo1)) * 255.0f);
logo2_alpha_ = static_cast<int>((1.0f - std::min(1.0f, fade_progress_logo2)) * 255.0f);
// ================================================================
// Aplicar MISMA animación (current_animation_) de forma invertida
// ================================================================
switch (current_animation_) {
case AppLogoAnimationType::ZOOM_ONLY:
logo1_scale_ = 1.0f + (std::min(1.0f, fade_progress_logo1) * 0.2f);
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
logo1_rotation_ = 0.0f;
logo2_scale_ = 1.0f + (std::min(1.0f, fade_progress_logo2) * 0.2f);
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
logo2_rotation_ = 0.0f;
break;
case AppLogoAnimationType::ELASTIC_STICK:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
logo1_scale_ = 1.0f + (prog1 * prog1 * 0.2f);
logo1_squash_y_ = 1.0f + (prog1 * 0.3f);
logo1_stretch_x_ = 1.0f - (prog1 * 0.2f);
logo1_rotation_ = prog1 * 0.1f;
float prog2 = std::min(1.0f, fade_progress_logo2);
logo2_scale_ = 1.0f + (prog2 * prog2 * 0.2f);
logo2_squash_y_ = 1.0f + (prog2 * 0.3f);
logo2_stretch_x_ = 1.0f - (prog2 * 0.2f);
logo2_rotation_ = prog2 * 0.1f;
}
break;
case AppLogoAnimationType::ROTATE_SPIRAL:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
float ease_t1 = easeInOutQuad(prog1);
logo1_scale_ = 1.0f - (ease_t1 * 0.7f);
logo1_rotation_ = prog1 * 6.28f;
logo1_squash_y_ = 1.0f;
logo1_stretch_x_ = 1.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
float ease_t2 = easeInOutQuad(prog2);
logo2_scale_ = 1.0f - (ease_t2 * 0.7f);
logo2_rotation_ = prog2 * 6.28f;
logo2_squash_y_ = 1.0f;
logo2_stretch_x_ = 1.0f;
}
break;
case AppLogoAnimationType::BOUNCE_SQUASH:
{
float prog1 = std::min(1.0f, fade_progress_logo1);
if (prog1 < 0.2f) {
float squash_t = prog1 / 0.2f;
logo1_squash_y_ = 1.0f - (squash_t * 0.3f);
logo1_stretch_x_ = 1.0f + (squash_t * 0.2f);
} else {
float jump_t = (prog1 - 0.2f) / 0.8f;
logo1_squash_y_ = 0.7f + (jump_t * 0.5f);
logo1_stretch_x_ = 1.2f - (jump_t * 0.2f);
}
logo1_scale_ = 1.0f + (prog1 * 0.3f);
logo1_rotation_ = 0.0f;
float prog2 = std::min(1.0f, fade_progress_logo2);
if (prog2 < 0.2f) {
float squash_t = prog2 / 0.2f;
logo2_squash_y_ = 1.0f - (squash_t * 0.3f);
logo2_stretch_x_ = 1.0f + (squash_t * 0.2f);
} else {
float jump_t = (prog2 - 0.2f) / 0.8f;
logo2_squash_y_ = 0.7f + (jump_t * 0.5f);
logo2_stretch_x_ = 1.2f - (jump_t * 0.2f);
}
logo2_scale_ = 1.0f + (prog2 * 0.3f);
logo2_rotation_ = 0.0f;
}
break;
}
}
}
break;
}
}
void AppLogo::render() {
// Renderizar si NO está en estado HIDDEN (incluye FADE_IN, VISIBLE, FADE_OUT)
if (state_ != AppLogoState::HIDDEN) {
// Renderizar LOGO1 primero (fondo), luego LOGO2 (encima)
renderWithGeometry(1);
renderWithGeometry(2);
}
}
void AppLogo::updateScreenSize(int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
// ========================================================================
// Detectar si coincide con resolución nativa o base, cambiar texturas
// ========================================================================
bool is_native = (screen_width == native_screen_width_ && screen_height == native_screen_height_);
if (is_native) {
// Cambiar a texturas nativas (F4 fullscreen)
logo1_current_texture_ = logo1_native_texture_;
logo1_current_width_ = logo1_native_width_;
logo1_current_height_ = logo1_native_height_;
logo2_current_texture_ = logo2_native_texture_;
logo2_current_width_ = logo2_native_width_;
logo2_current_height_ = logo2_native_height_;
std::cout << "AppLogo: Cambiado a texturas NATIVAS" << std::endl;
} else {
// Cambiar a texturas base (ventana redimensionable)
logo1_current_texture_ = logo1_base_texture_;
logo1_current_width_ = logo1_base_width_;
logo1_current_height_ = logo1_base_height_;
logo2_current_texture_ = logo2_base_texture_;
logo2_current_width_ = logo2_base_width_;
logo2_current_height_ = logo2_base_height_;
std::cout << "AppLogo: Cambiado a texturas BASE" << std::endl;
}
// Nota: No es necesario recalcular escalas porque las texturas están pre-escaladas
// al tamaño exacto de pantalla. Solo renderizamos al 100% (o con deformaciones de animación).
}
// ============================================================================
// Funciones de easing para animaciones
// ============================================================================
float AppLogo::easeOutElastic(float t) {
// Elastic easing out: bounce elástico al final
const float c4 = (2.0f * 3.14159f) / 3.0f;
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
return powf(2.0f, -10.0f * t) * sinf((t * 10.0f - 0.75f) * c4) + 1.0f;
}
float AppLogo::easeOutBack(float t) {
// Back easing out: overshoot suave al final
const float c1 = 1.70158f;
const float c3 = c1 + 1.0f;
return 1.0f + c3 * powf(t - 1.0f, 3.0f) + c1 * powf(t - 1.0f, 2.0f);
}
float AppLogo::easeOutBounce(float t) {
// Bounce easing out: rebotes decrecientes (para BOUNCE_SQUASH)
const float n1 = 7.5625f;
const float d1 = 2.75f;
if (t < 1.0f / d1) {
return n1 * t * t;
} else if (t < 2.0f / d1) {
t -= 1.5f / d1;
return n1 * t * t + 0.75f;
} else if (t < 2.5f / d1) {
t -= 2.25f / d1;
return n1 * t * t + 0.9375f;
} else {
t -= 2.625f / d1;
return n1 * t * t + 0.984375f;
}
}
float AppLogo::easeInOutQuad(float t) {
// Quadratic easing in/out: aceleración suave (para ROTATE_SPIRAL)
if (t < 0.5f) {
return 2.0f * t * t;
} else {
return 1.0f - powf(-2.0f * t + 2.0f, 2.0f) / 2.0f;
}
}
// ============================================================================
// Función auxiliar para aleatorización
// ============================================================================
AppLogoAnimationType AppLogo::getRandomAnimation() {
// Generar número aleatorio entre 0 y 3 (4 tipos de animación)
int random_value = rand() % 4;
switch (random_value) {
case 0:
return AppLogoAnimationType::ZOOM_ONLY;
case 1:
return AppLogoAnimationType::ELASTIC_STICK;
case 2:
return AppLogoAnimationType::ROTATE_SPIRAL;
case 3:
default:
return AppLogoAnimationType::BOUNCE_SQUASH;
}
}
// ============================================================================
// Renderizado con geometría (para todos los logos, con deformaciones)
// ============================================================================
void AppLogo::renderWithGeometry(int logo_index) {
if (!renderer_) return;
// Seleccionar variables según el logo_index (1 = logo1, 2 = logo2)
SDL_Texture* texture;
int base_width, base_height;
float scale, squash_y, stretch_x, rotation;
if (logo_index == 1) {
if (!logo1_current_texture_) return;
texture = logo1_current_texture_;
base_width = logo1_current_width_;
base_height = logo1_current_height_;
scale = logo1_scale_;
squash_y = logo1_squash_y_;
stretch_x = logo1_stretch_x_;
rotation = logo1_rotation_;
} else if (logo_index == 2) {
if (!logo2_current_texture_) return;
texture = logo2_current_texture_;
base_width = logo2_current_width_;
base_height = logo2_current_height_;
scale = logo2_scale_;
squash_y = logo2_squash_y_;
stretch_x = logo2_stretch_x_;
rotation = logo2_rotation_;
} else {
return; // Índice inválido
}
// Aplicar alpha específico de cada logo (con retraso para logo2)
int alpha = (logo_index == 1) ? logo1_alpha_ : logo2_alpha_;
float alpha_normalized = static_cast<float>(alpha) / 255.0f; // Convertir 0-255 → 0.0-1.0
// NO usar SDL_SetTextureAlphaMod - aplicar alpha directamente a vértices
// Calcular padding desde bordes derecho e inferior
float padding_x = screen_width_ * APPLOGO_PADDING_PERCENT;
float padding_y = screen_height_ * APPLOGO_PADDING_PERCENT;
// Calcular esquina BASE (sin escala) para obtener centro FIJO
// Esto asegura que el centro no se mueva cuando cambia scale/squash/stretch
float corner_x_base = screen_width_ - base_width - padding_x;
float corner_y_base = screen_height_ - base_height - padding_y;
// Centro FIJO del logo (no cambia con scale/squash/stretch)
float center_x = corner_x_base + (base_width / 2.0f);
float center_y = corner_y_base + (base_height / 2.0f);
// Calcular tamaño ESCALADO (para vértices)
// (base_width y base_height ya están pre-escalados al tamaño correcto de pantalla)
float width = base_width * scale * stretch_x;
float height = base_height * scale * squash_y;
// Pre-calcular seno y coseno de rotación
float cos_rot = cosf(rotation);
float sin_rot = sinf(rotation);
// Crear 4 vértices del quad (centrado en center_x, center_y)
SDL_Vertex vertices[4];
// Offset desde el centro
float half_w = width / 2.0f;
float half_h = height / 2.0f;
// Vértice superior izquierdo (rotado)
{
float local_x = -half_w;
float local_y = -half_h;
float rotated_x = local_x * cos_rot - local_y * sin_rot;
float rotated_y = local_x * sin_rot + local_y * cos_rot;
vertices[0].position = {center_x + rotated_x, center_y + rotated_y};
vertices[0].tex_coord = {0.0f, 0.0f};
vertices[0].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice
}
// Vértice superior derecho (rotado)
{
float local_x = half_w;
float local_y = -half_h;
float rotated_x = local_x * cos_rot - local_y * sin_rot;
float rotated_y = local_x * sin_rot + local_y * cos_rot;
vertices[1].position = {center_x + rotated_x, center_y + rotated_y};
vertices[1].tex_coord = {1.0f, 0.0f};
vertices[1].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice
}
// Vértice inferior derecho (rotado)
{
float local_x = half_w;
float local_y = half_h;
float rotated_x = local_x * cos_rot - local_y * sin_rot;
float rotated_y = local_x * sin_rot + local_y * cos_rot;
vertices[2].position = {center_x + rotated_x, center_y + rotated_y};
vertices[2].tex_coord = {1.0f, 1.0f};
vertices[2].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice
}
// Vértice inferior izquierdo (rotado)
{
float local_x = -half_w;
float local_y = half_h;
float rotated_x = local_x * cos_rot - local_y * sin_rot;
float rotated_y = local_x * sin_rot + local_y * cos_rot;
vertices[3].position = {center_x + rotated_x, center_y + rotated_y};
vertices[3].tex_coord = {0.0f, 1.0f};
vertices[3].color = {1.0f, 1.0f, 1.0f, alpha_normalized}; // Alpha aplicado al vértice
}
// Índices para 2 triángulos
int indices[6] = {0, 1, 2, 2, 3, 0};
// Renderizar con la textura del logo correspondiente
SDL_RenderGeometry(renderer_, texture, vertices, 4, indices, 6);
}

117
source/app_logo.hpp Normal file
View File

@@ -0,0 +1,117 @@
#pragma once
#include <SDL3/SDL_render.h> // for SDL_Renderer
#include <memory> // for unique_ptr, shared_ptr
#include "defines.hpp" // for AppMode
class Texture;
class Sprite;
// Estados de la máquina de estados del logo
enum class AppLogoState {
HIDDEN, // Logo oculto, esperando APPLOGO_DISPLAY_INTERVAL
FADE_IN, // Apareciendo (alpha 0 → 255)
VISIBLE, // Completamente visible, esperando APPLOGO_DISPLAY_DURATION
FADE_OUT // Desapareciendo (alpha 255 → 0)
};
// Tipo de animación de entrada/salida
enum class AppLogoAnimationType {
ZOOM_ONLY, // A: Solo zoom simple (120% → 100% → 120%)
ELASTIC_STICK, // B: Zoom + deformación elástica tipo "pegatina"
ROTATE_SPIRAL, // C: Rotación en espiral (entra girando, sale girando)
BOUNCE_SQUASH // D: Rebote con aplastamiento (cae rebotando, salta)
};
class AppLogo {
public:
AppLogo() = default;
~AppLogo(); // Necesario para liberar las 4 texturas SDL
// Inicializar textura y sprite del logo
bool initialize(SDL_Renderer* renderer, int screen_width, int screen_height);
// Actualizar temporizadores y estado de visibilidad
void update(float delta_time, AppMode current_mode);
// Renderizar logo si está visible
void render();
// Actualizar tamaño de pantalla (reposicionar logo)
void updateScreenSize(int screen_width, int screen_height);
private:
// ====================================================================
// Texturas pre-escaladas (4 texturas: 2 logos × 2 resoluciones)
// ====================================================================
SDL_Texture* logo1_base_texture_ = nullptr; // Logo1 para resolución base
SDL_Texture* logo1_native_texture_ = nullptr; // Logo1 para resolución nativa (F4)
SDL_Texture* logo2_base_texture_ = nullptr; // Logo2 para resolución base
SDL_Texture* logo2_native_texture_ = nullptr; // Logo2 para resolución nativa (F4)
// Dimensiones pre-calculadas para cada textura
int logo1_base_width_ = 0, logo1_base_height_ = 0;
int logo1_native_width_ = 0, logo1_native_height_ = 0;
int logo2_base_width_ = 0, logo2_base_height_ = 0;
int logo2_native_width_ = 0, logo2_native_height_ = 0;
// Texturas actualmente en uso (apuntan a base o native según resolución)
SDL_Texture* logo1_current_texture_ = nullptr;
SDL_Texture* logo2_current_texture_ = nullptr;
int logo1_current_width_ = 0, logo1_current_height_ = 0;
int logo2_current_width_ = 0, logo2_current_height_ = 0;
// Resoluciones conocidas
int base_screen_width_ = 0, base_screen_height_ = 0; // Resolución inicial
int native_screen_width_ = 0, native_screen_height_ = 0; // Resolución nativa (F4)
// ====================================================================
// Variables COMPARTIDAS (sincronización de ambos logos)
// ====================================================================
AppLogoState state_ = AppLogoState::HIDDEN; // Estado actual de la máquina de estados
float timer_ = 0.0f; // Contador de tiempo para estado actual
// Alpha INDEPENDIENTE para cada logo (Logo 2 con retraso)
int logo1_alpha_ = 0; // Alpha de Logo 1 (0-255)
int logo2_alpha_ = 0; // Alpha de Logo 2 (0-255, con retraso)
// Animación COMPARTIDA (misma para ambos logos, misma entrada y salida)
AppLogoAnimationType current_animation_ = AppLogoAnimationType::ZOOM_ONLY;
// Variables de deformación INDEPENDIENTES para logo1
float logo1_scale_ = 1.0f; // Escala actual de logo1 (1.0 = 100%)
float logo1_squash_y_ = 1.0f; // Factor de aplastamiento vertical logo1
float logo1_stretch_x_ = 1.0f; // Factor de estiramiento horizontal logo1
float logo1_rotation_ = 0.0f; // Rotación en radianes logo1
// Variables de deformación INDEPENDIENTES para logo2
float logo2_scale_ = 1.0f; // Escala actual de logo2 (1.0 = 100%)
float logo2_squash_y_ = 1.0f; // Factor de aplastamiento vertical logo2
float logo2_stretch_x_ = 1.0f; // Factor de estiramiento horizontal logo2
float logo2_rotation_ = 0.0f; // Rotación en radianes logo2
int screen_width_ = 0; // Ancho de pantalla (para centrar)
int screen_height_ = 0; // Alto de pantalla (para centrar)
// Tamaño base del logo (calculado una vez)
float base_width_ = 0.0f;
float base_height_ = 0.0f;
// SDL renderer (necesario para renderizado con geometría)
SDL_Renderer* renderer_ = nullptr;
// Métodos privados auxiliares
void updateLogoPosition(); // Centrar ambos logos en pantalla (superpuestos)
void renderWithGeometry(int logo_index); // Renderizar logo con vértices deformados (1 o 2)
// Funciones de easing
float easeOutElastic(float t); // Elastic bounce out
float easeOutBack(float t); // Overshoot out
float easeOutBounce(float t); // Bounce easing (para BOUNCE_SQUASH)
float easeInOutQuad(float t); // Quadratic easing (para ROTATE_SPIRAL)
// Función auxiliar para elegir animación aleatoria
AppLogoAnimationType getRandomAnimation();
};

View File

@@ -1,10 +1,10 @@
#include "ball.h"
#include "ball.hpp"
#include <stdlib.h> // for rand
#include <cmath> // for fabs
#include "defines.h" // for Color, SCREEN_HEIGHT, GRAVITY_FORCE
#include "defines.hpp" // for Color, SCREEN_HEIGHT, GRAVITY_FORCE
class Texture;
// Función auxiliar para generar pérdida aleatoria en rebotes
@@ -22,9 +22,9 @@ float generateLateralLoss() {
}
// Constructor
Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> texture, int screen_width, int screen_height, int ball_size, GravityDirection gravity_dir, float mass_factor)
Ball::Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr<Texture> texture, int screen_width, int screen_height, int ball_size, GravityDirection gravity_dir, float mass_factor)
: sprite_(std::make_unique<Sprite>(texture)),
pos_({x, 0.0f, static_cast<float>(ball_size), static_cast<float>(ball_size)}) {
pos_({x, y, static_cast<float>(ball_size), static_cast<float>(ball_size)}) {
// Convertir velocidades de píxeles/frame a píxeles/segundo (multiplicar por 60)
vx_ = vx * 60.0f;
vy_ = vy * 60.0f;

View File

@@ -4,8 +4,8 @@
#include <memory> // for shared_ptr, unique_ptr
#include "defines.h" // for Color
#include "external/sprite.h" // for Sprite
#include "defines.hpp" // for Color
#include "external/sprite.hpp" // for Sprite
class Texture;
class Ball {
@@ -31,7 +31,7 @@ class Ball {
public:
// Constructor
Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> texture, int screen_width, int screen_height, int ball_size, GravityDirection gravity_dir = GravityDirection::DOWN, float mass_factor = 1.0f);
Ball(float x, float y, float vx, float vy, Color color, std::shared_ptr<Texture> texture, int screen_width, int screen_height, int ball_size, GravityDirection gravity_dir = GravityDirection::DOWN, float mass_factor = 1.0f);
// Destructor
~Ball() = default;

View File

@@ -1,13 +1,13 @@
#include "boid_manager.h"
#include "boid_manager.hpp"
#include <algorithm> // for std::min, std::max
#include <cmath> // for sqrt, atan2
#include "../ball.h" // for Ball
#include "../engine.h" // for Engine (si se necesita)
#include "../scene/scene_manager.h" // for SceneManager
#include "../state/state_manager.h" // for StateManager
#include "../ui/ui_manager.h" // for UIManager
#include "ball.hpp" // for Ball
#include "engine.hpp" // for Engine (si se necesita)
#include "scene/scene_manager.hpp" // for SceneManager
#include "state/state_manager.hpp" // for StateManager
#include "ui/ui_manager.hpp" // for UIManager
BoidManager::BoidManager()
: engine_(nullptr)
@@ -16,7 +16,19 @@ BoidManager::BoidManager()
, state_mgr_(nullptr)
, screen_width_(0)
, screen_height_(0)
, boids_active_(false) {
, boids_active_(false)
, spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) // Tamaño por defecto, se actualiza en initialize()
, separation_radius_(BOID_SEPARATION_RADIUS)
, alignment_radius_(BOID_ALIGNMENT_RADIUS)
, cohesion_radius_(BOID_COHESION_RADIUS)
, separation_weight_(BOID_SEPARATION_WEIGHT)
, alignment_weight_(BOID_ALIGNMENT_WEIGHT)
, cohesion_weight_(BOID_COHESION_WEIGHT)
, max_speed_(BOID_MAX_SPEED)
, min_speed_(BOID_MIN_SPEED)
, max_force_(BOID_MAX_FORCE)
, boundary_margin_(BOID_BOUNDARY_MARGIN)
, boundary_weight_(BOID_BOUNDARY_WEIGHT) {
}
BoidManager::~BoidManager() {
@@ -30,11 +42,17 @@ void BoidManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager*
state_mgr_ = state_mgr;
screen_width_ = screen_width;
screen_height_ = screen_height;
// Actualizar dimensiones del spatial grid
spatial_grid_.updateWorldSize(screen_width, screen_height);
}
void BoidManager::updateScreenSize(int width, int height) {
screen_width_ = width;
screen_height_ = height;
// Actualizar dimensiones del spatial grid (FASE 2)
spatial_grid_.updateWorldSize(width, height);
}
void BoidManager::activateBoids() {
@@ -50,9 +68,9 @@ void BoidManager::activateBoids() {
float vx, vy;
ball->getVelocity(vx, vy);
if (vx == 0.0f && vy == 0.0f) {
// Velocidad aleatoria entre -1 y 1
vx = (rand() % 200 - 100) / 100.0f;
vy = (rand() % 200 - 100) / 100.0f;
// Velocidad aleatoria entre -60 y +60 px/s (time-based)
vx = ((rand() % 200 - 100) / 100.0f) * 60.0f;
vy = ((rand() % 200 - 100) / 100.0f) * 60.0f;
ball->setVelocity(vx, vy);
}
}
@@ -92,7 +110,17 @@ void BoidManager::update(float delta_time) {
auto& balls = scene_mgr_->getBallsMutable();
// FASE 2: Poblar spatial grid al inicio de cada frame (O(n))
spatial_grid_.clear();
for (auto& ball : balls) {
SDL_FRect pos = ball->getPosition();
float center_x = pos.x + pos.w / 2.0f;
float center_y = pos.y + pos.h / 2.0f;
spatial_grid_.insert(ball.get(), center_x, center_y);
}
// Aplicar las tres reglas de Reynolds a cada boid
// FASE 2: Ahora usa spatial grid para búsquedas O(1) en lugar de O(n)
for (auto& ball : balls) {
applySeparation(ball.get(), delta_time);
applyAlignment(ball.get(), delta_time);
@@ -101,14 +129,14 @@ void BoidManager::update(float delta_time) {
limitSpeed(ball.get());
}
// Actualizar posiciones con velocidades resultantes
// Actualizar posiciones con velocidades resultantes (time-based)
for (auto& ball : balls) {
float vx, vy;
ball->getVelocity(vx, vy);
SDL_FRect pos = ball->getPosition();
pos.x += vx;
pos.y += vy;
pos.x += vx * delta_time; // time-based
pos.y += vy * delta_time;
ball->setPosition(pos.x, pos.y);
}
@@ -128,9 +156,11 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
float center_x = pos.x + pos.w / 2.0f;
float center_y = pos.y + pos.h / 2.0f;
const auto& balls = scene_mgr_->getBalls();
for (const auto& other : balls) {
if (other.get() == boid) continue; // Ignorar a sí mismo
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, separation_radius_);
for (Ball* other : neighbors) {
if (other == boid) continue; // Ignorar a sí mismo
SDL_FRect other_pos = other->getPosition();
float other_x = other_pos.x + other_pos.w / 2.0f;
@@ -140,10 +170,12 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
float dy = center_y - other_y;
float distance = std::sqrt(dx * dx + dy * dy);
if (distance > 0.0f && distance < BOID_SEPARATION_RADIUS) {
// Vector normalizado apuntando lejos del vecino, ponderado por cercanía
steer_x += (dx / distance) / distance;
steer_y += (dy / distance) / distance;
if (distance > 0.0f && distance < separation_radius_) {
// FASE 1.3: Separación más fuerte cuando más cerca (inversamente proporcional a distancia)
// Fuerza proporcional a cercanía: 0% en radio máximo, 100% en colisión
float separation_strength = (separation_radius_ - distance) / separation_radius_;
steer_x += (dx / distance) * separation_strength;
steer_y += (dy / distance) * separation_strength;
count++;
}
}
@@ -156,8 +188,8 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
// Aplicar fuerza de separación
float vx, vy;
boid->getVelocity(vx, vy);
vx += steer_x * BOID_SEPARATION_WEIGHT * delta_time;
vy += steer_y * BOID_SEPARATION_WEIGHT * delta_time;
vx += steer_x * separation_weight_ * delta_time;
vy += steer_y * separation_weight_ * delta_time;
boid->setVelocity(vx, vy);
}
}
@@ -172,9 +204,11 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
float center_x = pos.x + pos.w / 2.0f;
float center_y = pos.y + pos.h / 2.0f;
const auto& balls = scene_mgr_->getBalls();
for (const auto& other : balls) {
if (other.get() == boid) continue;
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, alignment_radius_);
for (Ball* other : neighbors) {
if (other == boid) continue;
SDL_FRect other_pos = other->getPosition();
float other_x = other_pos.x + other_pos.w / 2.0f;
@@ -184,7 +218,7 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
float dy = center_y - other_y;
float distance = std::sqrt(dx * dx + dy * dy);
if (distance < BOID_ALIGNMENT_RADIUS) {
if (distance < alignment_radius_) {
float other_vx, other_vy;
other->getVelocity(other_vx, other_vy);
avg_vx += other_vx;
@@ -201,14 +235,14 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
// Steering hacia la velocidad promedio
float vx, vy;
boid->getVelocity(vx, vy);
float steer_x = (avg_vx - vx) * BOID_ALIGNMENT_WEIGHT * delta_time;
float steer_y = (avg_vy - vy) * BOID_ALIGNMENT_WEIGHT * delta_time;
float steer_x = (avg_vx - vx) * alignment_weight_ * delta_time;
float steer_y = (avg_vy - vy) * alignment_weight_ * delta_time;
// Limitar fuerza máxima de steering
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
if (steer_mag > BOID_MAX_FORCE) {
steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
if (steer_mag > max_force_) {
steer_x = (steer_x / steer_mag) * max_force_;
steer_y = (steer_y / steer_mag) * max_force_;
}
vx += steer_x;
@@ -227,9 +261,11 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
float center_x = pos.x + pos.w / 2.0f;
float center_y = pos.y + pos.h / 2.0f;
const auto& balls = scene_mgr_->getBalls();
for (const auto& other : balls) {
if (other.get() == boid) continue;
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, cohesion_radius_);
for (Ball* other : neighbors) {
if (other == boid) continue;
SDL_FRect other_pos = other->getPosition();
float other_x = other_pos.x + other_pos.w / 2.0f;
@@ -239,7 +275,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
float dy = center_y - other_y;
float distance = std::sqrt(dx * dx + dy * dy);
if (distance < BOID_COHESION_RADIUS) {
if (distance < cohesion_radius_) {
center_of_mass_x += other_x;
center_of_mass_y += other_y;
count++;
@@ -251,52 +287,97 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
center_of_mass_x /= count;
center_of_mass_y /= count;
// Dirección hacia el centro
float steer_x = (center_of_mass_x - center_x) * BOID_COHESION_WEIGHT * delta_time;
float steer_y = (center_of_mass_y - center_y) * BOID_COHESION_WEIGHT * delta_time;
// FASE 1.4: Normalizar dirección hacia el centro (CRÍTICO - antes no estaba normalizado!)
float dx_to_center = center_of_mass_x - center_x;
float dy_to_center = center_of_mass_y - center_y;
float distance_to_center = std::sqrt(dx_to_center * dx_to_center + dy_to_center * dy_to_center);
// Limitar fuerza máxima de steering
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
if (steer_mag > BOID_MAX_FORCE) {
steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
// Solo aplicar si hay distancia al centro (evitar división por cero)
if (distance_to_center > 0.1f) {
// Normalizar vector dirección (fuerza independiente de distancia)
float steer_x = (dx_to_center / distance_to_center) * cohesion_weight_ * delta_time;
float steer_y = (dy_to_center / distance_to_center) * cohesion_weight_ * delta_time;
// Limitar fuerza máxima de steering
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
if (steer_mag > max_force_) {
steer_x = (steer_x / steer_mag) * max_force_;
steer_y = (steer_y / steer_mag) * max_force_;
}
float vx, vy;
boid->getVelocity(vx, vy);
vx += steer_x;
vy += steer_y;
boid->setVelocity(vx, vy);
}
float vx, vy;
boid->getVelocity(vx, vy);
vx += steer_x;
vy += steer_y;
boid->setVelocity(vx, vy);
}
}
void BoidManager::applyBoundaries(Ball* boid) {
// Mantener boids dentro de los límites de la pantalla
// Comportamiento "wrapping" (teletransporte al otro lado)
// NUEVA IMPLEMENTACIÓN: Bordes como obstáculos (repulsión en lugar de wrapping)
// Cuando un boid se acerca a un borde, se aplica una fuerza alejándolo
SDL_FRect pos = boid->getPosition();
float center_x = pos.x + pos.w / 2.0f;
float center_y = pos.y + pos.h / 2.0f;
bool wrapped = false;
float steer_x = 0.0f;
float steer_y = 0.0f;
if (center_x < 0) {
pos.x = screen_width_ - pos.w / 2.0f;
wrapped = true;
} else if (center_x > screen_width_) {
pos.x = -pos.w / 2.0f;
wrapped = true;
// Borde izquierdo (x < boundary_margin_)
if (center_x < boundary_margin_) {
float distance = center_x; // Distancia al borde (0 = colisión)
if (distance < boundary_margin_) {
// Fuerza proporcional a cercanía: 0% en margen, 100% en colisión
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
steer_x += repulsion_strength; // Empujar hacia la derecha
}
}
if (center_y < 0) {
pos.y = screen_height_ - pos.h / 2.0f;
wrapped = true;
} else if (center_y > screen_height_) {
pos.y = -pos.h / 2.0f;
wrapped = true;
// Borde derecho (x > screen_width_ - boundary_margin_)
if (center_x > screen_width_ - boundary_margin_) {
float distance = screen_width_ - center_x;
if (distance < boundary_margin_) {
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
steer_x -= repulsion_strength; // Empujar hacia la izquierda
}
}
if (wrapped) {
boid->setPosition(pos.x, pos.y);
// Borde superior (y < boundary_margin_)
if (center_y < boundary_margin_) {
float distance = center_y;
if (distance < boundary_margin_) {
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
steer_y += repulsion_strength; // Empujar hacia abajo
}
}
// Borde inferior (y > screen_height_ - boundary_margin_)
if (center_y > screen_height_ - boundary_margin_) {
float distance = screen_height_ - center_y;
if (distance < boundary_margin_) {
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
steer_y -= repulsion_strength; // Empujar hacia arriba
}
}
// Aplicar fuerza de repulsión si hay alguna
if (steer_x != 0.0f || steer_y != 0.0f) {
float vx, vy;
boid->getVelocity(vx, vy);
// Normalizar fuerza de repulsión (para que todas las direcciones tengan la misma intensidad)
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
if (steer_mag > 0.0f) {
steer_x /= steer_mag;
steer_y /= steer_mag;
}
// Aplicar aceleración de repulsión (time-based)
// boundary_weight_ es más fuerte que separation para garantizar que no escapen
vx += steer_x * boundary_weight_ * (1.0f / 60.0f); // Simular delta_time fijo para independencia
vy += steer_y * boundary_weight_ * (1.0f / 60.0f);
boid->setVelocity(vx, vy);
}
}
@@ -306,9 +387,18 @@ void BoidManager::limitSpeed(Ball* boid) {
boid->getVelocity(vx, vy);
float speed = std::sqrt(vx * vx + vy * vy);
if (speed > BOID_MAX_SPEED) {
vx = (vx / speed) * BOID_MAX_SPEED;
vy = (vy / speed) * BOID_MAX_SPEED;
// Limitar velocidad máxima
if (speed > max_speed_) {
vx = (vx / speed) * max_speed_;
vy = (vy / speed) * max_speed_;
boid->setVelocity(vx, vy);
}
// FASE 1.2: Aplicar velocidad mínima (evitar boids estáticos)
if (speed > 0.0f && speed < min_speed_) {
vx = (vx / speed) * min_speed_;
vy = (vy / speed) * min_speed_;
boid->setVelocity(vx, vy);
}
}

View File

@@ -2,7 +2,8 @@
#include <cstddef> // for size_t
#include "../defines.h" // for SimulationMode, AppMode
#include "defines.hpp" // for SimulationMode, AppMode
#include "spatial_grid.hpp" // for SpatialGrid
// Forward declarations
class Engine;
@@ -98,10 +99,28 @@ class BoidManager {
// Estado del modo boids
bool boids_active_;
// Spatial Hash Grid para optimización O(n²) → O(n)
// FASE 2: Grid reutilizable para búsquedas de vecinos
SpatialGrid spatial_grid_;
// === Parámetros ajustables en runtime (inicializados con valores de defines.h) ===
// Permite modificar comportamiento sin recompilar (para tweaking/debug visual)
float separation_radius_; // Radio de separación (evitar colisiones)
float alignment_radius_; // Radio de alineación (matching de velocidad)
float cohesion_radius_; // Radio de cohesión (centro de masa)
float separation_weight_; // Peso fuerza de separación (aceleración px/s²)
float alignment_weight_; // Peso fuerza de alineación (steering proporcional)
float cohesion_weight_; // Peso fuerza de cohesión (aceleración px/s²)
float max_speed_; // Velocidad máxima (px/s)
float min_speed_; // Velocidad mínima (px/s)
float max_force_; // Fuerza máxima de steering (px/s)
float boundary_margin_; // Margen para repulsión de bordes (px)
float boundary_weight_; // Peso fuerza de repulsión de bordes (aceleración px/s²)
// Métodos privados para las reglas de Reynolds
void applySeparation(Ball* boid, float delta_time);
void applyAlignment(Ball* boid, float delta_time);
void applyCohesion(Ball* boid, float delta_time);
void applyBoundaries(Ball* boid); // Mantener boids dentro de pantalla
void applyBoundaries(Ball* boid); // Repulsión de bordes (ya no wrapping)
void limitSpeed(Ball* boid); // Limitar velocidad máxima
};

View File

@@ -8,9 +8,9 @@
constexpr char WINDOW_CAPTION[] = "ViBe3 Physics (JailDesigner 2025)";
// Resolución por defecto (usada si no se especifica en CLI)
constexpr int DEFAULT_SCREEN_WIDTH = 320; // Ancho lógico por defecto (si no hay -w)
constexpr int DEFAULT_SCREEN_HEIGHT = 240; // Alto lógico por defecto (si no hay -h)
constexpr int DEFAULT_WINDOW_ZOOM = 3; // Zoom inicial de ventana (1x = sin zoom)
constexpr int DEFAULT_SCREEN_WIDTH = 1280; // Ancho lógico por defecto (si no hay -w)
constexpr int DEFAULT_SCREEN_HEIGHT = 720; // Alto lógico por defecto (si no hay -h)
constexpr int DEFAULT_WINDOW_ZOOM = 1; // Zoom inicial de ventana (1x = sin zoom)
// Configuración de zoom dinámico de ventana
constexpr int WINDOW_ZOOM_MIN = 1; // Zoom mínimo (320x240)
@@ -22,17 +22,17 @@ constexpr int WINDOW_DECORATION_HEIGHT = 30; // Altura estimada de decoraciones
constexpr float GRAVITY_FORCE = 0.2f; // Fuerza de gravedad (píxeles/frame²)
// Configuración de interfaz
constexpr Uint64 TEXT_DURATION = 2000; // Duración del texto informativo (ms) - OBSOLETO, usar NOTIFICATION_DURATION
constexpr float THEME_TRANSITION_DURATION = 0.5f; // Duración de transiciones LERP entre temas (segundos)
// Configuración de notificaciones (sistema Notifier)
constexpr int TEXT_ABSOLUTE_SIZE = 12; // Tamaño fuente base en píxeles físicos (múltiplo de 12px, tamaño nativo de la fuente)
constexpr Uint64 NOTIFICATION_DURATION = 2000; // Duración default de notificaciones (ms)
constexpr Uint64 NOTIFICATION_SLIDE_TIME = 300; // Duración animación entrada (ms)
constexpr Uint64 NOTIFICATION_FADE_TIME = 200; // Duración animación salida (ms)
constexpr float NOTIFICATION_BG_ALPHA = 0.7f; // Opacidad fondo semitransparente (0.0-1.0)
constexpr int NOTIFICATION_PADDING = 10; // Padding interno del fondo (píxeles físicos)
constexpr int NOTIFICATION_TOP_MARGIN = 20; // Margen superior desde borde pantalla (píxeles físicos)
constexpr int TEXT_ABSOLUTE_SIZE = 12; // Tamaño fuente base en píxeles físicos (múltiplo de 12px, tamaño nativo de la fuente)
constexpr Uint64 NOTIFICATION_DURATION = 2000; // Duración default de notificaciones (ms)
constexpr Uint64 NOTIFICATION_SLIDE_TIME = 300; // Duración animación entrada (ms)
constexpr Uint64 NOTIFICATION_FADE_TIME = 200; // Duración animación salida (ms)
constexpr float NOTIFICATION_BG_ALPHA = 0.7f; // Opacidad fondo semitransparente (0.0-1.0)
constexpr int NOTIFICATION_PADDING = 10; // Padding interno del fondo (píxeles físicos)
constexpr int NOTIFICATION_TOP_MARGIN = 20; // Margen superior desde borde pantalla (píxeles físicos)
constexpr char KIOSK_NOTIFICATION_TEXT[] = "MODO KIOSKO";
// Configuración de pérdida aleatoria en rebotes
constexpr float BASE_BOUNCE_COEFFICIENT = 0.75f; // Coeficiente base IGUAL para todas las pelotas
@@ -53,6 +53,14 @@ constexpr float BALL_SPAWN_MARGIN = 0.15f; // Margen lateral para spawn (0.25 =
// Escenarios de número de pelotas (teclas 1-8)
constexpr int BALL_COUNT_SCENARIOS[8] = {10, 50, 100, 500, 1000, 5000, 10000, 50000};
// Límites de escenario para modos automáticos (índices en BALL_COUNT_SCENARIOS)
// BALL_COUNT_SCENARIOS = {10, 50, 100, 500, 1000, 5000, 10000, 50000}
// 0 1 2 3 4 5 6 7
constexpr int DEMO_AUTO_MIN_SCENARIO = 2; // mínimo 100 bolas
constexpr int DEMO_AUTO_MAX_SCENARIO = 7; // máximo sin restricción hardware (ajustado por benchmark)
constexpr int LOGO_MIN_SCENARIO_IDX = 4; // mínimo 1000 bolas (sustituye LOGO_MODE_MIN_BALLS)
constexpr int CUSTOM_SCENARIO_IDX = 8; // Escenario custom opcional (tecla 9, --custom-balls)
// Estructura para representar colores RGB
struct Color {
int r, g, b; // Componentes rojo, verde, azul (0-255)
@@ -60,12 +68,12 @@ struct Color {
// Estructura de tema de colores estático
struct ThemeColors {
const char* name_en; // Nombre en inglés (para debug)
const char* name_es; // Nombre en español (para display)
int text_color_r, text_color_g, text_color_b; // Color del texto del tema
float bg_top_r, bg_top_g, bg_top_b;
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
std::vector<Color> ball_colors;
const char* name_en; // Nombre en inglés (para debug)
const char* name_es; // Nombre en español (para display)
int text_color_r, text_color_g, text_color_b; // Color del texto del tema
float bg_top_r, bg_top_g, bg_top_b;
float bg_bottom_r, bg_bottom_g, bg_bottom_b;
std::vector<Color> ball_colors;
};
// Estructura para keyframe de tema dinámico
@@ -99,21 +107,21 @@ enum class GravityDirection {
// Enum para temas de colores (seleccionables con teclado numérico y Shift+Numpad)
// Todos los temas usan ahora sistema dinámico de keyframes
enum class ColorTheme {
SUNSET = 0, // Naranjas, rojos, amarillos, rosas (estático: 1 keyframe)
OCEAN = 1, // Azules, turquesas, blancos (estático: 1 keyframe)
NEON = 2, // Cian, magenta, verde lima, amarillo vibrante (estático: 1 keyframe)
FOREST = 3, // Verdes, marrones, amarillos otoño (estático: 1 keyframe)
RGB = 4, // RGB puros y subdivisiones matemáticas - fondo blanco (estático: 1 keyframe)
MONOCHROME = 5, // Fondo negro degradado, sprites blancos monocromáticos (estático: 1 keyframe)
LAVENDER = 6, // Degradado violeta-azul, pelotas amarillo dorado (estático: 1 keyframe)
CRIMSON = 7, // Fondo negro-rojo, pelotas rojas uniformes (estático: 1 keyframe)
EMERALD = 8, // Fondo negro-verde, pelotas verdes uniformes (estático: 1 keyframe)
SUNRISE = 9, // Amanecer: Noche → Alba → Día (animado: 4 keyframes, 12s ciclo)
OCEAN_WAVES = 10, // Olas oceánicas: Azul oscuro ↔ Turquesa (animado: 3 keyframes, 8s ciclo)
NEON_PULSE = 11, // Pulso neón: Negro ↔ Neón vibrante (animado: 3 keyframes, 3s ping-pong)
FIRE = 12, // Fuego vivo: Brasas → Llamas → Inferno (animado: 4 keyframes, 10s ciclo)
AURORA = 13, // Aurora boreal: Verde → Violeta → Cian (animado: 4 keyframes, 14s ciclo)
VOLCANIC = 14 // Erupción volcánica: Ceniza → Erupción → Lava (animado: 4 keyframes, 12s ciclo)
SUNSET = 0, // Naranjas, rojos, amarillos, rosas (estático: 1 keyframe)
OCEAN = 1, // Azules, turquesas, blancos (estático: 1 keyframe)
NEON = 2, // Cian, magenta, verde lima, amarillo vibrante (estático: 1 keyframe)
FOREST = 3, // Verdes, marrones, amarillos otoño (estático: 1 keyframe)
RGB = 4, // RGB puros y subdivisiones matemáticas - fondo blanco (estático: 1 keyframe)
MONOCHROME = 5, // Fondo negro degradado, sprites blancos monocromáticos (estático: 1 keyframe)
LAVENDER = 6, // Degradado violeta-azul, pelotas amarillo dorado (estático: 1 keyframe)
CRIMSON = 7, // Fondo negro-rojo, pelotas rojas uniformes (estático: 1 keyframe)
EMERALD = 8, // Fondo negro-verde, pelotas verdes uniformes (estático: 1 keyframe)
SUNRISE = 9, // Amanecer: Noche → Alba → Día (animado: 4 keyframes, 12s ciclo)
OCEAN_WAVES = 10, // Olas oceánicas: Azul oscuro ↔ Turquesa (animado: 3 keyframes, 8s ciclo)
NEON_PULSE = 11, // Pulso neón: Negro ↔ Neón vibrante (animado: 3 keyframes, 3s ping-pong)
FIRE = 12, // Fuego vivo: Brasas → Llamas → Inferno (animado: 4 keyframes, 10s ciclo)
AURORA = 13, // Aurora boreal: Verde → Violeta → Cian (animado: 4 keyframes, 14s ciclo)
VOLCANIC = 14 // Erupción volcánica: Ceniza → Erupción → Lava (animado: 4 keyframes, 12s ciclo)
};
// Enum para tipo de figura 3D
@@ -282,21 +290,42 @@ constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO = 5; // 5% probabilidad en D
constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO_LITE = 3; // 3% probabilidad en DEMO LITE (aún más raro)
// Sistema de espera de flips en LOGO MODE (camino alternativo de decisión)
constexpr int LOGO_FLIP_WAIT_MIN = 1; // Mínimo de flips a esperar antes de cambiar a PHYSICS
constexpr int LOGO_FLIP_WAIT_MAX = 3; // Máximo de flips a esperar
constexpr float LOGO_FLIP_TRIGGER_MIN = 0.20f; // 20% mínimo de progreso de flip para trigger
constexpr float LOGO_FLIP_TRIGGER_MAX = 0.80f; // 80% máximo de progreso de flip para trigger
constexpr int LOGO_FLIP_WAIT_PROBABILITY = 50; // 50% probabilidad de elegir el camino "esperar flip"
constexpr int LOGO_FLIP_WAIT_MIN = 1; // Mínimo de flips a esperar antes de cambiar a PHYSICS
constexpr int LOGO_FLIP_WAIT_MAX = 3; // Máximo de flips a esperar
constexpr float LOGO_FLIP_TRIGGER_MIN = 0.20f; // 20% mínimo de progreso de flip para trigger
constexpr float LOGO_FLIP_TRIGGER_MAX = 0.80f; // 80% máximo de progreso de flip para trigger
constexpr int LOGO_FLIP_WAIT_PROBABILITY = 50; // 50% probabilidad de elegir el camino "esperar flip"
// Configuración de AppLogo (logo periódico en pantalla)
constexpr float APPLOGO_DISPLAY_INTERVAL = 90.0f; // Intervalo entre apariciones del logo (segundos)
constexpr float APPLOGO_DISPLAY_DURATION = 30.0f; // Duración de visibilidad del logo (segundos)
constexpr float APPLOGO_ANIMATION_DURATION = 0.5f; // Duración de animación entrada/salida (segundos)
constexpr float APPLOGO_HEIGHT_PERCENT = 0.4f; // Altura del logo = 40% de la altura de pantalla
constexpr float APPLOGO_PADDING_PERCENT = 0.05f; // Padding desde esquina inferior-derecha = 10%
constexpr float APPLOGO_LOGO2_DELAY = 0.25f; // Retraso de Logo 2 respecto a Logo 1 (segundos)
// Configuración de Modo BOIDS (comportamiento de enjambre)
constexpr float BOID_SEPARATION_RADIUS = 30.0f; // Radio para evitar colisiones (píxeles)
constexpr float BOID_ALIGNMENT_RADIUS = 50.0f; // Radio para alinear velocidad con vecinos
constexpr float BOID_COHESION_RADIUS = 80.0f; // Radio para moverse hacia centro del grupo
constexpr float BOID_SEPARATION_WEIGHT = 1.5f; // Peso de separación (evitar colisiones)
constexpr float BOID_ALIGNMENT_WEIGHT = 1.0f; // Peso de alineación (seguir dirección del grupo)
constexpr float BOID_COHESION_WEIGHT = 0.8f; // Peso de cohesión (moverse al centro)
constexpr float BOID_MAX_SPEED = 3.0f; // Velocidad máxima (píxeles/frame)
constexpr float BOID_MAX_FORCE = 0.1f; // Fuerza máxima de steering
// TIME-BASED CONVERSION (frame-based → time-based):
// - Radios: sin cambios (píxeles)
// - Velocidades (MAX_SPEED, MIN_SPEED): ×60 (px/frame → px/s)
// - Aceleraciones puras (SEPARATION, COHESION): ×60² = ×3600 (px/frame² → px/s²)
// - Steering proporcional (ALIGNMENT): ×60 (proporcional a velocidad)
// - Límite velocidad (MAX_FORCE): ×60 (px/frame → px/s)
constexpr float BOID_SEPARATION_RADIUS = 30.0f; // Radio para evitar colisiones (píxeles)
constexpr float BOID_ALIGNMENT_RADIUS = 50.0f; // Radio para alinear velocidad con vecinos
constexpr float BOID_COHESION_RADIUS = 80.0f; // Radio para moverse hacia centro del grupo
constexpr float BOID_SEPARATION_WEIGHT = 5400.0f; // Aceleración de separación (px/s²) [era 1.5 × 3600]
constexpr float BOID_ALIGNMENT_WEIGHT = 60.0f; // Steering de alineación (proporcional) [era 1.0 × 60]
constexpr float BOID_COHESION_WEIGHT = 3.6f; // Aceleración de cohesión (px/s²) [era 0.001 × 3600]
constexpr float BOID_MAX_SPEED = 150.0f; // Velocidad máxima (px/s) [era 2.5 × 60]
constexpr float BOID_MAX_FORCE = 3.0f; // Fuerza máxima de steering (px/s) [era 0.05 × 60]
constexpr float BOID_MIN_SPEED = 18.0f; // Velocidad mínima (px/s) [era 0.3 × 60]
constexpr float BOID_BOUNDARY_MARGIN = 50.0f; // Distancia a borde para activar repulsión (píxeles)
constexpr float BOID_BOUNDARY_WEIGHT = 7200.0f; // Aceleración de repulsión de bordes (px/s²) [más fuerte que separation]
// FASE 2: Spatial Hash Grid para optimización O(n²) → O(n)
constexpr float BOID_GRID_CELL_SIZE = 100.0f; // Tamaño de celda del grid (píxeles)
// Debe ser ≥ BOID_COHESION_RADIUS para funcionar correctamente
constexpr float PI = 3.14159265358979323846f; // Constante PI

View File

@@ -1,4 +1,4 @@
#include "engine.h"
#include "engine.hpp"
#include <SDL3/SDL_error.h> // for SDL_GetError
#include <SDL3/SDL_events.h> // for SDL_Event, SDL_PollEvent
@@ -17,27 +17,29 @@
#include <iostream> // for cout
#include <string> // for string
#include "resource_manager.hpp" // for ResourceManager
#ifdef _WIN32
#include <windows.h> // for GetModuleFileName
#endif
#include "ball.h" // for Ball
#include "external/mouse.h" // for Mouse namespace
#include "external/texture.h" // for Texture
#include "shapes/atom_shape.h" // for AtomShape
#include "shapes/cube_shape.h" // for CubeShape
#include "shapes/cylinder_shape.h" // for CylinderShape
#include "shapes/helix_shape.h" // for HelixShape
#include "shapes/icosahedron_shape.h" // for IcosahedronShape
#include "shapes/lissajous_shape.h" // for LissajousShape
#include "shapes/png_shape.h" // for PNGShape
#include "shapes/sphere_shape.h" // for SphereShape
#include "shapes/torus_shape.h" // for TorusShape
#include "ball.hpp" // for Ball
#include "external/mouse.hpp" // for Mouse namespace
#include "external/texture.hpp" // for Texture
#include "shapes/atom_shape.hpp" // for AtomShape
#include "shapes/cube_shape.hpp" // for CubeShape
#include "shapes/cylinder_shape.hpp" // for CylinderShape
#include "shapes/helix_shape.hpp" // for HelixShape
#include "shapes/icosahedron_shape.hpp" // for IcosahedronShape
#include "shapes/lissajous_shape.hpp" // for LissajousShape
#include "shapes/png_shape.hpp" // for PNGShape
#include "shapes/sphere_shape.hpp" // for SphereShape
#include "shapes/torus_shape.hpp" // for TorusShape
// getExecutableDirectory() ya está definido en defines.h como inline
// Implementación de métodos públicos
bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMode initial_mode) {
bool success = true;
// Obtener resolución de pantalla para validación
@@ -72,7 +74,16 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
// VALIDACIÓN 2: Calcular max_zoom y ajustar si es necesario
int max_zoom = std::min(screen_w / logical_width, screen_h / logical_height);
if (window_zoom > max_zoom) {
if (max_zoom < 1) {
// Resolució lògica no cap en pantalla ni a zoom=1: escalar-la per fer-la càpida
float scale = std::min(static_cast<float>(screen_w) / logical_width,
static_cast<float>(screen_h) / logical_height);
logical_width = std::max(320, static_cast<int>(logical_width * scale));
logical_height = std::max(240, static_cast<int>(logical_height * scale));
window_zoom = 1;
std::cout << "Advertencia: Resolución no cabe en pantalla. Ajustando a "
<< logical_width << "x" << logical_height << "\n";
} else if (window_zoom > max_zoom) {
std::cout << "Advertencia: Zoom " << window_zoom << " excede máximo " << max_zoom
<< " para " << logical_width << "x" << logical_height << ". Ajustando a " << max_zoom << "\n";
window_zoom = max_zoom;
@@ -164,9 +175,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
}
}
} else {
// Fallback: cargar texturas desde pack usando la lista del ResourcePack
if (Texture::isPackLoaded()) {
auto pack_resources = Texture::getPackResourceList();
// Fallback: cargar texturas desde pack usando la lista del ResourceManager
if (ResourceManager::isPackLoaded()) {
auto pack_resources = ResourceManager::getResourceList();
// Filtrar solo los recursos en balls/ con extensión .png
for (const auto& resource : pack_resources) {
@@ -224,6 +235,10 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
scene_manager_ = std::make_unique<SceneManager>(current_screen_width_, current_screen_height_);
scene_manager_->initialize(0, texture_, theme_manager_.get()); // Escenario 0 (10 bolas) por defecto
// Propagar configuración custom si fue establecida antes de initialize()
if (custom_scenario_enabled_)
scene_manager_->setCustomBallCount(custom_scenario_balls_);
// Calcular tamaño físico de ventana ANTES de inicializar UIManager
// NOTA: No llamar a updatePhysicalWindowSize() aquí porque ui_manager_ aún no existe
// Calcular manualmente para poder pasar valores al constructor de UIManager
@@ -235,7 +250,9 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
// Inicializar UIManager (HUD, FPS, notificaciones)
// NOTA: Debe llamarse DESPUÉS de calcular physical_window_* y ThemeManager
ui_manager_ = std::make_unique<UIManager>();
ui_manager_->initialize(renderer_, theme_manager_.get(), physical_window_width_, physical_window_height_);
ui_manager_->initialize(renderer_, theme_manager_.get(),
physical_window_width_, physical_window_height_,
current_screen_width_, current_screen_height_);
// Inicializar ShapeManager (gestión de figuras 3D)
shape_manager_ = std::make_unique<ShapeManager>();
@@ -246,6 +263,22 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
state_manager_ = std::make_unique<StateManager>();
state_manager_->initialize(this); // Callback al Engine
// Establecer modo inicial si no es SANDBOX (default)
// Usar métodos de alto nivel que ejecutan las acciones de configuración
if (initial_mode == AppMode::DEMO) {
state_manager_->toggleDemoMode(current_screen_width_, current_screen_height_);
// Como estamos en SANDBOX (default), toggleDemoMode() cambiará a DEMO + randomizará
}
else if (initial_mode == AppMode::DEMO_LITE) {
state_manager_->toggleDemoLiteMode(current_screen_width_, current_screen_height_);
// Como estamos en SANDBOX (default), toggleDemoLiteMode() cambiará a DEMO_LITE + randomizará
}
else if (initial_mode == AppMode::LOGO) {
size_t initial_ball_count = scene_manager_->getBallCount();
state_manager_->enterLogoMode(false, current_screen_width_, current_screen_height_, initial_ball_count);
// enterLogoMode() hace: setState(LOGO) + executeEnterLogoMode() (tema, PNG_SHAPE, etc.)
}
// Actualizar ShapeManager con StateManager (dependencia circular - StateManager debe existir primero)
shape_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(),
current_screen_width_, current_screen_height_);
@@ -254,6 +287,27 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
boid_manager_ = std::make_unique<BoidManager>();
boid_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(),
current_screen_width_, current_screen_height_);
// Inicializar AppLogo (logo periódico en pantalla)
app_logo_ = std::make_unique<AppLogo>();
if (!app_logo_->initialize(renderer_, current_screen_width_, current_screen_height_)) {
std::cerr << "Advertencia: No se pudo inicializar AppLogo (logo periódico)" << std::endl;
// No es crítico, continuar sin logo
app_logo_.reset();
}
// Benchmark de rendimiento (determina max_auto_scenario_ para modos automáticos)
if (!skip_benchmark_)
runPerformanceBenchmark();
else if (custom_scenario_enabled_)
custom_auto_available_ = true; // benchmark omitido: confiar en que el hardware lo soporta
// Precalentar caché: shapes PNG (evitar I/O en primera activación de PNG_SHAPE)
{
unsigned char* tmp = nullptr; size_t tmp_size = 0;
ResourceManager::loadResource("shapes/jailgames.png", tmp, tmp_size);
delete[] tmp;
}
}
return success;
@@ -334,12 +388,27 @@ void Engine::update() {
// Actualizar transiciones de temas (delegado a ThemeManager)
theme_manager_->update(delta_time_);
// Actualizar AppLogo (logo periódico)
if (app_logo_) {
app_logo_->update(delta_time_, state_manager_->getCurrentMode());
}
}
// === IMPLEMENTACIÓN DE MÉTODOS PÚBLICOS PARA INPUT HANDLER ===
// Gravedad y física
void Engine::handleGravityToggle() {
// Si estamos en modo boids, salir a modo física CON GRAVEDAD OFF
// Según RULES.md: "BOIDS a PHYSICS: Pulsando la tecla G: Gravedad OFF"
if (current_mode_ == SimulationMode::BOIDS) {
toggleBoidsMode(false); // Cambiar a PHYSICS sin activar gravedad (preserva inercia)
// NO llamar a forceBallsGravityOff() porque aplica impulsos que destruyen la inercia de BOIDS
// La gravedad ya está desactivada por BoidManager::activateBoids() y se mantiene al salir
showNotificationForAction("Modo Física - Gravedad Off");
return;
}
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeModeInternal(false); // Desactivar figura sin forzar gravedad ON
@@ -354,12 +423,19 @@ void Engine::handleGravityToggle() {
}
void Engine::handleGravityDirectionChange(GravityDirection direction, const char* notification_text) {
// Si estamos en modo boids, salir a modo física primero PRESERVANDO VELOCIDAD
if (current_mode_ == SimulationMode::BOIDS) {
current_mode_ = SimulationMode::PHYSICS;
boid_manager_->deactivateBoids(false); // NO activar gravedad aún (preservar momentum)
scene_manager_->forceBallsGravityOn(); // Activar gravedad SIN impulsos (preserva velocidad)
}
// Si estamos en modo figura, salir a modo física CON gravedad
if (current_mode_ == SimulationMode::SHAPE) {
else if (current_mode_ == SimulationMode::SHAPE) {
toggleShapeModeInternal(); // Desactivar figura (activa gravedad automáticamente)
} else {
scene_manager_->enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
}
scene_manager_->changeGravityDirection(direction);
showNotificationForAction(notification_text);
}
@@ -369,6 +445,10 @@ void Engine::toggleDebug() {
ui_manager_->toggleDebug();
}
void Engine::toggleHelp() {
ui_manager_->toggleHelp();
}
// Figuras 3D
void Engine::toggleShapeMode() {
toggleShapeModeInternal();
@@ -415,11 +495,11 @@ void Engine::toggleDepthZoom() {
}
// Boids (comportamiento de enjambre)
void Engine::toggleBoidsMode() {
void Engine::toggleBoidsMode(bool force_gravity_on) {
if (current_mode_ == SimulationMode::BOIDS) {
// Salir del modo boids
// Salir del modo boids (velocidades ya son time-based, no requiere conversión)
current_mode_ = SimulationMode::PHYSICS;
boid_manager_->deactivateBoids();
boid_manager_->deactivateBoids(force_gravity_on); // Pasar parámetro para control preciso
} else {
// Entrar al modo boids (desde PHYSICS o SHAPE)
if (current_mode_ == SimulationMode::SHAPE) {
@@ -486,15 +566,51 @@ void Engine::switchTexture() {
switchTextureInternal(true); // Mostrar notificación en modo manual
}
// Control manual del benchmark (--skip-benchmark, --max-balls)
void Engine::setSkipBenchmark() {
skip_benchmark_ = true;
}
void Engine::setMaxBallsOverride(int n) {
skip_benchmark_ = true;
int best = DEMO_AUTO_MIN_SCENARIO;
for (int i = DEMO_AUTO_MIN_SCENARIO; i <= DEMO_AUTO_MAX_SCENARIO; ++i) {
if (BALL_COUNT_SCENARIOS[i] <= n) best = i;
else break;
}
max_auto_scenario_ = best;
}
// Escenario custom (--custom-balls)
void Engine::setCustomScenario(int balls) {
custom_scenario_balls_ = balls;
custom_scenario_enabled_ = true;
// scene_manager_ puede no existir aún (llamada pre-init); propagación en initialize()
if (scene_manager_)
scene_manager_->setCustomBallCount(balls);
}
// Escenarios (número de pelotas)
void Engine::changeScenario(int scenario_id, const char* notification_text) {
// Resetear modo SHAPE si está activo
// Pasar el modo actual al SceneManager para inicialización correcta
scene_manager_->changeScenario(scenario_id, current_mode_);
// Si estamos en modo SHAPE, regenerar la figura con nuevo número de pelotas
if (current_mode_ == SimulationMode::SHAPE) {
current_mode_ = SimulationMode::PHYSICS;
active_shape_.reset();
generateShape();
// Activar atracción física en las bolas nuevas (crítico tras changeScenario)
auto& balls = scene_manager_->getBallsMutable();
for (auto& ball : balls) {
ball->enableShapeAttraction(true);
}
}
// Si estamos en modo BOIDS, desactivar gravedad (modo BOIDS = gravedad OFF siempre)
if (current_mode_ == SimulationMode::BOIDS) {
scene_manager_->forceBallsGravityOff();
}
scene_manager_->changeScenario(scenario_id);
showNotificationForAction(notification_text);
}
@@ -602,34 +718,31 @@ void Engine::render() {
// MODO FIGURA 3D: Ordenar por profundidad Z (Painter's Algorithm)
// Las pelotas con menor depth_brightness (más lejos/oscuras) se renderizan primero
// Crear vector de índices para ordenamiento
std::vector<size_t> render_order;
render_order.reserve(balls.size());
// Bucket sort per profunditat Z (O(N) vs O(N log N))
for (size_t i = 0; i < balls.size(); i++) {
render_order.push_back(i);
int b = static_cast<int>(balls[i]->getDepthBrightness() * (DEPTH_SORT_BUCKETS - 1));
depth_buckets_[std::clamp(b, 0, DEPTH_SORT_BUCKETS - 1)].push_back(i);
}
// Ordenar índices por profundidad Z (menor primero = fondo primero)
std::sort(render_order.begin(), render_order.end(), [&balls](size_t a, size_t b) {
return balls[a]->getDepthBrightness() < balls[b]->getDepthBrightness();
});
// Renderizar en orden de profundidad (bucket 0 = fons, bucket 255 = davant)
for (int b = 0; b < DEPTH_SORT_BUCKETS; b++) {
for (size_t idx : depth_buckets_[b]) {
SDL_FRect pos = balls[idx]->getPosition();
Color color = theme_manager_->getInterpolatedColor(idx); // Usar color interpolado (LERP)
float brightness = balls[idx]->getDepthBrightness();
float depth_scale = balls[idx]->getDepthScale();
// Renderizar en orden de profundidad (fondo → frente)
for (size_t idx : render_order) {
SDL_FRect pos = balls[idx]->getPosition();
Color color = theme_manager_->getInterpolatedColor(idx); // Usar color interpolado (LERP)
float brightness = balls[idx]->getDepthBrightness();
float depth_scale = balls[idx]->getDepthScale();
// Mapear brightness de 0-1 a rango MIN-MAX
float brightness_factor = (ROTOBALL_MIN_BRIGHTNESS + brightness * (ROTOBALL_MAX_BRIGHTNESS - ROTOBALL_MIN_BRIGHTNESS)) / 255.0f;
// Mapear brightness de 0-1 a rango MIN-MAX
float brightness_factor = (ROTOBALL_MIN_BRIGHTNESS + brightness * (ROTOBALL_MAX_BRIGHTNESS - ROTOBALL_MIN_BRIGHTNESS)) / 255.0f;
// Aplicar factor de brillo al color
int r_mod = static_cast<int>(color.r * brightness_factor);
int g_mod = static_cast<int>(color.g * brightness_factor);
int b_mod = static_cast<int>(color.b * brightness_factor);
// Aplicar factor de brillo al color
int r_mod = static_cast<int>(color.r * brightness_factor);
int g_mod = static_cast<int>(color.g * brightness_factor);
int b_mod = static_cast<int>(color.b * brightness_factor);
addSpriteToBatch(pos.x, pos.y, pos.w, pos.h, r_mod, g_mod, b_mod, depth_scale);
addSpriteToBatch(pos.x, pos.y, pos.w, pos.h, r_mod, g_mod, b_mod, depth_scale);
}
depth_buckets_[b].clear(); // netejar per al proper frame
}
} else {
// MODO PHYSICS: Renderizar en orden normal del vector (sin escala de profundidad)
@@ -679,10 +792,15 @@ void Engine::render() {
*/
// Renderizar UI (debug HUD, texto obsoleto, notificaciones) - delegado a UIManager
ui_manager_->render(renderer_, scene_manager_.get(), current_mode_, state_manager_->getCurrentMode(),
ui_manager_->render(renderer_, this, scene_manager_.get(), current_mode_, state_manager_->getCurrentMode(),
active_shape_.get(), shape_convergence_,
physical_window_width_, physical_window_height_, current_screen_width_);
// Renderizar AppLogo (logo periódico) - después de UI, antes de present
if (app_logo_) {
app_logo_->render();
}
SDL_RenderPresent(renderer_);
}
@@ -717,6 +835,12 @@ void Engine::toggleFullscreen() {
fullscreen_enabled_ = !fullscreen_enabled_;
SDL_SetWindowFullscreen(window_, fullscreen_enabled_);
// Si acabamos de salir de fullscreen, restaurar tamaño de ventana
if (!fullscreen_enabled_) {
SDL_SetWindowSize(window_, base_screen_width_ * current_window_zoom_, base_screen_height_ * current_window_zoom_);
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
// Actualizar dimensiones físicas después del cambio
updatePhysicalWindowSize();
}
@@ -753,7 +877,26 @@ void Engine::toggleRealFullscreen() {
// Reinicar la escena con nueva resolución
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
scene_manager_->changeScenario(scene_manager_->getCurrentScenario());
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_);
// Actualizar tamaño de pantalla para boids (wrapping boundaries)
boid_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
// Actualizar AppLogo con nueva resolución
if (app_logo_) {
app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
}
// Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
if (current_mode_ == SimulationMode::SHAPE) {
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla
// Activar atracción física en las bolas nuevas (crítico tras changeScenario)
auto& balls = scene_manager_->getBallsMutable();
for (auto& ball : balls) {
ball->enableShapeAttraction(true);
}
}
}
SDL_free(displays);
}
@@ -775,7 +918,23 @@ void Engine::toggleRealFullscreen() {
// Reinicar la escena con resolución original
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
scene_manager_->changeScenario(scene_manager_->getCurrentScenario());
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_);
// Actualizar AppLogo con resolución restaurada
if (app_logo_) {
app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
}
// Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
if (current_mode_ == SimulationMode::SHAPE) {
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla
// Activar atracción física en las bolas nuevas (crítico tras changeScenario)
auto& balls = scene_manager_->getBallsMutable();
for (auto& ball : balls) {
ball->enableShapeAttraction(true);
}
}
}
}
@@ -1110,20 +1269,26 @@ void Engine::performLogoAction(bool logo_waiting_for_flip) {
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
}
} else {
// Logo animado (PHYSICS) → 3 opciones posibles
if (action < 60) {
// 60%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
// Logo animado (PHYSICS) → 4 opciones posibles
if (action < 50) {
// 50%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
toggleShapeModeInternal(false);
// Resetear variables de espera de flips al volver a SHAPE
logo_waiting_for_flip_ = false;
logo_current_flip_count_ = 0;
} else if (action < 80) {
// 20%: Forzar gravedad ON (empezar a caer mientras da vueltas)
} else if (action < 68) {
// 18%: Forzar gravedad ON (empezar a caer mientras da vueltas)
scene_manager_->forceBallsGravityOn();
} else {
// 20%: Forzar gravedad OFF (flotar mientras da vueltas)
} else if (action < 84) {
// 16%: Forzar gravedad OFF (flotar mientras da vueltas)
scene_manager_->forceBallsGravityOff();
} else {
// 16%: Cambiar dirección de gravedad (nueva variación)
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
scene_manager_->changeGravityDirection(new_direction);
// Si la gravedad está OFF, activarla para que el cambio sea visible
scene_manager_->forceBallsGravityOn();
}
// Resetear timer con intervalos escalados
@@ -1135,7 +1300,7 @@ void Engine::performLogoAction(bool logo_waiting_for_flip) {
// Solo salir automáticamente si la entrada a LOGO fue automática (desde DEMO)
// No salir si el usuario entró manualmente con tecla K
// Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1)
if (!logo_entered_manually_ && rand() % 100 < 60) {
if (!state_manager_->getLogoEnteredManually() && rand() % 100 < 60) {
state_manager_->exitLogoMode(true); // Volver a DEMO/DEMO_LITE
}
}
@@ -1164,7 +1329,7 @@ void Engine::executeDemoAction(bool is_lite) {
if (is_lite) {
// DEMO LITE: Verificar condiciones para salto a Logo Mode
if (static_cast<int>(scene_manager_->getBallCount()) >= LOGO_MODE_MIN_BALLS &&
if (static_cast<int>(scene_manager_->getBallCount()) >= BALL_COUNT_SCENARIOS[LOGO_MIN_SCENARIO_IDX] &&
theme_manager_->getCurrentThemeIndex() == 5) { // MONOCHROME
// 10% probabilidad de saltar a Logo Mode
if (rand() % 100 < LOGO_JUMP_PROBABILITY_FROM_DEMO_LITE) {
@@ -1174,7 +1339,7 @@ void Engine::executeDemoAction(bool is_lite) {
}
} else {
// DEMO COMPLETO: Verificar condiciones para salto a Logo Mode
if (static_cast<int>(scene_manager_->getBallCount()) >= LOGO_MODE_MIN_BALLS) {
if (static_cast<int>(scene_manager_->getBallCount()) >= BALL_COUNT_SCENARIOS[LOGO_MIN_SCENARIO_IDX]) {
// 15% probabilidad de saltar a Logo Mode
if (rand() % 100 < LOGO_JUMP_PROBABILITY_FROM_DEMO) {
state_manager_->enterLogoMode(true, current_screen_width_, current_screen_height_, scene_manager_->getBallCount());
@@ -1288,13 +1453,29 @@ void Engine::executeDemoAction(bool is_lite) {
return;
}
// Cambiar escenario (10%) - EXCLUIR índices 0, 6, 7 (1, 50K, 100K pelotas)
// Cambiar escenario (10%) - rango dinámico según benchmark de rendimiento
accumulated_weight += DEMO_WEIGHT_SCENARIO;
if (random_value < accumulated_weight) {
// Escenarios válidos: índices 1, 2, 3, 4, 5 (10, 100, 500, 1000, 10000 pelotas)
int valid_scenarios[] = {1, 2, 3, 4, 5};
int new_scenario = valid_scenarios[rand() % 5];
scene_manager_->changeScenario(new_scenario);
int auto_max = std::min(max_auto_scenario_, DEMO_AUTO_MAX_SCENARIO);
std::vector<int> candidates;
for (int i = DEMO_AUTO_MIN_SCENARIO; i <= auto_max; ++i)
candidates.push_back(i);
if (custom_scenario_enabled_ && custom_auto_available_)
candidates.push_back(CUSTOM_SCENARIO_IDX);
int new_scenario = candidates[rand() % candidates.size()];
scene_manager_->changeScenario(new_scenario, current_mode_);
// Si estamos en modo SHAPE, regenerar la figura con nuevo número de pelotas
if (current_mode_ == SimulationMode::SHAPE) {
generateShape();
// Activar atracción física en las bolas nuevas (crítico tras changeScenario)
auto& balls = scene_manager_->getBallsMutable();
for (auto& ball : balls) {
ball->enableShapeAttraction(true);
}
}
return;
}
@@ -1376,21 +1557,7 @@ void Engine::executeRandomizeOnDemoStart(bool is_lite) {
} else {
// DEMO COMPLETO: Randomizar TODO
// 1. Escenario (excluir índices 0, 6, 7)
int valid_scenarios[] = {1, 2, 3, 4, 5};
int new_scenario = valid_scenarios[rand() % 5];
scene_manager_->changeScenario(new_scenario);
// 2. Tema (elegir entre TODOS los 15 temas)
int random_theme_index = rand() % 15;
theme_manager_->switchToTheme(random_theme_index);
// 3. Sprite
if (rand() % 2 == 0) {
switchTextureInternal(false); // Suprimir notificación al activar modo DEMO
}
// 4. Física o Figura
// 1. Física o Figura (decidir PRIMERO antes de cambiar escenario)
if (rand() % 2 == 0) {
// Modo física
if (current_mode_ == SimulationMode::SHAPE) {
@@ -1399,20 +1566,88 @@ void Engine::executeRandomizeOnDemoStart(bool is_lite) {
} else {
// Modo figura: elegir figura aleatoria (excluir PNG_SHAPE - es logo especial)
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::LISSAJOUS, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShapeInternal(shapes[rand() % 8]);
ShapeType selected_shape = shapes[rand() % 8];
// 5. Profundidad (solo si estamos en figura)
// Configurar figura SIN generar puntos (changeScenario lo hará después)
last_shape_type_ = selected_shape;
current_shape_type_ = selected_shape;
current_mode_ = SimulationMode::SHAPE;
// Crear instancia de la figura sin generar puntos todavía
switch (selected_shape) {
case ShapeType::SPHERE:
active_shape_ = std::make_unique<SphereShape>();
break;
case ShapeType::CUBE:
active_shape_ = std::make_unique<CubeShape>();
break;
case ShapeType::HELIX:
active_shape_ = std::make_unique<HelixShape>();
break;
case ShapeType::TORUS:
active_shape_ = std::make_unique<TorusShape>();
break;
case ShapeType::LISSAJOUS:
active_shape_ = std::make_unique<LissajousShape>();
break;
case ShapeType::CYLINDER:
active_shape_ = std::make_unique<CylinderShape>();
break;
case ShapeType::ICOSAHEDRON:
active_shape_ = std::make_unique<IcosahedronShape>();
break;
case ShapeType::ATOM:
active_shape_ = std::make_unique<AtomShape>();
break;
default:
active_shape_ = std::make_unique<SphereShape>();
break;
}
// Profundidad (solo si estamos en figura)
if (rand() % 2 == 0) {
depth_zoom_enabled_ = !depth_zoom_enabled_;
}
// 6. Escala de figura (aleatoria entre 0.5x y 2.0x)
// Escala de figura (aleatoria entre 0.5x y 2.0x)
shape_scale_factor_ = 0.5f + (rand() % 1500) / 1000.0f;
clampShapeScale();
generateShape();
// NOTA: NO llamar a generateShape() ni activar atracción aquí
// changeScenario() creará las pelotas y luego llamará a generateShape()
}
// 7. Gravedad: dirección + ON/OFF
// 2. Escenario - rango dinámico según benchmark de rendimiento
int auto_max = std::min(max_auto_scenario_, DEMO_AUTO_MAX_SCENARIO);
std::vector<int> candidates;
for (int i = DEMO_AUTO_MIN_SCENARIO; i <= auto_max; ++i)
candidates.push_back(i);
if (custom_scenario_enabled_ && custom_auto_available_)
candidates.push_back(CUSTOM_SCENARIO_IDX);
int new_scenario = candidates[rand() % candidates.size()];
scene_manager_->changeScenario(new_scenario, current_mode_);
// Si estamos en modo SHAPE, generar la figura y activar atracción
if (current_mode_ == SimulationMode::SHAPE) {
generateShape();
// Activar atracción física en las bolas nuevas
auto& balls = scene_manager_->getBallsMutable();
for (auto& ball : balls) {
ball->enableShapeAttraction(true);
}
}
// 3. Tema (elegir entre TODOS los 15 temas)
int random_theme_index = rand() % 15;
theme_manager_->switchToTheme(random_theme_index);
// 4. Sprite
if (rand() % 2 == 0) {
switchTextureInternal(false); // Suprimir notificación al activar modo DEMO
}
// 5. Gravedad: dirección + ON/OFF
GravityDirection new_direction = static_cast<GravityDirection>(rand() % 4);
scene_manager_->changeGravityDirection(new_direction);
if (rand() % 3 == 0) { // 33% probabilidad de desactivar gravedad
@@ -1435,16 +1670,110 @@ void Engine::executeToggleGravityOnOff() {
}
}
// ============================================================================
// BENCHMARK DE RENDIMIENTO
// ============================================================================
void Engine::runPerformanceBenchmark() {
int num_displays = 0;
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
float monitor_hz = 60.0f;
if (displays && num_displays > 0) {
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
if (dm && dm->refresh_rate > 0) monitor_hz = dm->refresh_rate;
SDL_free(displays);
}
// Ocultar ventana y desactivar V-sync para medición limpia
SDL_HideWindow(window_);
SDL_SetRenderVSync(renderer_, 0);
const int BENCH_DURATION_MS = 600;
const int WARMUP_FRAMES = 5;
SimulationMode original_mode = current_mode_;
auto restore = [&]() {
SDL_SetRenderVSync(renderer_, vsync_enabled_ ? 1 : 0);
SDL_ShowWindow(window_);
current_mode_ = original_mode;
active_shape_.reset();
scene_manager_->changeScenario(0, original_mode);
last_frame_time_ = 0;
};
// Test escenario custom (independiente de max_auto_scenario_)
custom_auto_available_ = false;
if (custom_scenario_enabled_) {
scene_manager_->changeScenario(CUSTOM_SCENARIO_IDX, SimulationMode::SHAPE);
activateShapeInternal(ShapeType::SPHERE);
last_frame_time_ = 0;
for (int w = 0; w < WARMUP_FRAMES; ++w) {
calculateDeltaTime();
SDL_Event e; while (SDL_PollEvent(&e)) {}
update();
render();
}
int frame_count = 0;
Uint64 start = SDL_GetTicks();
while (SDL_GetTicks() - start < static_cast<Uint64>(BENCH_DURATION_MS)) {
calculateDeltaTime();
SDL_Event e; while (SDL_PollEvent(&e)) {}
update();
render();
++frame_count;
}
float fps = static_cast<float>(frame_count) / (BENCH_DURATION_MS / 1000.0f);
custom_auto_available_ = (fps >= monitor_hz);
}
// Probar de más pesado a más ligero
for (int idx = DEMO_AUTO_MAX_SCENARIO; idx >= DEMO_AUTO_MIN_SCENARIO; --idx) {
scene_manager_->changeScenario(idx, SimulationMode::SHAPE);
activateShapeInternal(ShapeType::SPHERE);
// Warmup: estabilizar física y pipeline GPU
last_frame_time_ = 0;
for (int w = 0; w < WARMUP_FRAMES; ++w) {
calculateDeltaTime();
SDL_Event e; while (SDL_PollEvent(&e)) {}
update();
render();
}
// Medición
int frame_count = 0;
Uint64 start = SDL_GetTicks();
while (SDL_GetTicks() - start < static_cast<Uint64>(BENCH_DURATION_MS)) {
calculateDeltaTime();
SDL_Event e;
while (SDL_PollEvent(&e)) { /* descartar */ }
update();
render();
++frame_count;
}
float measured_fps = static_cast<float>(frame_count) / (BENCH_DURATION_MS / 1000.0f);
if (measured_fps >= monitor_hz) {
max_auto_scenario_ = idx;
restore();
return;
}
}
// Fallback: escenario mínimo
max_auto_scenario_ = DEMO_AUTO_MIN_SCENARIO;
restore();
}
// ============================================================================
// CALLBACKS PARA STATEMANAGER - LOGO MODE
// ============================================================================
// Callback para StateManager - Configuración visual al entrar a LOGO MODE
void Engine::executeEnterLogoMode(size_t ball_count) {
// Verificar mínimo de pelotas
if (static_cast<int>(ball_count) < LOGO_MODE_MIN_BALLS) {
// Ajustar a 5000 pelotas automáticamente
scene_manager_->changeScenario(5); // Escenario 5000 pelotas (índice 5 en BALL_COUNT_SCENARIOS)
// Verificar mínimo de pelotas (LOGO_MIN_SCENARIO_IDX = índice 4 → 1000 bolas)
if (scene_manager_->getCurrentScenario() < LOGO_MIN_SCENARIO_IDX) {
scene_manager_->changeScenario(LOGO_MIN_SCENARIO_IDX, current_mode_);
}
// Guardar estado previo (para restaurar al salir)
@@ -1509,6 +1838,15 @@ void Engine::executeExitLogoMode() {
clampShapeScale();
generateShape();
// Activar atracción física si estamos en modo SHAPE
// (crítico para que las bolas se muevan hacia la figura restaurada)
if (current_mode_ == SimulationMode::SHAPE) {
auto& balls = scene_manager_->getBallsMutable();
for (auto& ball : balls) {
ball->enableShapeAttraction(true);
}
}
// Desactivar modo LOGO en PNG_SHAPE (volver a flip intervals normales)
if (active_shape_) {
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
@@ -1641,7 +1979,7 @@ void Engine::activateShapeInternal(ShapeType type) {
active_shape_ = std::make_unique<AtomShape>();
break;
case ShapeType::PNG_SHAPE:
active_shape_ = std::make_unique<PNGShape>("data/shapes/jailgames.png");
active_shape_ = std::make_unique<PNGShape>("shapes/jailgames.png");
break;
default:
active_shape_ = std::make_unique<SphereShape>(); // Fallback

View File

@@ -10,22 +10,23 @@
#include <string> // for string
#include <vector> // for vector
#include "ball.h" // for Ball
#include "boids_mgr/boid_manager.h" // for BoidManager
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
#include "external/texture.h" // for Texture
#include "input/input_handler.h" // for InputHandler
#include "scene/scene_manager.h" // for SceneManager
#include "shapes/shape.h" // for Shape (interfaz polimórfica)
#include "shapes_mgr/shape_manager.h" // for ShapeManager
#include "state/state_manager.h" // for StateManager
#include "theme_manager.h" // for ThemeManager
#include "ui/ui_manager.h" // for UIManager
#include "app_logo.hpp" // for AppLogo
#include "ball.hpp" // for Ball
#include "boids_mgr/boid_manager.hpp" // for BoidManager
#include "defines.hpp" // for GravityDirection, ColorTheme, ShapeType
#include "external/texture.hpp" // for Texture
#include "input/input_handler.hpp" // for InputHandler
#include "scene/scene_manager.hpp" // for SceneManager
#include "shapes/shape.hpp" // for Shape (interfaz polimórfica)
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
#include "state/state_manager.hpp" // for StateManager
#include "theme_manager.hpp" // for ThemeManager
#include "ui/ui_manager.hpp" // for UIManager
class Engine {
public:
// Interfaz pública principal
bool initialize(int width = 0, int height = 0, int zoom = 0, bool fullscreen = false);
bool initialize(int width = 0, int height = 0, int zoom = 0, bool fullscreen = false, AppMode initial_mode = AppMode::SANDBOX);
void run();
void shutdown();
@@ -39,6 +40,7 @@ class Engine {
// Display y depuración
void toggleVSync();
void toggleDebug();
void toggleHelp();
// Figuras 3D
void toggleShapeMode();
@@ -48,7 +50,7 @@ class Engine {
void toggleDepthZoom();
// Boids (comportamiento de enjambre)
void toggleBoidsMode();
void toggleBoidsMode(bool force_gravity_on = true);
// Temas de colores
void cycleTheme(bool forward);
@@ -69,6 +71,23 @@ class Engine {
void toggleRealFullscreen();
void toggleIntegerScaling();
// Modo kiosko
void setKioskMode(bool enabled) { kiosk_mode_ = enabled; }
bool isKioskMode() const { return kiosk_mode_; }
// Escenario custom (tecla 9, --custom-balls)
void setCustomScenario(int balls);
bool isCustomScenarioEnabled() const { return custom_scenario_enabled_; }
bool isCustomAutoAvailable() const { return custom_auto_available_; }
int getCustomScenarioBalls() const { return custom_scenario_balls_; }
// Control manual del benchmark (--skip-benchmark, --max-balls)
void setSkipBenchmark();
void setMaxBallsOverride(int n);
// Notificaciones (público para InputHandler)
void showNotificationForAction(const std::string& text);
// Modos de aplicación (DEMO/LOGO)
void toggleDemoMode();
void toggleDemoLiteMode();
@@ -86,6 +105,21 @@ class Engine {
void executeEnterLogoMode(size_t ball_count);
void executeExitLogoMode();
// === Getters públicos para UIManager (Debug HUD) ===
bool getVSyncEnabled() const { return vsync_enabled_; }
bool getFullscreenEnabled() const { return fullscreen_enabled_; }
bool getRealFullscreenEnabled() const { return real_fullscreen_enabled_; }
ScalingMode getCurrentScalingMode() const { return current_scaling_mode_; }
int getCurrentScreenWidth() const { return current_screen_width_; }
int getCurrentScreenHeight() const { return current_screen_height_; }
std::string getCurrentTextureName() const {
if (texture_names_.empty()) return "";
return texture_names_[current_texture_index_];
}
int getBaseScreenWidth() const { return base_screen_width_; }
int getBaseScreenHeight() const { return base_screen_height_; }
int getMaxAutoScenario() const { return max_auto_scenario_; }
private:
// === Componentes del sistema (Composición) ===
std::unique_ptr<InputHandler> input_handler_; // Manejo de entradas SDL
@@ -94,6 +128,7 @@ class Engine {
std::unique_ptr<BoidManager> boid_manager_; // Gestión de comportamiento boids
std::unique_ptr<StateManager> state_manager_; // Gestión de estados (DEMO/LOGO)
std::unique_ptr<UIManager> ui_manager_; // Gestión de UI (HUD, FPS, notificaciones)
std::unique_ptr<AppLogo> app_logo_; // Gestión de logo periódico en pantalla
// Recursos SDL
SDL_Window* window_ = nullptr;
@@ -118,6 +153,7 @@ class Engine {
bool vsync_enabled_ = true;
bool fullscreen_enabled_ = false;
bool real_fullscreen_enabled_ = false;
bool kiosk_mode_ = false;
ScalingMode current_scaling_mode_ = ScalingMode::INTEGER; // Modo de escalado actual (F5)
// Resolución base (configurada por CLI o default)
@@ -149,9 +185,15 @@ class Engine {
// Sistema de Modo DEMO (auto-play) y LOGO
// NOTA: Engine mantiene estado de implementación para callbacks performLogoAction()
// StateManager coordina los triggers y timers, Engine ejecuta las acciones
AppMode previous_app_mode_ = AppMode::SANDBOX; // Modo previo antes de entrar a LOGO
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
int max_auto_scenario_ = 5; // Índice máximo en modos auto (default conservador: 5000 bolas)
// Escenario custom (--custom-balls)
int custom_scenario_balls_ = 0;
bool custom_scenario_enabled_ = false;
bool custom_auto_available_ = false;
bool skip_benchmark_ = false;
// Sistema de convergencia para LOGO MODE (escala con resolución)
// Usado por performLogoAction() para detectar cuando las bolas forman el logo
@@ -167,9 +209,9 @@ class Engine {
float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8)
int logo_current_flip_count_ = 0; // Flips observados hasta ahora
// Control de entrada manual vs automática a LOGO MODE
// Determina si LOGO debe salir automáticamente o esperar input del usuario
bool logo_entered_manually_ = false; // true si se activó con tecla K, false si automático desde DEMO
// NOTA: logo_entered_manually_ fue eliminado de Engine (duplicado)
// Ahora se obtiene de StateManager con state_manager_->getLogoEnteredManually()
// Esto evita desincronización entre Engine y StateManager
// Estado previo antes de entrar a Logo Mode (para restaurar al salir)
// Guardado por executeEnterLogoMode(), restaurado por executeExitLogoMode()
@@ -181,6 +223,10 @@ class Engine {
std::vector<SDL_Vertex> batch_vertices_;
std::vector<int> batch_indices_;
// Bucket sort per z-ordering (SHAPE mode)
static constexpr int DEPTH_SORT_BUCKETS = 256;
std::array<std::vector<size_t>, DEPTH_SORT_BUCKETS> depth_buckets_;
// Configuración del sistema de texto (constantes configurables)
static constexpr const char* TEXT_FONT_PATH = "data/fonts/determination.ttf";
static constexpr int TEXT_BASE_SIZE = 24; // Tamaño base para 240p
@@ -191,8 +237,10 @@ class Engine {
void update();
void render();
// Benchmark de rendimiento (determina max_auto_scenario_ al inicio)
void runPerformanceBenchmark();
// Métodos auxiliares privados (llamados por la interfaz pública)
void showNotificationForAction(const std::string& text); // Mostrar notificación solo en modo MANUAL
// Sistema de cambio de sprites dinámico - Métodos privados
void switchTextureInternal(bool show_notification); // Implementación interna del cambio de textura

View File

@@ -1,78 +0,0 @@
#pragma once
namespace {
SDL_Texture* dbg_tex = nullptr;
SDL_Renderer* dbg_ren = nullptr;
} // namespace
inline void dbg_init(SDL_Renderer* renderer) {
dbg_ren = renderer;
Uint8 font[448] = {0x42, 0x4D, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x01, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x18, 0xF3, 0x83, 0x83, 0xCF, 0x83, 0x87, 0x00, 0x00, 0xF3, 0x39, 0x39, 0xCF, 0x79, 0xF3, 0x00, 0x00, 0x01, 0xF9, 0x39, 0xCF, 0x61, 0xF9, 0x00, 0x00, 0x33, 0xF9, 0x03, 0xE7, 0x87, 0x81, 0x00, 0x00, 0x93, 0x03, 0x3F, 0xF3, 0x1B, 0x39, 0x00, 0x00, 0xC3, 0x3F, 0x9F, 0x39, 0x3B, 0x39, 0x00, 0x41, 0xE3, 0x03, 0xC3, 0x01, 0x87, 0x83, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE7, 0x01, 0xC7, 0x81, 0x01, 0x83, 0x00, 0x00, 0xE7, 0x1F, 0x9B, 0xE7, 0x1F, 0x39, 0x00, 0x00, 0xE7, 0x8F, 0x39, 0xE7, 0x87, 0xF9, 0x00, 0x00, 0xC3, 0xC7, 0x39, 0xE7, 0xC3, 0xC3, 0x00, 0x00, 0x99, 0xE3, 0x39, 0xE7, 0xF1, 0xE7, 0x00, 0x00, 0x99, 0xF1, 0xB3, 0xC7, 0x39, 0xF3, 0x00, 0x00, 0x99, 0x01, 0xC7, 0xE7, 0x83, 0x81, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x83, 0xE7, 0x83, 0xEF, 0x39, 0x39, 0x00, 0x00, 0x39, 0xE7, 0x39, 0xC7, 0x11, 0x11, 0x00, 0x00, 0xF9, 0xE7, 0x39, 0x83, 0x01, 0x83, 0x00, 0x00, 0x83, 0xE7, 0x39, 0x11, 0x01, 0xC7, 0x00, 0x00, 0x3F, 0xE7, 0x39, 0x39, 0x29, 0x83, 0x00, 0x00, 0x33, 0xE7, 0x39, 0x39, 0x39, 0x11, 0x00, 0x00, 0x87, 0x81, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x39, 0x39, 0x83, 0x3F, 0x85, 0x31, 0x00, 0x00, 0x39, 0x31, 0x39, 0x3F, 0x33, 0x23, 0x00, 0x00, 0x29, 0x21, 0x39, 0x03, 0x21, 0x07, 0x00, 0x00, 0x01, 0x01, 0x39, 0x39, 0x39, 0x31, 0x00, 0x00, 0x01, 0x09, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0x11, 0x19, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0x39, 0x39, 0x83, 0x03, 0x83, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xC1, 0x39, 0x81, 0x83, 0x31, 0x01, 0x00, 0x00, 0x99, 0x39, 0xE7, 0x39, 0x23, 0x3F, 0x00, 0x00, 0x39, 0x39, 0xE7, 0xF9, 0x07, 0x3F, 0x00, 0x00, 0x31, 0x01, 0xE7, 0xF9, 0x0F, 0x3F, 0x00, 0x00, 0x3F, 0x39, 0xE7, 0xF9, 0x27, 0x3F, 0x00, 0x00, 0x9F, 0x39, 0xE7, 0xF9, 0x33, 0x3F, 0x00, 0x00, 0xC1, 0x39, 0x81, 0xF9, 0x39, 0x3F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x39, 0x03, 0xC3, 0x07, 0x01, 0x3F, 0x00, 0x00, 0x39, 0x39, 0x99, 0x33, 0x3F, 0x3F, 0x00, 0x00, 0x01, 0x39, 0x3F, 0x39, 0x3F, 0x3F, 0x00, 0x00, 0x39, 0x03, 0x3F, 0x39, 0x03, 0x03, 0x00, 0x00, 0x39, 0x39, 0x3F, 0x39, 0x3F, 0x3F, 0x00, 0x00, 0x93, 0x39, 0x99, 0x33, 0x3F, 0x3F, 0x00, 0x00, 0xC7, 0x03, 0xC3, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00};
// Cargar surface del bitmap font
SDL_Surface* font_surface = SDL_LoadBMP_IO(SDL_IOFromMem(font, 448), 1);
if (font_surface != nullptr) {
// Crear una nueva surface de 32 bits con canal alpha
SDL_Surface* rgba_surface = SDL_CreateSurface(font_surface->w, font_surface->h, SDL_PIXELFORMAT_RGBA8888);
if (rgba_surface != nullptr) {
// Obtener píxeles de ambas surfaces
Uint8* src_pixels = (Uint8*)font_surface->pixels;
Uint32* dst_pixels = (Uint32*)rgba_surface->pixels;
int width = font_surface->w;
int height = font_surface->h;
// Procesar cada píxel
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int byte_index = y * font_surface->pitch + (x / 8);
int bit_index = 7 - (x % 8);
// Extraer bit del bitmap monocromo
bool is_white = (src_pixels[byte_index] >> bit_index) & 1;
if (is_white) // Fondo blanco original -> transparente
{
dst_pixels[y * width + x] = 0x00000000; // Transparente
} else // Texto negro original -> blanco opaco
{
dst_pixels[y * width + x] = 0xFFFFFFFF; // Blanco opaco
}
}
}
dbg_tex = SDL_CreateTextureFromSurface(dbg_ren, rgba_surface);
SDL_DestroySurface(rgba_surface);
}
SDL_DestroySurface(font_surface);
}
// Configurar filtro nearest neighbor para píxel perfect del texto
if (dbg_tex != nullptr) {
SDL_SetTextureScaleMode(dbg_tex, SDL_SCALEMODE_NEAREST);
// Configurar blend mode para transparencia normal
SDL_SetTextureBlendMode(dbg_tex, SDL_BLENDMODE_BLEND);
}
}
inline void dbg_print(int x, int y, const char* text, Uint8 r, Uint8 g, Uint8 b) {
int cc = 0;
SDL_SetTextureColorMod(dbg_tex, r, g, b);
SDL_FRect src = {0, 0, 8, 8};
SDL_FRect dst = {static_cast<float>(x), static_cast<float>(y), 8, 8};
while (text[cc] != 0) {
if (text[cc] != 32) {
if (text[cc] >= 65) {
src.x = ((text[cc] - 65) % 6) * 8;
src.y = ((text[cc] - 65) / 6) * 8;
} else {
src.x = ((text[cc] - 22) % 6) * 8;
src.y = ((text[cc] - 22) / 6) * 8;
}
SDL_RenderTexture(dbg_ren, dbg_tex, &src, &dst);
}
cc++;
dst.x += 8;
}
}

View File

@@ -1,4 +1,4 @@
#include "mouse.h"
#include "mouse.hpp"
#include <SDL3/SDL.h> // Para SDL_GetTicks, Uint32, SDL_HideCursor, SDL_ShowCursor

View File

@@ -1,6 +1,6 @@
#include "sprite.h"
#include "sprite.hpp"
#include "texture.h" // for Texture
#include "texture.hpp" // for Texture
// Constructor
Sprite::Sprite(std::shared_ptr<Texture> texture)

10630
source/external/stb_image_resize2.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
#define STB_IMAGE_IMPLEMENTATION
#include "texture.h"
#include "texture.hpp"
#include <SDL3/SDL_error.h> // Para SDL_GetError
#include <SDL3/SDL_log.h> // Para SDL_Log
@@ -12,38 +12,7 @@
#include <string> // Para operator<<, string
#include "stb_image.h" // Para stbi_failure_reason, stbi_image_free
#include "../resource_pack.h" // Sistema de empaquetado de recursos
// Instancia global de ResourcePack (se inicializa al primer uso)
static ResourcePack* g_resourcePack = nullptr;
// Inicializar el sistema de recursos (llamar desde main antes de cargar texturas)
void Texture::initResourceSystem(const std::string& packFilePath) {
if (g_resourcePack == nullptr) {
g_resourcePack = new ResourcePack();
if (!g_resourcePack->loadPack(packFilePath)) {
// Si falla, borrar instancia (usará fallback a disco)
delete g_resourcePack;
g_resourcePack = nullptr;
std::cout << "resources.pack no encontrado - usando carpeta data/" << std::endl;
} else {
std::cout << "resources.pack cargado (" << g_resourcePack->getResourceCount() << " recursos)" << std::endl;
}
}
}
// Obtener lista de recursos disponibles en el pack
std::vector<std::string> Texture::getPackResourceList() {
if (g_resourcePack != nullptr) {
return g_resourcePack->getResourceList();
}
return std::vector<std::string>(); // Vacío si no hay pack
}
// Verificar si el pack está cargado
bool Texture::isPackLoaded() {
return g_resourcePack != nullptr;
}
#include "resource_manager.hpp" // Sistema de empaquetado de recursos centralizado
Texture::Texture(SDL_Renderer *renderer)
: renderer_(renderer),
@@ -70,30 +39,29 @@ bool Texture::loadFromFile(const std::string &file_path) {
int width, height, orig_format;
unsigned char *data = nullptr;
// 1. Intentar cargar desde pack (si está inicializado)
if (g_resourcePack != nullptr) {
ResourcePack::ResourceData packData = g_resourcePack->loadResource(file_path);
if (packData.data != nullptr) {
// Descodificar imagen desde memoria usando stb_image
data = stbi_load_from_memory(packData.data, static_cast<int>(packData.size),
&width, &height, &orig_format, req_format);
delete[] packData.data; // Liberar buffer temporal del pack
// 1. Intentar cargar desde ResourceManager (pack o disco)
unsigned char* resourceData = nullptr;
size_t resourceSize = 0;
if (data != nullptr) {
if (ResourceManager::loadResource(file_path, resourceData, resourceSize)) {
// Descodificar imagen desde memoria usando stb_image
data = stbi_load_from_memory(resourceData, static_cast<int>(resourceSize),
&width, &height, &orig_format, req_format);
delete[] resourceData; // Liberar buffer temporal
if (data != nullptr) {
if (ResourceManager::isPackLoaded()) {
std::cout << "Imagen cargada desde pack: " << filename.c_str() << std::endl;
} else {
std::cout << "Imagen cargada desde disco: " << filename.c_str() << std::endl;
}
}
}
// 2. Fallback: cargar desde disco (modo desarrollo)
// 2. Si todo falla, error
if (data == nullptr) {
data = stbi_load(file_path.c_str(), &width, &height, &orig_format, req_format);
if (data == nullptr) {
SDL_Log("Error al cargar la imagen: %s", stbi_failure_reason());
exit(1);
} else {
std::cout << "Imagen cargada desde disco: " << filename.c_str() << std::endl;
}
SDL_Log("Error al cargar la imagen: %s", stbi_failure_reason());
exit(1);
}
int pitch;
@@ -128,6 +96,9 @@ bool Texture::loadFromFile(const std::string &file_path) {
// Configurar filtro nearest neighbor para píxel perfect
SDL_SetTextureScaleMode(new_texture, SDL_SCALEMODE_NEAREST);
// Habilitar alpha blending para transparencias
SDL_SetTextureBlendMode(new_texture, SDL_BLENDMODE_BLEND);
}
// Destruye la superficie cargada
@@ -169,3 +140,17 @@ int Texture::getHeight() {
void Texture::setColor(int r, int g, int b) {
SDL_SetTextureColorMod(texture_, r, g, b);
}
// Modula el alpha de la textura
void Texture::setAlpha(int alpha) {
if (texture_ != nullptr) {
SDL_SetTextureAlphaMod(texture_, static_cast<Uint8>(alpha));
}
}
// Configurar modo de escalado
void Texture::setScaleMode(SDL_ScaleMode mode) {
if (texture_ != nullptr) {
SDL_SetTextureScaleMode(texture_, mode);
}
}

View File

@@ -16,11 +16,6 @@ class Texture {
int height_;
public:
// Sistema de recursos empaquetados (inicializar desde main)
static void initResourceSystem(const std::string& packFilePath);
static std::vector<std::string> getPackResourceList();
static bool isPackLoaded();
// Inicializa las variables
explicit Texture(SDL_Renderer *renderer);
Texture(SDL_Renderer *renderer, const std::string &file_path);
@@ -44,6 +39,12 @@ class Texture {
// Modula el color de la textura
void setColor(int r, int g, int b);
// Modula el alpha (transparencia) de la textura
void setAlpha(int alpha);
// Configurar modo de escalado (NEAREST para pixel art, LINEAR para suavizado)
void setScaleMode(SDL_ScaleMode mode);
// Getter para batch rendering
SDL_Texture *getSDLTexture() const { return texture_; }
};

View File

@@ -1,10 +1,11 @@
#include "input_handler.h"
#include "input_handler.hpp"
#include <SDL3/SDL_keycode.h> // for SDL_Keycode
#include <string> // for std::string, std::to_string
#include "../engine.h" // for Engine
#include "../external/mouse.h" // for Mouse namespace
#include "defines.hpp" // for KIOSK_NOTIFICATION_TEXT
#include "engine.hpp" // for Engine
#include "external/mouse.hpp" // for Mouse namespace
bool InputHandler::processEvents(Engine& engine) {
SDL_Event event;
@@ -21,6 +22,10 @@ bool InputHandler::processEvents(Engine& engine) {
if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat == 0) {
switch (event.key.key) {
case SDLK_ESCAPE:
if (engine.isKioskMode()) {
engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
break;
}
return true; // Solicitar salida
case SDLK_SPACE:
@@ -53,7 +58,7 @@ bool InputHandler::processEvents(Engine& engine) {
break;
case SDLK_H:
engine.toggleDebug();
engine.toggleHelp(); // Toggle ayuda de teclas
break;
// Toggle Física ↔ Última Figura (antes era C)
@@ -99,20 +104,20 @@ bool InputHandler::processEvents(Engine& engine) {
break;
// Toggle Modo Boids (comportamiento de enjambre)
case SDLK_J:
case SDLK_B:
engine.toggleBoidsMode();
break;
// Ciclar temas de color (movido de T a B)
case SDLK_B:
// Ciclar temas de color (movido de B a C)
case SDLK_C:
{
// Detectar si Shift está presionado
SDL_Keymod modstate = SDL_GetModState();
if (modstate & SDL_KMOD_SHIFT) {
// Shift+B: Ciclar hacia atrás (tema anterior)
// Shift+C: Ciclar hacia atrás (tema anterior)
engine.cycleTheme(false);
} else {
// B solo: Ciclar hacia adelante (tema siguiente)
// C solo: Ciclar hacia adelante (tema siguiente)
engine.cycleTheme(true);
}
}
@@ -219,23 +224,34 @@ bool InputHandler::processEvents(Engine& engine) {
engine.changeScenario(7, "50,000 Pelotas");
break;
case SDLK_9:
if (engine.isCustomScenarioEnabled()) {
std::string custom_notif = std::to_string(engine.getCustomScenarioBalls()) + " Pelotas";
engine.changeScenario(CUSTOM_SCENARIO_IDX, custom_notif.c_str());
}
break;
// Controles de zoom dinámico (solo si no estamos en fullscreen)
case SDLK_F1:
engine.handleZoomOut();
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
else engine.handleZoomOut();
break;
case SDLK_F2:
engine.handleZoomIn();
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
else engine.handleZoomIn();
break;
// Control de pantalla completa
case SDLK_F3:
engine.toggleFullscreen();
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
else engine.toggleFullscreen();
break;
// Modo real fullscreen (cambia resolución interna)
case SDLK_F4:
engine.toggleRealFullscreen();
if (engine.isKioskMode()) engine.showNotificationForAction(KIOSK_NOTIFICATION_TEXT);
else engine.toggleRealFullscreen();
break;
// Toggle escalado entero/estirado (solo en fullscreen F3)
@@ -263,6 +279,11 @@ bool InputHandler::processEvents(Engine& engine) {
case SDLK_K:
engine.toggleLogoMode();
break;
// Toggle Debug Display (movido de H a F12)
case SDLK_F12:
engine.toggleDebug();
break;
}
}
}

157
source/logo_scaler.cpp Normal file
View File

@@ -0,0 +1,157 @@
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "logo_scaler.hpp"
#include <SDL3/SDL_error.h> // Para SDL_GetError
#include <SDL3/SDL_log.h> // Para SDL_Log
#include <SDL3/SDL_pixels.h> // Para SDL_PixelFormat
#include <SDL3/SDL_render.h> // Para SDL_CreateTexture
#include <SDL3/SDL_surface.h> // Para SDL_CreateSurfaceFrom
#include <SDL3/SDL_video.h> // Para SDL_GetDisplays
#include <cstdlib> // Para free()
#include <iostream> // Para std::cout
#include "external/stb_image.h" // Para stbi_load, stbi_image_free
#include "external/stb_image_resize2.h" // Para stbir_resize_uint8_srgb
#include "resource_manager.hpp" // Para cargar desde pack
// ============================================================================
// Detectar resolución nativa del monitor principal
// ============================================================================
bool LogoScaler::detectNativeResolution(int& native_width, int& native_height) {
int num_displays = 0;
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
if (displays == nullptr || num_displays == 0) {
SDL_Log("Error al obtener displays: %s", SDL_GetError());
return false;
}
// Obtener resolución del display principal (displays[0])
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
if (dm == nullptr) {
SDL_Log("Error al obtener modo del display: %s", SDL_GetError());
SDL_free(displays);
return false;
}
native_width = dm->w;
native_height = dm->h;
SDL_free(displays);
std::cout << "Resolución nativa detectada: " << native_width << "x" << native_height << std::endl;
return true;
}
// ============================================================================
// Cargar PNG y escalar al tamaño especificado
// ============================================================================
unsigned char* LogoScaler::loadAndScale(const std::string& path,
int target_width, int target_height,
int& out_width, int& out_height) {
// 1. Intentar cargar imagen desde ResourceManager (pack o disco)
int orig_width, orig_height, orig_channels;
unsigned char* orig_data = nullptr;
// 1a. Cargar desde ResourceManager
unsigned char* resourceData = nullptr;
size_t resourceSize = 0;
if (ResourceManager::loadResource(path, resourceData, resourceSize)) {
// Descodificar imagen desde memoria usando stb_image
orig_data = stbi_load_from_memory(resourceData, static_cast<int>(resourceSize),
&orig_width, &orig_height, &orig_channels, STBI_rgb_alpha);
delete[] resourceData; // Liberar buffer temporal
}
// 1b. Si falla todo, error
if (orig_data == nullptr) {
SDL_Log("Error al cargar imagen %s: %s", path.c_str(), stbi_failure_reason());
return nullptr;
}
std::cout << "Imagen cargada: " << path << " (" << orig_width << "x" << orig_height << ")" << std::endl;
// 2. Calcular tamaño final manteniendo aspect ratio
// El alto está fijado por target_height (APPLOGO_HEIGHT_PERCENT)
// Calcular ancho proporcional al aspect ratio original
float aspect_ratio = static_cast<float>(orig_width) / static_cast<float>(orig_height);
out_width = static_cast<int>(target_height * aspect_ratio);
out_height = target_height;
std::cout << " Escalando a: " << out_width << "x" << out_height << std::endl;
// 3. Alocar buffer para imagen escalada (RGBA = 4 bytes por píxel)
unsigned char* scaled_data = static_cast<unsigned char*>(malloc(out_width * out_height * 4));
if (scaled_data == nullptr) {
SDL_Log("Error al alocar memoria para imagen escalada");
stbi_image_free(orig_data);
return nullptr;
}
// 4. Escalar con stb_image_resize2 (algoritmo Mitchell, espacio sRGB)
// La función devuelve el puntero de salida, o nullptr si falla
unsigned char* result = stbir_resize_uint8_srgb(
orig_data, orig_width, orig_height, 0, // Input
scaled_data, out_width, out_height, 0, // Output
STBIR_RGBA // Formato píxel
);
// Liberar imagen original (ya no la necesitamos)
stbi_image_free(orig_data);
if (result == nullptr) {
SDL_Log("Error al escalar imagen");
free(scaled_data);
return nullptr;
}
std::cout << " Escalado completado correctamente" << std::endl;
return scaled_data;
}
// ============================================================================
// Crear textura SDL desde buffer RGBA
// ============================================================================
SDL_Texture* LogoScaler::createTextureFromBuffer(SDL_Renderer* renderer,
unsigned char* data,
int width, int height) {
if (renderer == nullptr || data == nullptr || width <= 0 || height <= 0) {
SDL_Log("Parámetros inválidos para createTextureFromBuffer");
return nullptr;
}
// 1. Crear surface SDL desde buffer RGBA
int pitch = width * 4; // 4 bytes por píxel (RGBA)
SDL_PixelFormat pixel_format = SDL_PIXELFORMAT_RGBA32;
SDL_Surface* surface = SDL_CreateSurfaceFrom(
width, height,
pixel_format,
data,
pitch
);
if (surface == nullptr) {
SDL_Log("Error al crear surface: %s", SDL_GetError());
return nullptr;
}
// 2. Crear textura desde surface
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (texture == nullptr) {
SDL_Log("Error al crear textura: %s", SDL_GetError());
SDL_DestroySurface(surface);
return nullptr;
}
// 3. Liberar surface (la textura ya tiene los datos)
SDL_DestroySurface(surface);
return texture;
}

61
source/logo_scaler.hpp Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include <SDL3/SDL_render.h> // Para SDL_Renderer, SDL_Texture
#include <SDL3/SDL_video.h> // Para SDL_DisplayID, SDL_GetDisplays
#include <string> // Para std::string
/**
* @brief Helper class para pre-escalar logos usando stb_image_resize2
*
* Proporciona funciones para:
* - Detectar resolución nativa del monitor
* - Cargar PNG y escalar a tamaño específico con algoritmos de alta calidad
* - Crear texturas SDL desde buffers escalados
*
* Usado por AppLogo para pre-generar versiones de logos al tamaño exacto
* de pantalla, eliminando el escalado dinámico de SDL y mejorando calidad visual.
*/
class LogoScaler {
public:
/**
* @brief Detecta la resolución nativa del monitor principal
*
* @param native_width [out] Ancho nativo del display en píxeles
* @param native_height [out] Alto nativo del display en píxeles
* @return true si se pudo detectar, false si hubo error
*/
static bool detectNativeResolution(int& native_width, int& native_height);
/**
* @brief Carga un PNG y lo escala al tamaño especificado
*
* Usa stb_image para cargar y stb_image_resize2 para escalar con
* algoritmo Mitchell (balance calidad/velocidad) en espacio sRGB.
*
* @param path Ruta al archivo PNG (ej: "data/logo/logo.png")
* @param target_width Ancho destino en píxeles
* @param target_height Alto destino en píxeles
* @param out_width [out] Ancho real de la imagen escalada
* @param out_height [out] Alto real de la imagen escalada
* @return Buffer RGBA (4 bytes por píxel) o nullptr si falla
* IMPORTANTE: El caller debe liberar con free() cuando termine
*/
static unsigned char* loadAndScale(const std::string& path,
int target_width, int target_height,
int& out_width, int& out_height);
/**
* @brief Crea una textura SDL desde un buffer RGBA
*
* @param renderer Renderizador SDL activo
* @param data Buffer RGBA (4 bytes por píxel)
* @param width Ancho del buffer en píxeles
* @param height Alto del buffer en píxeles
* @return Textura SDL creada o nullptr si falla
* IMPORTANTE: El caller debe destruir con SDL_DestroyTexture()
*/
static SDL_Texture* createTextureFromBuffer(SDL_Renderer* renderer,
unsigned char* data,
int width, int height);
};

View File

@@ -1,6 +1,9 @@
#include <iostream>
#include <cstring>
#include "engine.h"
#include <string>
#include "engine.hpp"
#include "defines.hpp"
#include "resource_manager.hpp"
// getExecutableDirectory() ya está definido en defines.h como inline
@@ -11,13 +14,24 @@ void printHelp() {
std::cout << " -w, --width <px> Ancho de resolución (default: 320)\n";
std::cout << " -h, --height <px> Alto de resolución (default: 240)\n";
std::cout << " -z, --zoom <n> Zoom de ventana (default: 3)\n";
std::cout << " -f, --fullscreen Modo pantalla completa\n";
std::cout << " -f, --fullscreen Modo pantalla completa (F3 - letterbox)\n";
std::cout << " -F, --real-fullscreen Modo pantalla completa real (F4 - nativo)\n";
std::cout << " -k, --kiosk Modo kiosko (F4 fijo, sin ESC, sin zoom)\n";
std::cout << " -m, --mode <mode> Modo inicial: sandbox, demo, demo-lite, logo (default: sandbox)\n";
std::cout << " --custom-balls <n> Activa escenario custom (tecla 9) con N pelotas\n";
std::cout << " --skip-benchmark Salta el benchmark y usa el máximo de bolas (50000)\n";
std::cout << " --max-balls <n> Limita el máximo de bolas en modos DEMO/DEMO_LITE\n";
std::cout << " --help Mostrar esta ayuda\n\n";
std::cout << "Ejemplos:\n";
std::cout << " vibe3_physics # 320x240 zoom 3 (ventana 960x720)\n";
std::cout << " vibe3_physics -w 1920 -h 1080 # 1920x1080 zoom 1 (auto)\n";
std::cout << " vibe3_physics -w 640 -h 480 -z 2 # 640x480 zoom 2 (ventana 1280x960)\n";
std::cout << " vibe3_physics -w 1920 -h 1080 -f # 1920x1080 fullscreen\n\n";
std::cout << " vibe3_physics -f # Fullscreen letterbox (F3)\n";
std::cout << " vibe3_physics -F # Fullscreen real (F4 - resolución nativa)\n";
std::cout << " vibe3_physics -k # Modo kiosko (pantalla completa real, bloqueado)\n";
std::cout << " vibe3_physics --mode demo # Arrancar en modo DEMO (auto-play)\n";
std::cout << " vibe3_physics -m demo-lite # Arrancar en modo DEMO_LITE (solo física)\n";
std::cout << " vibe3_physics -F --mode logo # Fullscreen + modo LOGO (easter egg)\n\n";
std::cout << "Nota: Si resolución > pantalla, se usa default. Zoom se ajusta automáticamente.\n";
}
@@ -25,7 +39,13 @@ int main(int argc, char* argv[]) {
int width = 0;
int height = 0;
int zoom = 0;
int custom_balls = 0;
bool fullscreen = false;
bool real_fullscreen = false;
bool kiosk_mode = false;
bool skip_benchmark = false;
int max_balls_override = 0;
AppMode initial_mode = AppMode::SANDBOX; // Modo inicial (default: SANDBOX)
// Parsear argumentos
for (int i = 1; i < argc; i++) {
@@ -35,8 +55,8 @@ int main(int argc, char* argv[]) {
} else if (strcmp(argv[i], "-w") == 0 || strcmp(argv[i], "--width") == 0) {
if (i + 1 < argc) {
width = atoi(argv[++i]);
if (width < 640) {
std::cerr << "Error: Ancho mínimo es 640px\n";
if (width < 320) {
std::cerr << "Error: Ancho mínimo es 320\n";
return -1;
}
} else {
@@ -46,8 +66,8 @@ int main(int argc, char* argv[]) {
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--height") == 0) {
if (i + 1 < argc) {
height = atoi(argv[++i]);
if (height < 480) {
std::cerr << "Error: Alto mínimo es 480px\n";
if (height < 240) {
std::cerr << "Error: Alto mínimo es 240\n";
return -1;
}
} else {
@@ -67,6 +87,55 @@ int main(int argc, char* argv[]) {
}
} else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--fullscreen") == 0) {
fullscreen = true;
} else if (strcmp(argv[i], "-F") == 0 || strcmp(argv[i], "--real-fullscreen") == 0) {
real_fullscreen = true;
} else if (strcmp(argv[i], "-k") == 0 || strcmp(argv[i], "--kiosk") == 0) {
kiosk_mode = true;
} else if (strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--mode") == 0) {
if (i + 1 < argc) {
std::string mode_str = argv[++i];
if (mode_str == "sandbox") {
initial_mode = AppMode::SANDBOX;
} else if (mode_str == "demo") {
initial_mode = AppMode::DEMO;
} else if (mode_str == "demo-lite") {
initial_mode = AppMode::DEMO_LITE;
} else if (mode_str == "logo") {
initial_mode = AppMode::LOGO;
} else {
std::cerr << "Error: Modo '" << mode_str << "' no válido. Usa: sandbox, demo, demo-lite, logo\n";
return -1;
}
} else {
std::cerr << "Error: -m/--mode requiere un valor\n";
return -1;
}
} else if (strcmp(argv[i], "--custom-balls") == 0) {
if (i + 1 < argc) {
int n = atoi(argv[++i]);
if (n < 1) {
std::cerr << "Error: --custom-balls requiere un valor >= 1\n";
return -1;
}
custom_balls = n;
} else {
std::cerr << "Error: --custom-balls requiere un valor\n";
return -1;
}
} else if (strcmp(argv[i], "--skip-benchmark") == 0) {
skip_benchmark = true;
} else if (strcmp(argv[i], "--max-balls") == 0) {
if (i + 1 < argc) {
int n = atoi(argv[++i]);
if (n < 1) {
std::cerr << "Error: --max-balls requiere un valor >= 1\n";
return -1;
}
max_balls_override = n;
} else {
std::cerr << "Error: --max-balls requiere un valor\n";
return -1;
}
} else {
std::cerr << "Error: Opción desconocida '" << argv[i] << "'\n";
printHelp();
@@ -77,15 +146,31 @@ int main(int argc, char* argv[]) {
// Inicializar sistema de recursos empaquetados (intentar cargar resources.pack)
std::string resources_dir = getResourcesDirectory();
std::string pack_path = resources_dir + "/resources.pack";
Texture::initResourceSystem(pack_path);
ResourceManager::init(pack_path);
Engine engine;
if (!engine.initialize(width, height, zoom, fullscreen)) {
if (custom_balls > 0)
engine.setCustomScenario(custom_balls); // pre-init: asigna campos antes del benchmark
if (max_balls_override > 0)
engine.setMaxBallsOverride(max_balls_override);
else if (skip_benchmark)
engine.setSkipBenchmark();
if (!engine.initialize(width, height, zoom, fullscreen, initial_mode)) {
std::cout << "¡Error al inicializar el engine!" << std::endl;
return -1;
}
// Si se especificó real fullscreen (F4) o modo kiosko, activar después de inicializar
if (real_fullscreen || kiosk_mode) {
engine.toggleRealFullscreen();
}
if (kiosk_mode) {
engine.setKioskMode(true);
}
engine.run();
engine.shutdown();

View File

@@ -0,0 +1,99 @@
#include "resource_manager.hpp"
#include "resource_pack.hpp"
#include <iostream>
#include <fstream>
#include <cstring>
// Inicializar estáticos
ResourcePack* ResourceManager::resourcePack_ = nullptr;
std::map<std::string, std::vector<unsigned char>> ResourceManager::cache_;
bool ResourceManager::init(const std::string& packFilePath) {
// Si ya estaba inicializado, liberar primero
if (resourcePack_ != nullptr) {
delete resourcePack_;
resourcePack_ = nullptr;
}
// Intentar cargar el pack
resourcePack_ = new ResourcePack();
if (!resourcePack_->loadPack(packFilePath)) {
// Si falla, borrar instancia (usará fallback a disco)
delete resourcePack_;
resourcePack_ = nullptr;
std::cout << "resources.pack no encontrado - usando carpeta data/" << std::endl;
return false;
}
std::cout << "resources.pack cargado (" << resourcePack_->getResourceCount() << " recursos)" << std::endl;
return true;
}
void ResourceManager::shutdown() {
cache_.clear();
if (resourcePack_ != nullptr) {
delete resourcePack_;
resourcePack_ = nullptr;
}
}
bool ResourceManager::loadResource(const std::string& resourcePath, unsigned char*& data, size_t& size) {
data = nullptr;
size = 0;
// 1. Consultar caché en RAM (sin I/O)
auto it = cache_.find(resourcePath);
if (it != cache_.end()) {
size = it->second.size();
data = new unsigned char[size];
std::memcpy(data, it->second.data(), size);
return true;
}
// 2. Intentar cargar desde pack (si está disponible)
if (resourcePack_ != nullptr) {
ResourcePack::ResourceData packData = resourcePack_->loadResource(resourcePath);
if (packData.data != nullptr) {
cache_[resourcePath] = std::vector<unsigned char>(packData.data, packData.data + packData.size);
data = packData.data;
size = packData.size;
return true;
}
}
// 3. Fallback: cargar desde disco
std::ifstream file(resourcePath, std::ios::binary | std::ios::ate);
if (!file) {
std::string dataPath = "data/" + resourcePath;
file.open(dataPath, std::ios::binary | std::ios::ate);
if (!file) { return false; }
}
size = static_cast<size_t>(file.tellg());
file.seekg(0, std::ios::beg);
data = new unsigned char[size];
file.read(reinterpret_cast<char*>(data), size);
file.close();
// Guardar en caché
cache_[resourcePath] = std::vector<unsigned char>(data, data + size);
return true;
}
bool ResourceManager::isPackLoaded() {
return resourcePack_ != nullptr;
}
std::vector<std::string> ResourceManager::getResourceList() {
if (resourcePack_ != nullptr) {
return resourcePack_->getResourceList();
}
return std::vector<std::string>(); // Vacío si no hay pack
}
size_t ResourceManager::getResourceCount() {
if (resourcePack_ != nullptr) {
return resourcePack_->getResourceCount();
}
return 0;
}

View File

@@ -0,0 +1,86 @@
#pragma once
#include <map>
#include <string>
#include <vector>
class ResourcePack;
/**
* ResourceManager - Gestor centralizado de recursos empaquetados
*
* Singleton que administra el sistema de recursos empaquetados (resources.pack)
* y proporciona fallback automático a disco cuando el pack no está disponible.
*
* Uso:
* // En main.cpp, antes de inicializar cualquier sistema:
* ResourceManager::init("resources.pack");
*
* // Desde cualquier clase que necesite recursos:
* unsigned char* data = nullptr;
* size_t size = 0;
* if (ResourceManager::loadResource("textures/ball.png", data, size)) {
* // Usar datos...
* delete[] data; // Liberar cuando termine
* }
*/
class ResourceManager {
public:
/**
* Inicializa el sistema de recursos empaquetados
* Debe llamarse una única vez al inicio del programa
*
* @param packFilePath Ruta al archivo .pack (ej: "resources.pack")
* @return true si el pack se cargó correctamente, false si no existe (fallback a disco)
*/
static bool init(const std::string& packFilePath);
/**
* Libera el sistema de recursos
* Opcional - se llama automáticamente al cerrar el programa
*/
static void shutdown();
/**
* Carga un recurso desde el pack (o disco si no existe pack)
*
* @param resourcePath Ruta relativa del recurso (ej: "textures/ball.png")
* @param data [out] Puntero donde se almacenará el buffer (debe liberar con delete[])
* @param size [out] Tamaño del buffer en bytes
* @return true si se cargó correctamente, false si falla
*/
static bool loadResource(const std::string& resourcePath, unsigned char*& data, size_t& size);
/**
* Verifica si el pack está cargado
* @return true si hay un pack cargado, false si se usa disco
*/
static bool isPackLoaded();
/**
* Obtiene la lista de recursos disponibles en el pack
* @return Vector con las rutas de todos los recursos, vacío si no hay pack
*/
static std::vector<std::string> getResourceList();
/**
* Obtiene el número de recursos en el pack
* @return Número de recursos, 0 si no hay pack
*/
static size_t getResourceCount();
private:
// Constructor privado (singleton)
ResourceManager() = default;
~ResourceManager() = default;
// Deshabilitar copia y asignación
ResourceManager(const ResourceManager&) = delete;
ResourceManager& operator=(const ResourceManager&) = delete;
// Instancia del pack (nullptr si no está cargado)
static ResourcePack* resourcePack_;
// Caché en RAM para evitar I/O repetido en el bucle principal
static std::map<std::string, std::vector<unsigned char>> cache_;
};

View File

@@ -1,4 +1,4 @@
#include "resource_pack.h"
#include "resource_pack.hpp"
#include <algorithm>
#include <cstring>

View File

@@ -1,5 +1,4 @@
#ifndef RESOURCE_PACK_H
#define RESOURCE_PACK_H
#pragma once
#include <cstdint>
#include <fstream>
@@ -64,5 +63,3 @@ private:
uint32_t calculateChecksum(const unsigned char* data, size_t size);
std::string normalizePath(const std::string& path);
};
#endif // RESOURCE_PACK_H

View File

@@ -1,10 +1,10 @@
#include "scene_manager.h"
#include "scene_manager.hpp"
#include <cstdlib> // for rand
#include "../defines.h" // for BALL_COUNT_SCENARIOS, GRAVITY_MASS_MIN, etc
#include "../external/texture.h" // for Texture
#include "../theme_manager.h" // for ThemeManager
#include "defines.hpp" // for BALL_COUNT_SCENARIOS, GRAVITY_MASS_MIN, etc
#include "external/texture.hpp" // for Texture
#include "theme_manager.hpp" // for ThemeManager
SceneManager::SceneManager(int screen_width, int screen_height)
: current_gravity_(GravityDirection::DOWN)
@@ -22,8 +22,8 @@ void SceneManager::initialize(int scenario, std::shared_ptr<Texture> texture, Th
theme_manager_ = theme_manager;
current_ball_size_ = texture_->getWidth();
// Crear bolas iniciales
changeScenario(scenario_);
// Crear bolas iniciales (siempre en modo PHYSICS al inicio)
changeScenario(scenario_, SimulationMode::PHYSICS);
}
void SceneManager::update(float delta_time) {
@@ -33,7 +33,7 @@ void SceneManager::update(float delta_time) {
}
}
void SceneManager::changeScenario(int scenario_id) {
void SceneManager::changeScenario(int scenario_id, SimulationMode mode) {
// Guardar escenario
scenario_ = scenario_id;
@@ -44,15 +44,57 @@ void SceneManager::changeScenario(int scenario_id) {
changeGravityDirection(GravityDirection::DOWN);
// Crear las bolas según el escenario
for (int i = 0; i < BALL_COUNT_SCENARIOS[scenario_id]; ++i) {
const int SIGN = ((rand() % 2) * 2) - 1; // Genera un signo aleatorio (+ o -)
int ball_count = (scenario_id == CUSTOM_SCENARIO_IDX)
? custom_ball_count_
: BALL_COUNT_SCENARIOS[scenario_id];
for (int i = 0; i < ball_count; ++i) {
float X, Y, VX, VY;
// Calcular spawn zone: margen a cada lado, zona central para spawn
const int margin = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
const int spawn_zone_width = screen_width_ - (2 * margin);
const float X = (rand() % spawn_zone_width) + margin; // Posición inicial en X
const float VX = (((rand() % 20) + 10) * 0.1f) * SIGN; // Velocidad en X
const float VY = ((rand() % 60) - 30) * 0.1f; // Velocidad en Y
// Inicialización según SimulationMode (RULES.md líneas 23-26)
switch (mode) {
case SimulationMode::PHYSICS: {
// PHYSICS: Parte superior, 75% distribución central en X
const int SIGN = ((rand() % 2) * 2) - 1;
const int margin = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
const int spawn_zone_width = screen_width_ - (2 * margin);
X = (rand() % spawn_zone_width) + margin;
Y = 0.0f; // Parte superior
VX = (((rand() % 20) + 10) * 0.1f) * SIGN;
VY = ((rand() % 60) - 30) * 0.1f;
break;
}
case SimulationMode::SHAPE: {
// SHAPE: Centro de pantalla, sin velocidad inicial
X = screen_width_ / 2.0f;
Y = screen_height_ / 2.0f; // Centro vertical
VX = 0.0f;
VY = 0.0f;
break;
}
case SimulationMode::BOIDS: {
// BOIDS: Posiciones aleatorias, velocidades aleatorias
const int SIGN_X = ((rand() % 2) * 2) - 1;
const int SIGN_Y = ((rand() % 2) * 2) - 1;
X = static_cast<float>(rand() % screen_width_);
Y = static_cast<float>(rand() % screen_height_); // Posición Y aleatoria
VX = (((rand() % 40) + 10) * 0.1f) * SIGN_X; // 1.0 - 5.0 px/frame
VY = (((rand() % 40) + 10) * 0.1f) * SIGN_Y;
break;
}
default:
// Fallback a PHYSICS por seguridad
const int SIGN = ((rand() % 2) * 2) - 1;
const int margin = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
const int spawn_zone_width = screen_width_ - (2 * margin);
X = (rand() % spawn_zone_width) + margin;
Y = 0.0f; // Parte superior
VX = (((rand() % 20) + 10) * 0.1f) * SIGN;
VY = ((rand() % 60) - 30) * 0.1f;
break;
}
// Seleccionar color de la paleta del tema actual (delegado a ThemeManager)
int random_index = rand();
@@ -62,7 +104,7 @@ void SceneManager::changeScenario(int scenario_id) {
float mass_factor = GRAVITY_MASS_MIN + (rand() % 1000) / 1000.0f * (GRAVITY_MASS_MAX - GRAVITY_MASS_MIN);
balls_.emplace_back(std::make_unique<Ball>(
X, VX, VY, COLOR, texture_,
X, Y, VX, VY, COLOR, texture_,
screen_width_, screen_height_, current_ball_size_,
current_gravity_, mass_factor
));

View File

@@ -3,8 +3,8 @@
#include <memory> // for unique_ptr, shared_ptr
#include <vector> // for vector
#include "../ball.h" // for Ball
#include "../defines.h" // for GravityDirection
#include "ball.hpp" // for Ball
#include "defines.hpp" // for GravityDirection
// Forward declarations
class Texture;
@@ -50,9 +50,16 @@ class SceneManager {
/**
* @brief Cambia el número de bolas según escenario
* @param scenario_id Índice del escenario (0-7 para 10 a 50,000 bolas)
* @param scenario_id Índice del escenario (0-7 para 10 a 50,000 bolas; 8 = custom)
* @param mode Modo de simulación actual (afecta inicialización)
*/
void changeScenario(int scenario_id);
void changeScenario(int scenario_id, SimulationMode mode);
/**
* @brief Configura el número de bolas para el escenario custom (índice 8)
* @param n Número de bolas del escenario custom
*/
void setCustomBallCount(int n) { custom_ball_count_ = n; }
/**
* @brief Actualiza textura y tamaño de todas las bolas
@@ -145,6 +152,7 @@ class SceneManager {
std::vector<std::unique_ptr<Ball>> balls_;
GravityDirection current_gravity_;
int scenario_;
int custom_ball_count_ = 0; // Número de bolas para escenario custom (índice 8)
// === Configuración de pantalla ===
int screen_width_;

View File

@@ -1,5 +1,5 @@
#include "atom_shape.h"
#include "../defines.h"
#include "atom_shape.hpp"
#include "defines.hpp"
#include <cmath>
void AtomShape::generatePoints(int num_points, float screen_width, float screen_height) {

View File

@@ -1,6 +1,6 @@
#pragma once
#include "shape.h"
#include "shape.hpp"
// Figura: Átomo con núcleo central y órbitas electrónicas
// Comportamiento: Núcleo estático + electrones orbitando en planos inclinados

View File

@@ -1,5 +1,5 @@
#include "cube_shape.h"
#include "../defines.h"
#include "cube_shape.hpp"
#include "defines.hpp"
#include <cmath>
void CubeShape::generatePoints(int num_points, float screen_width, float screen_height) {

View File

@@ -1,6 +1,6 @@
#pragma once
#include "shape.h"
#include "shape.hpp"
#include <vector>
// Figura: Cubo 3D rotante

View File

@@ -1,5 +1,5 @@
#include "cylinder_shape.h"
#include "../defines.h"
#include "cylinder_shape.hpp"
#include "defines.hpp"
#include <cmath>
#include <cstdlib> // Para rand()

View File

@@ -1,6 +1,6 @@
#pragma once
#include "shape.h"
#include "shape.hpp"
// Figura: Cilindro 3D rotante
// Comportamiento: Superficie cilíndrica con rotación en eje Y + tumbling ocasional en X/Z

View File

@@ -1,5 +1,5 @@
#include "helix_shape.h"
#include "../defines.h"
#include "helix_shape.hpp"
#include "defines.hpp"
#include <cmath>
void HelixShape::generatePoints(int num_points, float screen_width, float screen_height) {

View File

@@ -1,6 +1,6 @@
#pragma once
#include "shape.h"
#include "shape.hpp"
// Figura: Espiral helicoidal 3D con distribución uniforme
// Comportamiento: Rotación en eje Y + animación de fase vertical

View File

@@ -1,5 +1,5 @@
#include "icosahedron_shape.h"
#include "../defines.h"
#include "icosahedron_shape.hpp"
#include "defines.hpp"
#include <cmath>
#include <vector>

View File

@@ -1,6 +1,6 @@
#pragma once
#include "shape.h"
#include "shape.hpp"
// Figura: Icosaedro 3D (D20, poliedro regular de 20 caras)
// Comportamiento: 12 vértices distribuidos uniformemente con rotación triple

View File

@@ -1,5 +1,5 @@
#include "lissajous_shape.h"
#include "../defines.h"
#include "lissajous_shape.hpp"
#include "defines.hpp"
#include <cmath>
void LissajousShape::generatePoints(int num_points, float screen_width, float screen_height) {

View File

@@ -1,6 +1,6 @@
#pragma once
#include "shape.h"
#include "shape.hpp"
// Figura: Curva de Lissajous 3D
// Comportamiento: Curva paramétrica 3D con rotación global y animación de fase

View File

@@ -1,6 +1,7 @@
#include "png_shape.h"
#include "../defines.h"
#include "../external/stb_image.h"
#include "png_shape.hpp"
#include "defines.hpp"
#include "external/stb_image.h"
#include "resource_manager.hpp"
#include <cmath>
#include <algorithm>
#include <iostream>
@@ -9,6 +10,7 @@
PNGShape::PNGShape(const char* png_path) {
// Cargar PNG desde path
if (!loadPNG(png_path)) {
std::cerr << "[PNGShape] Usando fallback 10x10" << std::endl;
// Fallback: generar un cuadrado simple si falla la carga
image_width_ = 10;
image_height_ = 10;
@@ -19,24 +21,29 @@ PNGShape::PNGShape(const char* png_path) {
next_idle_time_ = PNG_IDLE_TIME_MIN + (rand() % 1000) / 1000.0f * (PNG_IDLE_TIME_MAX - PNG_IDLE_TIME_MIN);
}
bool PNGShape::loadPNG(const char* path) {
int width, height, channels;
unsigned char* data = stbi_load(path, &width, &height, &channels, 1); // Forzar 1 canal (grayscale)
if (!data) {
bool PNGShape::loadPNG(const char* resource_key) {
std::cout << "[PNGShape] Cargando recurso: " << resource_key << std::endl;
unsigned char* file_data = nullptr;
size_t file_size = 0;
if (!ResourceManager::loadResource(resource_key, file_data, file_size)) {
std::cerr << "[PNGShape] ERROR: recurso no encontrado: " << resource_key << std::endl;
return false;
}
int width, height, channels;
unsigned char* pixels = stbi_load_from_memory(file_data, static_cast<int>(file_size),
&width, &height, &channels, 1);
delete[] file_data;
if (!pixels) {
std::cerr << "[PNGShape] ERROR al decodificar PNG: " << stbi_failure_reason() << std::endl;
return false;
}
image_width_ = width;
image_height_ = height;
pixel_data_.resize(width * height);
// Convertir a mapa booleano (true = píxel blanco/visible, false = negro/transparente)
for (int i = 0; i < width * height; i++) {
pixel_data_[i] = (data[i] > 128); // Umbral: >128 = blanco
pixel_data_[i] = (pixels[i] > 128);
}
stbi_image_free(data);
stbi_image_free(pixels);
return true;
}

Some files were not shown because too many files have changed in this diff Show More