29 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
80 changed files with 1313 additions and 563 deletions

21
.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/
@@ -93,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

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")

135
Makefile
View File

@@ -42,8 +42,8 @@ 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
@@ -67,36 +67,44 @@ APP_SOURCES := $(filter-out source/main_old.cpp, $(APP_SOURCES))
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 -lSDL3_ttf
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..."
@@ -113,6 +121,13 @@ resources.pack: $(PACK_TOOL) $(DATA_FILES)
$(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,7 +146,7 @@ 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'
@@ -144,7 +159,7 @@ windows_release: resources.pack
# Copia los ficheros que estan en la raíz del proyecto
@copy /Y "LICENSE" "$(RELEASE_FOLDER)\" >nul 2>&1 || echo LICENSE not found (optional)
@copy /Y "README.md" "$(RELEASE_FOLDER)\" >nul
@copy /Y release\*.dll "$(RELEASE_FOLDER)\" >nul 2>&1 || echo DLLs copied successfully
@copy /Y release\dll\*.dll "$(RELEASE_FOLDER)\" >nul 2>&1 || echo DLLs copied successfully
# Compila
@windres release/vibe3.rc -O coff -o $(RESOURCE_FILE)
@@ -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"
@@ -193,8 +217,8 @@ macos_release: resources.pack
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
@@ -203,11 +227,28 @@ ifdef ENABLE_MACOS_X86_64
# 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
@@ -216,11 +257,28 @@ endif
# 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
@@ -235,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)"
@@ -260,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)"
@@ -364,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)"
@@ -389,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
@@ -428,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"

View File

@@ -1,6 +1,8 @@
# ViBe3 Physics - Simulador de Sprites con Física Avanzada
**ViBe3 Physics** es una demo experimental de **vibe-coding** que implementa **físicas avanzadas** con sistema de delta time independiente del framerate. Utiliza **SDL3** para renderizado optimizado con batch geometry de hasta 100,000 sprites simultáneos.
**ViBe3 Physics** es una demo experimental de **vibe-coding** que implementa **físicas avanzadas** con sistema de delta time independiente del framerate. Utiliza **SDL3** para renderizado optimizado con batch geometry de hasta 50,000 sprites simultáneos.
![ViBe3 Physics - Gameplay](https://php.sustancia.synology.me/images/vibe3_physics/captura_vibe3_physics_1.png)
El nombre refleja su propósito: **ViBe** (vibe-coding experimental) + **Physics** (nuevas físicas experimentales). La demo sirve como sandbox para probar bucles de juego con timing independiente, comportamientos emergentes (boids), figuras 3D procedurales y efectos demoscene.
@@ -154,6 +156,8 @@ Controlan el comportamiento físico de las pelotas:
ViBe3 Physics incluye **15 temas visuales** organizados en **2 páginas** (9 estáticos + 1 dinámico en Página 1, 5 dinámicos en Página 2). Los temas dinámicos tienen animación de colores en tiempo real.
![ViBe3 Physics - Gameplay](https://php.sustancia.synology.me/images/vibe3_physics/captura_vibe3_physics_2.png)
### Controles de Temas
- **`B`**: Ciclar entre TODOS los temas (adelante)
@@ -407,8 +411,8 @@ vibe3_physics [opciones]
### Validaciones
- **Ancho mínimo**: 640px
- **Alto mínimo**: 480px
- **Ancho mínimo**: 320px
- **Alto mínimo**: 240px
- **Zoom automático**: Si resolución > pantalla, usa default
- **Zoom máximo**: Ajustado según pantalla disponible

Binary file not shown.

BIN
release/vibe3.res Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,12 +1,12 @@
#include "app_logo.h"
#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.h" // for LogoScaler
#include "defines.h" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory
#include "logo_scaler.hpp" // for LogoScaler
#include "defines.hpp" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory
// ============================================================================
// Destructor - Liberar las 4 texturas SDL

View File

@@ -4,7 +4,7 @@
#include <memory> // for unique_ptr, shared_ptr
#include "defines.h" // for AppMode
#include "defines.hpp" // for AppMode
class Texture;
class Sprite;

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

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 {

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)

View File

@@ -2,8 +2,8 @@
#include <cstddef> // for size_t
#include "../defines.h" // for SimulationMode, AppMode
#include "../spatial_grid.h" // for SpatialGrid
#include "defines.hpp" // for SimulationMode, AppMode
#include "spatial_grid.hpp" // for SpatialGrid
// Forward declarations
class Engine;

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,7 +22,6 @@ 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)
@@ -33,6 +32,7 @@ constexpr Uint64 NOTIFICATION_FADE_TIME = 200; // Duración animación salida
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)

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,22 +17,24 @@
#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
@@ -72,7 +74,16 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
// 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, AppMod
}
}
} 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, AppMod
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, AppMod
// 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>();
@@ -278,6 +295,19 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
// 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;
@@ -536,6 +566,30 @@ 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) {
// Pasar el modo actual al SceneManager para inicialización correcta
@@ -664,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)
@@ -1278,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) {
@@ -1288,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());
@@ -1402,12 +1453,16 @@ 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];
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
@@ -1502,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, current_mode_);
// 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) {
@@ -1525,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
@@ -1561,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, current_mode_); // 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)
@@ -1776,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,18 +10,18 @@
#include <string> // for string
#include <vector> // for vector
#include "app_logo.h" // for AppLogo
#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:
@@ -71,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();
@@ -95,8 +112,13 @@ class Engine {
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) ===
@@ -131,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)
@@ -164,6 +187,13 @@ class Engine {
// StateManager coordina los triggers y timers, Engine ejecuta las acciones
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
@@ -193,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
@@ -203,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,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)

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;

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);

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:
@@ -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)

View File

@@ -1,5 +1,5 @@
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "logo_scaler.h"
#include "logo_scaler.hpp"
#include <SDL3/SDL_error.h> // Para SDL_GetError
#include <SDL3/SDL_log.h> // Para SDL_Log
@@ -13,6 +13,7 @@
#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
@@ -51,10 +52,22 @@ bool LogoScaler::detectNativeResolution(int& native_width, int& native_height) {
unsigned char* LogoScaler::loadAndScale(const std::string& path,
int target_width, int target_height,
int& out_width, int& out_height) {
// 1. Cargar imagen original con stb_image
// 1. Intentar cargar imagen desde ResourceManager (pack o disco)
int orig_width, orig_height, orig_channels;
unsigned char* orig_data = stbi_load(path.c_str(), &orig_width, &orig_height, &orig_channels, STBI_rgb_alpha);
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;

View File

@@ -1,8 +1,9 @@
#include <iostream>
#include <cstring>
#include <string>
#include "engine.h"
#include "defines.h"
#include "engine.hpp"
#include "defines.hpp"
#include "resource_manager.hpp"
// getExecutableDirectory() ya está definido en defines.h como inline
@@ -15,7 +16,11 @@ void printHelp() {
std::cout << " -z, --zoom <n> Zoom de ventana (default: 3)\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";
@@ -23,6 +28,7 @@ void printHelp() {
std::cout << " vibe3_physics -w 640 -h 480 -z 2 # 640x480 zoom 2 (ventana 1280x960)\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";
@@ -33,8 +39,12 @@ 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
@@ -45,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 {
@@ -56,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 {
@@ -79,6 +89,8 @@ int main(int argc, char* argv[]) {
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];
@@ -98,6 +110,32 @@ int main(int argc, char* argv[]) {
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();
@@ -108,19 +146,30 @@ 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 (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), activar después de inicializar
if (real_fullscreen) {
// 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)
@@ -44,7 +44,10 @@ void SceneManager::changeScenario(int scenario_id, SimulationMode mode) {
changeGravityDirection(GravityDirection::DOWN);
// Crear las bolas según el escenario
for (int i = 0; i < BALL_COUNT_SCENARIOS[scenario_id]; ++i) {
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;
// Inicialización según SimulationMode (RULES.md líneas 23-26)

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,11 +50,17 @@ 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, 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
* @param new_texture Nueva textura compartida
@@ -146,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;
}

View File

@@ -1,7 +1,7 @@
#pragma once
#include "shape.h"
#include "../defines.h" // Para PNG_IDLE_TIME_MIN/MAX constantes
#include "shape.hpp"
#include "defines.hpp" // Para PNG_IDLE_TIME_MIN/MAX constantes
#include <vector>
#include <cstdlib> // Para rand()

View File

@@ -1,5 +1,5 @@
#include "sphere_shape.h"
#include "../defines.h"
#include "sphere_shape.hpp"
#include "defines.hpp"
#include <cmath>
void SphereShape::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: Esfera 3D con distribución uniforme (Fibonacci Sphere Algorithm)
// Comportamiento: Rotación dual en ejes X e Y

View File

@@ -1,5 +1,5 @@
#include "torus_shape.h"
#include "../defines.h"
#include "torus_shape.hpp"
#include "defines.hpp"
#include <cmath>
void TorusShape::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: Torus/Toroide 3D (donut/rosquilla)
// Comportamiento: Superficie toroidal con rotación triple (X, Y, Z)

View File

@@ -1,25 +1,25 @@
#include "shape_manager.h"
#include "shape_manager.hpp"
#include <algorithm> // for std::min, std::max
#include <cstdlib> // for rand
#include <string> // for std::string
#include "../ball.h" // for Ball
#include "../defines.h" // for constantes
#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 "defines.hpp" // for constantes
#include "scene/scene_manager.hpp" // for SceneManager
#include "state/state_manager.hpp" // for StateManager
#include "ui/ui_manager.hpp" // for UIManager
// Includes de todas las shapes (necesario para creación polimórfica)
#include "../shapes/atom_shape.h"
#include "../shapes/cube_shape.h"
#include "../shapes/cylinder_shape.h"
#include "../shapes/helix_shape.h"
#include "../shapes/icosahedron_shape.h"
#include "../shapes/lissajous_shape.h"
#include "../shapes/png_shape.h"
#include "../shapes/sphere_shape.h"
#include "../shapes/torus_shape.h"
#include "shapes/atom_shape.hpp"
#include "shapes/cube_shape.hpp"
#include "shapes/cylinder_shape.hpp"
#include "shapes/helix_shape.hpp"
#include "shapes/icosahedron_shape.hpp"
#include "shapes/lissajous_shape.hpp"
#include "shapes/png_shape.hpp"
#include "shapes/sphere_shape.hpp"
#include "shapes/torus_shape.hpp"
ShapeManager::ShapeManager()
: engine_(nullptr)
@@ -269,7 +269,7 @@ void ShapeManager::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

@@ -2,8 +2,8 @@
#include <memory> // for unique_ptr
#include "../defines.h" // for SimulationMode, ShapeType
#include "../shapes/shape.h" // for Shape base class
#include "defines.hpp" // for SimulationMode, ShapeType
#include "shapes/shape.hpp" // for Shape base class
// Forward declarations
class Engine;

View File

@@ -1,9 +1,9 @@
#include "spatial_grid.h"
#include "spatial_grid.hpp"
#include <algorithm> // for std::max, std::min
#include <cmath> // for std::floor, std::ceil
#include "ball.h" // for Ball
#include "ball.hpp" // for Ball
SpatialGrid::SpatialGrid(int world_width, int world_height, float cell_size)
: world_width_(world_width)

View File

@@ -1,5 +1,4 @@
#ifndef SPATIAL_GRID_H
#define SPATIAL_GRID_H
#pragma once
#include <unordered_map>
#include <vector>
@@ -70,5 +69,3 @@ private:
// Usamos unordered_map para O(1) lookup
std::unordered_map<int, std::vector<Ball*>> cells_;
};
#endif // SPATIAL_GRID_H

View File

@@ -1,10 +1,10 @@
#include "state_manager.h"
#include "state_manager.hpp"
#include <cstdlib> // for rand
#include "../defines.h" // for constantes DEMO/LOGO
#include "../engine.h" // for Engine (callbacks)
#include "../shapes/png_shape.h" // for PNGShape flip detection
#include "defines.hpp" // for constantes DEMO/LOGO
#include "engine.hpp" // for Engine (callbacks)
#include "shapes/png_shape.hpp" // for PNGShape flip detection
StateManager::StateManager()
: engine_(nullptr)

View File

@@ -3,7 +3,7 @@
#include <SDL3/SDL_stdinc.h> // for Uint64
#include <cstddef> // for size_t
#include "../defines.h" // for AppMode, ShapeType, GravityDirection
#include "defines.hpp" // for AppMode, ShapeType, GravityDirection
// Forward declarations
class Engine;

View File

@@ -1,8 +1,9 @@
#include "textrenderer.h"
#include "textrenderer.hpp"
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "resource_manager.hpp"
TextRenderer::TextRenderer() : renderer_(nullptr), font_(nullptr), font_size_(0), use_antialiasing_(true) {
TextRenderer::TextRenderer() : renderer_(nullptr), font_(nullptr), font_size_(0), use_antialiasing_(true), font_data_buffer_(nullptr) {
}
TextRenderer::~TextRenderer() {
@@ -23,7 +24,34 @@ bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_
}
}
// Cargar la fuente
// Intentar cargar la fuente desde ResourceManager (pack o disco)
unsigned char* fontData = nullptr;
size_t fontDataSize = 0;
if (ResourceManager::loadResource(font_path, fontData, fontDataSize)) {
// Crear SDL_IOStream desde memoria
SDL_IOStream* fontIO = SDL_IOFromConstMem(fontData, static_cast<size_t>(fontDataSize));
if (fontIO != nullptr) {
// Cargar fuente desde IOStream
font_ = TTF_OpenFontIO(fontIO, true, font_size); // true = cerrar stream automáticamente
if (font_ == nullptr) {
SDL_Log("Error al cargar fuente desde memoria '%s': %s", font_path, SDL_GetError());
delete[] fontData; // Liberar solo si falla la carga
return false;
}
// CRÍTICO: NO eliminar fontData aquí - SDL_ttf necesita estos datos en memoria
// mientras la fuente esté abierta. Se liberará en cleanup()
font_data_buffer_ = fontData;
SDL_Log("Fuente cargada desde ResourceManager: %s (%lu bytes)", font_path, (unsigned long)fontDataSize);
return true;
} else {
delete[] fontData;
}
}
// Fallback final: intentar cargar directamente desde disco (por si falla ResourceManager)
font_ = TTF_OpenFont(font_path, font_size);
if (font_ == nullptr) {
SDL_Log("Error al cargar fuente '%s': %s", font_path, SDL_GetError());
@@ -45,13 +73,43 @@ bool TextRenderer::reinitialize(int new_font_size) {
return true;
}
// Cerrar fuente actual
// Cerrar fuente actual y liberar buffer previo
if (font_ != nullptr) {
TTF_CloseFont(font_);
font_ = nullptr;
}
if (font_data_buffer_ != nullptr) {
delete[] font_data_buffer_;
font_data_buffer_ = nullptr;
}
// Cargar fuente con nuevo tamaño
// Intentar cargar la fuente desde ResourceManager con el nuevo tamaño
unsigned char* fontData = nullptr;
size_t fontDataSize = 0;
if (ResourceManager::loadResource(font_path_, fontData, fontDataSize)) {
SDL_IOStream* fontIO = SDL_IOFromConstMem(fontData, static_cast<size_t>(fontDataSize));
if (fontIO != nullptr) {
font_ = TTF_OpenFontIO(fontIO, true, new_font_size);
if (font_ == nullptr) {
SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s",
font_path_.c_str(), new_font_size, SDL_GetError());
delete[] fontData; // Liberar solo si falla
return false;
}
// Mantener buffer en memoria (NO eliminar)
font_data_buffer_ = fontData;
font_size_ = new_font_size;
SDL_Log("Fuente recargada desde ResourceManager: %s (tamaño %d)", font_path_.c_str(), new_font_size);
return true;
} else {
delete[] fontData;
}
}
// Fallback: cargar directamente desde disco
font_ = TTF_OpenFont(font_path_.c_str(), new_font_size);
if (font_ == nullptr) {
SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s",
@@ -59,9 +117,7 @@ bool TextRenderer::reinitialize(int new_font_size) {
return false;
}
// Actualizar tamaño almacenado
font_size_ = new_font_size;
return true;
}
@@ -70,6 +126,10 @@ void TextRenderer::cleanup() {
TTF_CloseFont(font_);
font_ = nullptr;
}
if (font_data_buffer_ != nullptr) {
delete[] font_data_buffer_;
font_data_buffer_ = nullptr;
}
renderer_ = nullptr;
}
@@ -253,6 +313,17 @@ void TextRenderer::printAbsolute(int physical_x, int physical_y, const std::stri
printAbsolute(physical_x, physical_y, text.c_str(), color);
}
void TextRenderer::printAbsoluteShadowed(int physical_x, int physical_y, const char* text) {
// Sombra: negro semitransparente desplazado 1px
printAbsolute(physical_x + 1, physical_y + 1, text, {0, 0, 0, 180});
// Texto: blanco opaco
printAbsolute(physical_x, physical_y, text, {255, 255, 255, 255});
}
void TextRenderer::printAbsoluteShadowed(int physical_x, int physical_y, const std::string& text) {
printAbsoluteShadowed(physical_x, physical_y, text.c_str());
}
int TextRenderer::getTextWidth(const char* text) {
if (!isInitialized() || text == nullptr) {
return 0;

View File

@@ -31,6 +31,10 @@ public:
void printAbsolute(int physical_x, int physical_y, const char* text, SDL_Color color);
void printAbsolute(int physical_x, int physical_y, const std::string& text, SDL_Color color);
// Renderiza texto con sombra negra (+1px offset) para máxima legibilidad sobre cualquier fondo
void printAbsoluteShadowed(int physical_x, int physical_y, const char* text);
void printAbsoluteShadowed(int physical_x, int physical_y, const std::string& text);
// Obtiene el ancho de un texto renderizado (en píxeles lógicos para compatibilidad)
int getTextWidth(const char* text);
@@ -50,4 +54,5 @@ private:
int font_size_;
bool use_antialiasing_;
std::string font_path_; // Almacenar ruta para reinitialize()
unsigned char* font_data_buffer_; // Buffer de datos de fuente (mantener en memoria mientras esté abierta)
};

View File

@@ -1,7 +1,7 @@
#include "theme_manager.h"
#include "theme_manager.hpp"
#include "themes/static_theme.h"
#include "themes/dynamic_theme.h"
#include "themes/static_theme.hpp"
#include "themes/dynamic_theme.hpp"
// ============================================================================
// INICIALIZACIÓN

View File

@@ -3,10 +3,10 @@
#include <memory> // for unique_ptr
#include <vector> // for vector
#include "ball.h" // for Ball class
#include "defines.h" // for Color, ColorTheme
#include "themes/theme.h" // for Theme interface
#include "themes/theme_snapshot.h" // for ThemeSnapshot
#include "ball.hpp" // for Ball class
#include "defines.hpp" // for Color, ColorTheme
#include "themes/theme.hpp" // for Theme interface
#include "themes/theme_snapshot.hpp" // for ThemeSnapshot
/**
* ThemeManager: Gestiona el sistema de temas visuales (unificado, estáticos y dinámicos)

View File

@@ -1,4 +1,4 @@
#include "dynamic_theme.h"
#include "dynamic_theme.hpp"
#include <algorithm> // for std::min
DynamicTheme::DynamicTheme(const char* name_en, const char* name_es,

View File

@@ -1,6 +1,6 @@
#pragma once
#include "theme.h"
#include "theme.hpp"
#include <string>
// Forward declaration (estructura definida en defines.h)

View File

@@ -1,4 +1,4 @@
#include "static_theme.h"
#include "static_theme.hpp"
StaticTheme::StaticTheme(const char* name_en, const char* name_es,
int text_r, int text_g, int text_b,

View File

@@ -1,6 +1,6 @@
#pragma once
#include "theme.h"
#include "theme.hpp"
#include <string>
/**

View File

@@ -1,7 +1,7 @@
#pragma once
#include <vector>
#include "../defines.h" // for Color, ThemeKeyframe
#include "defines.hpp" // for Color, ThemeKeyframe
/**
* Theme: Interfaz polimórfica para todos los temas (estáticos y dinámicos)

View File

@@ -2,7 +2,7 @@
#include <string>
#include <vector>
#include "../defines.h" // for Color
#include "defines.hpp" // for Color
/**
* ThemeSnapshot: "Fotografía" del estado de un tema en un momento dado

View File

@@ -1,9 +1,9 @@
#include "help_overlay.h"
#include "help_overlay.hpp"
#include <algorithm> // for std::min
#include "../text/textrenderer.h"
#include "../theme_manager.h"
#include "text/textrenderer.hpp"
#include "theme_manager.hpp"
HelpOverlay::HelpOverlay()
: renderer_(nullptr),
@@ -28,7 +28,7 @@ HelpOverlay::HelpOverlay()
// COLUMNA 1: SIMULACIÓN
{"SIMULACIÓN", ""},
{"1-8", "Escenarios (10 a 50,000 pelotas)"},
{"F", "Toggle Física Última Figura"},
{"F", "Toggle Física - Última Figura"},
{"B", "Modo Boids (enjambre)"},
{"ESPACIO", "Impulso contra gravedad"},
{"G", "Toggle Gravedad ON/OFF"},
@@ -151,7 +151,8 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
}
int line_height = text_renderer_->getTextHeight();
int padding = 25;
// Padding dinámico basado en altura física: 25px para >= 600px, escalado proporcionalmente para menores
int padding = (physical_height_ >= 600) ? 25 : std::max(10, physical_height_ / 24);
// Calcular ancho máximo por columna
int max_col1_width = 0;
@@ -234,11 +235,11 @@ void HelpOverlay::calculateBoxDimensions() {
int text_width, text_height;
calculateTextDimensions(text_width, text_height);
// Usar directamente el ancho y altura calculados según el contenido
box_width_ = text_width;
// Aplicar límites máximos: 95% ancho, 90% altura
int max_width = static_cast<int>(physical_width_ * 0.95f);
int max_height = static_cast<int>(physical_height_ * 0.90f);
// Altura: 90% de altura física o altura calculada, el que sea menor
int max_height = static_cast<int>(physical_height_ * 0.9f);
box_width_ = std::min(text_width, max_width);
box_height_ = std::min(text_height, max_height);
// Centrar en pantalla
@@ -333,7 +334,8 @@ void HelpOverlay::rebuildCachedTexture() {
// Configuración de espaciado
int line_height = text_renderer_->getTextHeight();
int padding = 25;
// Padding dinámico basado en altura física: 25px para >= 600px, escalado proporcionalmente para menores
int padding = (physical_height_ >= 600) ? 25 : std::max(10, physical_height_ / 24);
int current_x = padding; // Coordenadas relativas a la textura (0,0)
int current_y = padding;

View File

@@ -1,8 +1,8 @@
#include "notifier.h"
#include "../text/textrenderer.h"
#include "../theme_manager.h"
#include "../defines.h"
#include "../utils/easing_functions.h"
#include "notifier.hpp"
#include "text/textrenderer.hpp"
#include "theme_manager.hpp"
#include "defines.hpp"
#include "utils/easing_functions.hpp"
#include <SDL3/SDL.h>
// ============================================================================

View File

@@ -1,17 +1,18 @@
#include "ui_manager.h"
#include "ui_manager.hpp"
#include <SDL3/SDL.h>
#include <algorithm>
#include <string>
#include "../ball.h" // for Ball
#include "../defines.h" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode
#include "../engine.h" // for Engine (info de sistema)
#include "../scene/scene_manager.h" // for SceneManager
#include "../shapes/shape.h" // for Shape
#include "../text/textrenderer.h" // for TextRenderer
#include "../theme_manager.h" // for ThemeManager
#include "notifier.h" // for Notifier
#include "help_overlay.h" // for HelpOverlay
#include "ball.hpp" // for Ball
#include "defines.hpp" // for TEXT_DURATION, NOTIFICATION_DURATION, AppMode, SimulationMode
#include "engine.hpp" // for Engine (info de sistema)
#include "scene/scene_manager.hpp" // for SceneManager
#include "shapes/shape.hpp" // for Shape
#include "text/textrenderer.hpp" // for TextRenderer
#include "theme_manager.hpp" // for ThemeManager
#include "notifier.hpp" // for Notifier
#include "help_overlay.hpp" // for HelpOverlay
// ============================================================================
// HELPER: Obtener viewport en coordenadas físicas (no lógicas)
@@ -39,16 +40,11 @@ static SDL_Rect getPhysicalViewport(SDL_Renderer* renderer) {
}
UIManager::UIManager()
: text_renderer_(nullptr)
, text_renderer_debug_(nullptr)
: text_renderer_debug_(nullptr)
, text_renderer_notifier_(nullptr)
, notifier_(nullptr)
, help_overlay_(nullptr)
, show_debug_(false)
, show_text_(true)
, text_()
, text_pos_(0)
, text_init_time_(0)
, fps_last_time_(0)
, fps_frame_count_(0)
, fps_current_(0)
@@ -58,12 +54,13 @@ UIManager::UIManager()
, theme_manager_(nullptr)
, physical_window_width_(0)
, physical_window_height_(0)
, logical_window_width_(0)
, logical_window_height_(0)
, current_font_size_(18) { // Tamaño por defecto (medium)
}
UIManager::~UIManager() {
// Limpieza: Los objetos creados con new deben ser eliminados
delete text_renderer_;
delete text_renderer_debug_;
delete text_renderer_notifier_;
delete notifier_;
@@ -71,23 +68,24 @@ UIManager::~UIManager() {
}
void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
int physical_width, int physical_height) {
int physical_width, int physical_height,
int logical_width, int logical_height) {
renderer_ = renderer;
theme_manager_ = theme_manager;
physical_window_width_ = physical_width;
physical_window_height_ = physical_height;
logical_window_width_ = logical_width;
logical_window_height_ = logical_height;
// Calcular tamaño de fuente apropiado según dimensiones físicas
current_font_size_ = calculateFontSize(physical_width, physical_height);
// Calcular tamaño de fuente apropiado según dimensiones LÓGICAS (sin zoom)
current_font_size_ = calculateFontSize(logical_height);
// Crear renderers de texto
text_renderer_ = new TextRenderer();
text_renderer_debug_ = new TextRenderer();
text_renderer_notifier_ = new TextRenderer();
// Inicializar renderers con tamaño dinámico
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
text_renderer_debug_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", std::max(9, current_font_size_ - 2), true);
text_renderer_notifier_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", current_font_size_, true);
// Crear y configurar sistema de notificaciones
@@ -112,12 +110,7 @@ void UIManager::update(Uint64 current_time, float delta_time) {
fps_current_ = fps_frame_count_;
fps_frame_count_ = 0;
fps_last_time_ = current_time;
fps_text_ = "fps: " + std::to_string(fps_current_);
}
// Actualizar texto obsoleto (DEPRECATED)
if (show_text_) {
show_text_ = !(SDL_GetTicks() - text_init_time_ > TEXT_DURATION);
fps_text_ = "FPS: " + std::to_string(fps_current_);
}
// Actualizar sistema de notificaciones
@@ -138,11 +131,6 @@ void UIManager::render(SDL_Renderer* renderer,
physical_window_width_ = physical_width;
physical_window_height_ = physical_height;
// Renderizar texto obsoleto centrado (DEPRECATED - mantener temporalmente)
if (show_text_) {
renderObsoleteText(current_screen_width);
}
// Renderizar debug HUD si está activo
if (show_debug_) {
renderDebugHUD(engine, scene_manager, current_mode, current_app_mode,
@@ -183,19 +171,17 @@ void UIManager::updatePhysicalWindowSize(int width, int height) {
physical_window_width_ = width;
physical_window_height_ = height;
// Calcular nuevo tamaño de fuente apropiado
int new_font_size = calculateFontSize(width, height);
// Calcular nuevo tamaño de fuente apropiado basado en altura LÓGICA
// (las dimensiones lógicas no cambian con zoom, solo con cambios explícitos de resolución)
int new_font_size = calculateFontSize(logical_window_height_);
// Si el tamaño cambió, reinicializar todos los text renderers
if (new_font_size != current_font_size_) {
current_font_size_ = new_font_size;
// Reinicializar text renderers con nuevo tamaño
if (text_renderer_) {
text_renderer_->reinitialize(current_font_size_);
}
if (text_renderer_debug_) {
text_renderer_debug_->reinitialize(current_font_size_);
text_renderer_debug_->reinitialize(std::max(9, current_font_size_ - 2));
}
if (text_renderer_notifier_) {
text_renderer_notifier_->reinitialize(current_font_size_);
@@ -211,13 +197,6 @@ void UIManager::updatePhysicalWindowSize(int width, int height) {
notifier_->updateWindowSize(width, height);
}
void UIManager::setTextObsolete(const std::string& text, int pos, int current_screen_width) {
text_ = text;
text_pos_ = pos;
text_init_time_ = SDL_GetTicks();
show_text_ = true;
}
// === Métodos privados ===
void UIManager::renderDebugHUD(const Engine* engine,
@@ -226,42 +205,23 @@ void UIManager::renderDebugHUD(const Engine* engine,
AppMode current_app_mode,
const Shape* active_shape,
float shape_convergence) {
// Obtener altura de línea para espaciado dinámico
int line_height = text_renderer_debug_->getTextHeight();
int margin = 8; // Margen constante en píxeles físicos
// Obtener viewport FÍSICO (píxeles reales, no lógicos)
// CRÍTICO: En F3, SDL_GetRenderViewport() devuelve coordenadas LÓGICAS,
// pero printAbsolute() trabaja en píxeles FÍSICOS. Usar helper para obtener
// viewport en coordenadas físicas.
int margin = 8;
SDL_Rect physical_viewport = getPhysicalViewport(renderer_);
// ===========================
// COLUMNA LEFT (Sistema)
// ===========================
int left_y = margin;
// --- Construir strings ---
// AppMode (antes estaba centrado, ahora va a la izquierda)
std::string appmode_text;
SDL_Color appmode_color = {255, 255, 255, 255}; // Blanco por defecto
if (current_app_mode == AppMode::LOGO) {
appmode_text = "AppMode: LOGO";
appmode_color = {255, 128, 0, 255}; // Naranja
} else if (current_app_mode == AppMode::DEMO) {
appmode_text = "AppMode: DEMO";
appmode_color = {255, 165, 0, 255}; // Naranja
} else if (current_app_mode == AppMode::DEMO_LITE) {
appmode_text = "AppMode: DEMO LITE";
appmode_color = {255, 200, 0, 255}; // Amarillo-naranja
} else {
appmode_text = "AppMode: SANDBOX";
appmode_color = {0, 255, 128, 255}; // Verde claro
}
text_renderer_debug_->printAbsolute(margin, left_y, appmode_text.c_str(), appmode_color);
left_y += line_height;
// SimulationMode
std::string simmode_text;
if (current_mode == SimulationMode::PHYSICS) {
simmode_text = "SimMode: PHYSICS";
@@ -274,35 +234,45 @@ void UIManager::renderDebugHUD(const Engine* engine,
} else if (current_mode == SimulationMode::BOIDS) {
simmode_text = "SimMode: BOIDS";
}
text_renderer_debug_->printAbsolute(margin, left_y, simmode_text.c_str(), {0, 255, 255, 255}); // Cian
left_y += line_height;
// Número de pelotas (escenario actual)
std::string sprite_name = engine->getCurrentTextureName();
std::transform(sprite_name.begin(), sprite_name.end(), sprite_name.begin(), ::toupper);
std::string sprite_text = "Sprite: " + sprite_name;
size_t ball_count = scene_manager->getBallCount();
std::string balls_text;
if (ball_count >= 1000) {
// Formatear con separador de miles (ejemplo: 5,000 o 50,000)
std::string count_str = std::to_string(ball_count);
std::string formatted;
int digits = count_str.length();
int digits = static_cast<int>(count_str.length());
for (int i = 0; i < digits; i++) {
if (i > 0 && (digits - i) % 3 == 0) {
formatted += ',';
}
if (i > 0 && (digits - i) % 3 == 0) formatted += ',';
formatted += count_str[i];
}
balls_text = "Balls: " + formatted;
} else {
balls_text = "Balls: " + std::to_string(ball_count);
}
text_renderer_debug_->printAbsolute(margin, left_y, balls_text.c_str(), {128, 255, 128, 255}); // Verde claro
left_y += line_height;
// V-Sync
text_renderer_debug_->printAbsolute(margin, left_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian
left_y += line_height;
int max_auto_idx = engine->getMaxAutoScenario();
int max_auto_balls = BALL_COUNT_SCENARIOS[max_auto_idx];
if (engine->isCustomAutoAvailable() && engine->getCustomScenarioBalls() > max_auto_balls) {
max_auto_balls = engine->getCustomScenarioBalls();
}
std::string max_auto_text;
if (max_auto_balls >= 1000) {
std::string count_str = std::to_string(max_auto_balls);
std::string formatted;
int digits = static_cast<int>(count_str.length());
for (int i = 0; i < digits; i++) {
if (i > 0 && (digits - i) % 3 == 0) formatted += ',';
formatted += count_str[i];
}
max_auto_text = "Auto max: " + formatted;
} else {
max_auto_text = "Auto max: " + std::to_string(max_auto_balls);
}
// Modo de escalado (INTEGER/LETTERBOX/STRETCH o WINDOWED si no está en fullscreen)
std::string scaling_text;
if (engine->getFullscreenEnabled() || engine->getRealFullscreenEnabled()) {
ScalingMode scaling = engine->getCurrentScalingMode();
@@ -316,20 +286,10 @@ void UIManager::renderDebugHUD(const Engine* engine,
} else {
scaling_text = "Scaling: WINDOWED";
}
text_renderer_debug_->printAbsolute(margin, left_y, scaling_text.c_str(), {255, 255, 0, 255}); // Amarillo
left_y += line_height;
// Resolución física (píxeles reales de la ventana)
std::string phys_res_text = "Physical: " + std::to_string(physical_window_width_) + "x" + std::to_string(physical_window_height_);
text_renderer_debug_->printAbsolute(margin, left_y, phys_res_text.c_str(), {255, 128, 255, 255}); // Magenta claro
left_y += line_height;
// Resolución lógica (resolución interna del renderizador)
std::string logic_res_text = "Logical: " + std::to_string(engine->getCurrentScreenWidth()) + "x" + std::to_string(engine->getCurrentScreenHeight());
text_renderer_debug_->printAbsolute(margin, left_y, logic_res_text.c_str(), {255, 128, 255, 255}); // Magenta claro
left_y += line_height;
// Display refresh rate (obtener de SDL)
std::string refresh_text;
int num_displays = 0;
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
@@ -344,105 +304,63 @@ void UIManager::renderDebugHUD(const Engine* engine,
} else {
refresh_text = "Refresh: N/A";
}
text_renderer_debug_->printAbsolute(margin, left_y, refresh_text.c_str(), {255, 255, 128, 255}); // Amarillo claro
left_y += line_height;
// Tema actual (delegado a ThemeManager)
std::string theme_text = std::string("Theme: ") + theme_manager_->getCurrentThemeNameEN();
text_renderer_debug_->printAbsolute(margin, left_y, theme_text.c_str(), {128, 255, 255, 255}); // Cian claro
left_y += line_height;
// ===========================
// COLUMNA RIGHT (Primera pelota)
// ===========================
int right_y = margin;
Uint64 ticks_ms = SDL_GetTicks();
Uint64 total_secs = ticks_ms / 1000;
int hh = static_cast<int>(total_secs / 3600);
int mm = static_cast<int>((total_secs % 3600) / 60);
int ss = static_cast<int>(total_secs % 60);
char elapsed_buf[32];
SDL_snprintf(elapsed_buf, sizeof(elapsed_buf), "Elapsed: %02d:%02d:%02d", hh, mm, ss);
std::string elapsed_text(elapsed_buf);
// FPS counter (esquina superior derecha)
int fps_text_width = text_renderer_debug_->getTextWidthPhysical(fps_text_.c_str());
int fps_x = physical_viewport.w - fps_text_width - margin;
text_renderer_debug_->printAbsolute(fps_x, right_y, fps_text_.c_str(), {255, 255, 0, 255}); // Amarillo
right_y += line_height;
// --- Construir vector de líneas en orden ---
std::vector<std::string> lines;
lines.push_back(fps_text_);
lines.push_back(appmode_text);
lines.push_back(simmode_text);
lines.push_back(sprite_text);
lines.push_back(balls_text);
lines.push_back(max_auto_text);
lines.push_back(vsync_text_);
lines.push_back(scaling_text);
lines.push_back(phys_res_text);
lines.push_back(logic_res_text);
lines.push_back(refresh_text);
lines.push_back(theme_text);
lines.push_back(elapsed_text);
// Info de la primera pelota (si existe)
const Ball* first_ball = scene_manager->getFirstBall();
if (first_ball != nullptr) {
// Posición X, Y
lines.push_back("VelX: " + std::to_string(static_cast<int>(first_ball->getVelocityX())));
lines.push_back("VelY: " + std::to_string(static_cast<int>(first_ball->getVelocityY())));
SDL_FRect pos = first_ball->getPosition();
std::string pos_text = "Pos: (" + std::to_string(static_cast<int>(pos.x)) + ", " + std::to_string(static_cast<int>(pos.y)) + ")";
int pos_width = text_renderer_debug_->getTextWidthPhysical(pos_text.c_str());
text_renderer_debug_->printAbsolute(physical_viewport.w - pos_width - margin, right_y, pos_text.c_str(), {255, 128, 128, 255}); // Rojo claro
right_y += line_height;
// Velocidad X
int vx_int = static_cast<int>(first_ball->getVelocityX());
std::string vx_text = "VelX: " + std::to_string(vx_int);
int vx_width = text_renderer_debug_->getTextWidthPhysical(vx_text.c_str());
text_renderer_debug_->printAbsolute(physical_viewport.w - vx_width - margin, right_y, vx_text.c_str(), {128, 255, 128, 255}); // Verde claro
right_y += line_height;
// Velocidad Y
int vy_int = static_cast<int>(first_ball->getVelocityY());
std::string vy_text = "VelY: " + std::to_string(vy_int);
int vy_width = text_renderer_debug_->getTextWidthPhysical(vy_text.c_str());
text_renderer_debug_->printAbsolute(physical_viewport.w - vy_width - margin, right_y, vy_text.c_str(), {128, 255, 128, 255}); // Verde claro
right_y += line_height;
// Fuerza de gravedad
int grav_int = static_cast<int>(first_ball->getGravityForce());
std::string grav_text = "Gravity: " + std::to_string(grav_int);
int grav_width = text_renderer_debug_->getTextWidthPhysical(grav_text.c_str());
text_renderer_debug_->printAbsolute(physical_viewport.w - grav_width - margin, right_y, grav_text.c_str(), {255, 255, 128, 255}); // Amarillo claro
right_y += line_height;
// Estado superficie
std::string surface_text = first_ball->isOnSurface() ? "Surface: YES" : "Surface: NO";
int surface_width = text_renderer_debug_->getTextWidthPhysical(surface_text.c_str());
text_renderer_debug_->printAbsolute(physical_viewport.w - surface_width - margin, right_y, surface_text.c_str(), {255, 200, 128, 255}); // Naranja claro
right_y += line_height;
// Coeficiente de rebote (loss)
float loss_val = first_ball->getLossCoefficient();
std::string loss_text = "Loss: " + std::to_string(loss_val).substr(0, 4);
int loss_width = text_renderer_debug_->getTextWidthPhysical(loss_text.c_str());
text_renderer_debug_->printAbsolute(physical_viewport.w - loss_width - margin, right_y, loss_text.c_str(), {255, 128, 255, 255}); // Magenta
right_y += line_height;
// Dirección de gravedad
std::string gravity_dir_text = "Dir: " + gravityDirectionToString(static_cast<int>(scene_manager->getCurrentGravity()));
int dir_width = text_renderer_debug_->getTextWidthPhysical(gravity_dir_text.c_str());
text_renderer_debug_->printAbsolute(physical_viewport.w - dir_width - margin, right_y, gravity_dir_text.c_str(), {128, 255, 255, 255}); // Cian claro
right_y += line_height;
lines.push_back("Pos: (" + std::to_string(static_cast<int>(pos.x)) + ", " + std::to_string(static_cast<int>(pos.y)) + ")");
lines.push_back("Gravity: " + std::to_string(static_cast<int>(first_ball->getGravityForce())));
lines.push_back(first_ball->isOnSurface() ? "Surface: YES" : "Surface: NO");
lines.push_back("Loss: " + std::to_string(first_ball->getLossCoefficient()).substr(0, 4));
lines.push_back("Dir: " + gravityDirectionToString(static_cast<int>(scene_manager->getCurrentGravity())));
}
// Convergencia en modo LOGO (solo cuando está activo) - Parte inferior derecha
if (current_app_mode == AppMode::LOGO && current_mode == SimulationMode::SHAPE) {
int convergence_percent = static_cast<int>(shape_convergence * 100.0f);
std::string convergence_text = "Convergence: " + std::to_string(convergence_percent) + "%";
int conv_width = text_renderer_debug_->getTextWidthPhysical(convergence_text.c_str());
text_renderer_debug_->printAbsolute(physical_viewport.w - conv_width - margin, right_y, convergence_text.c_str(), {255, 128, 0, 255}); // Naranja
right_y += line_height;
lines.push_back("Convergence: " + std::to_string(convergence_percent) + "%");
}
}
void UIManager::renderObsoleteText(int current_screen_width) {
// DEPRECATED: Sistema antiguo de texto centrado
// Mantener por compatibilidad temporal hasta migrar todo a Notifier
// --- Render con desbordamiento a segunda columna ---
int max_lines = (physical_viewport.h - 2 * margin) / line_height;
if (max_lines < 1) max_lines = 1;
int col_width = physical_viewport.w / 2;
// Calcular escala dinámica basada en resolución física
float text_scale_x = static_cast<float>(physical_window_width_) / 426.0f;
float text_scale_y = static_cast<float>(physical_window_height_) / 240.0f;
// Obtener color del tema actual (LERP interpolado)
int margin = 8;
Color text_color = theme_manager_->getInterpolatedColor(0);
int text_color_r = text_color.r;
int text_color_g = text_color.g;
int text_color_b = text_color.b;
// Renderizar texto centrado usando coordenadas físicas
text_renderer_->printPhysical(text_pos_, margin, text_.c_str(),
text_color_r, text_color_g, text_color_b,
text_scale_x, text_scale_y);
for (int i = 0; i < static_cast<int>(lines.size()); i++) {
int col = i / max_lines;
int row = i % max_lines;
int x = margin + col * col_width;
int y = margin + row * line_height;
text_renderer_debug_->printAbsoluteShadowed(x, y, lines[i].c_str());
}
}
std::string UIManager::gravityDirectionToString(int direction) const {
@@ -455,20 +373,37 @@ std::string UIManager::gravityDirectionToString(int direction) const {
}
}
int UIManager::calculateFontSize(int physical_width, int physical_height) const {
// Calcular área física de la ventana
int area = physical_width * physical_height;
int UIManager::calculateFontSize(int logical_height) const {
// Escalado híbrido basado en ALTURA LÓGICA (resolución interna, sin zoom)
// Esto asegura que el tamaño de fuente sea consistente independientemente del zoom de ventana
// - Proporcional en extremos (muy bajo/alto)
// - Escalonado en rango medio (estabilidad)
// Stepped scaling con 3 tamaños:
// - SMALL: < 800x600 (480,000 pixels) → 14px
// - MEDIUM: 800x600 a 1920x1080 (2,073,600 pixels) → 18px
// - LARGE: > 1920x1080 → 24px
int font_size = 14; // Default fallback
if (area < 480000) {
return 14; // Ventanas pequeñas
} else if (area < 2073600) {
return 18; // Ventanas medianas (default)
if (logical_height < 300) {
// Rango bajo: proporcional (240px→9.6, 280px→11.2)
font_size = logical_height / 25;
} else if (logical_height < 380) {
// Rango muy bajo (300-379px) → 10px (crítico para 640x360)
font_size = 10;
} else if (logical_height < 500) {
// Rango medio-bajo (380-499px) → 12px
font_size = 12;
} else if (logical_height < 700) {
// Rango medio (500-699px) → 14px
font_size = 14;
} else if (logical_height < 900) {
// Rango medio-alto (700-899px) → 18px
font_size = 18;
} else {
return 24; // Ventanas grandes
// Rango alto: proporcional (1080px→42, 1440px→55, 2160px→72)
font_size = logical_height / 26;
}
// Aplicar límites: mínimo 9px, máximo 72px
if (font_size < 9) font_size = 9;
if (font_size > 72) font_size = 72;
return font_size;
}

View File

@@ -46,9 +46,12 @@ class UIManager {
* @param theme_manager Gestor de temas (para colores)
* @param physical_width Ancho físico de ventana (píxeles reales)
* @param physical_height Alto físico de ventana (píxeles reales)
* @param logical_width Ancho lógico (resolución interna)
* @param logical_height Alto lógico (resolución interna)
*/
void initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
int physical_width, int physical_height);
int physical_width, int physical_height,
int logical_width, int logical_height);
/**
* @brief Actualiza UI (FPS counter, notificaciones, texto obsoleto)
@@ -111,14 +114,6 @@ class UIManager {
*/
void updatePhysicalWindowSize(int width, int height);
/**
* @brief Establece texto obsoleto (DEPRECATED - usar Notifier en su lugar)
* @param text Texto a mostrar
* @param pos Posición X del texto
* @param current_screen_width Ancho de pantalla (para cálculos)
*/
void setTextObsolete(const std::string& text, int pos, int current_screen_width);
// === Getters ===
/**
@@ -131,11 +126,6 @@ class UIManager {
*/
int getCurrentFPS() const { return fps_current_; }
/**
* @brief Verifica si texto obsoleto está visible
*/
bool isTextObsoleteVisible() const { return show_text_; }
private:
/**
* @brief Renderiza HUD de debug (solo si show_debug_ == true)
@@ -153,12 +143,6 @@ class UIManager {
const Shape* active_shape,
float shape_convergence);
/**
* @brief Renderiza texto obsoleto centrado (DEPRECATED)
* @param current_screen_width Ancho lógico de pantalla
*/
void renderObsoleteText(int current_screen_width);
/**
* @brief Convierte dirección de gravedad a string
* @param direction Dirección como int (cast de GravityDirection)
@@ -167,15 +151,13 @@ class UIManager {
std::string gravityDirectionToString(int direction) const;
/**
* @brief Calcula tamaño de fuente apropiado según dimensiones físicas
* @param physical_width Ancho físico de ventana
* @param physical_height Alto físico de ventana
* @return Tamaño de fuente (14px/18px/24px)
* @brief Calcula tamaño de fuente apropiado según dimensiones lógicas
* @param logical_height Alto lógico (resolución interna, sin zoom)
* @return Tamaño de fuente (9-72px)
*/
int calculateFontSize(int physical_width, int physical_height) const;
int calculateFontSize(int logical_height) const;
// === Recursos de renderizado ===
TextRenderer* text_renderer_; // Texto obsoleto (DEPRECATED)
TextRenderer* text_renderer_debug_; // HUD de debug
TextRenderer* text_renderer_notifier_; // Notificaciones
Notifier* notifier_; // Sistema de notificaciones
@@ -183,12 +165,6 @@ class UIManager {
// === Estado de UI ===
bool show_debug_; // HUD de debug activo (tecla F12)
bool show_text_; // Texto obsoleto visible (DEPRECATED)
// === Sistema de texto obsoleto (DEPRECATED) ===
std::string text_; // Texto a mostrar
int text_pos_; // Posición X del texto
Uint64 text_init_time_; // Tiempo de inicio de texto
// === Sistema de FPS ===
Uint64 fps_last_time_; // Último tiempo de actualización de FPS
@@ -202,7 +178,9 @@ class UIManager {
ThemeManager* theme_manager_; // Gestor de temas (para colores)
int physical_window_width_; // Ancho físico de ventana (píxeles reales)
int physical_window_height_; // Alto físico de ventana (píxeles reales)
int logical_window_width_; // Ancho lógico (resolución interna)
int logical_window_height_; // Alto lógico (resolución interna)
// === Sistema de escalado dinámico de texto ===
int current_font_size_; // Tamaño de fuente actual (14/18/24)
int current_font_size_; // Tamaño de fuente actual (9-72px)
};

187
tools/Makefile Normal file
View File

@@ -0,0 +1,187 @@
# ============================================================================
# ViBe3 Physics - Resource Packer Tool Makefile
# ============================================================================
# Directorios
DIR_ROOT := $(dir $(abspath $(dir $(MAKEFILE_LIST))))
DIR_SOURCES := $(DIR_ROOT)source/
DIR_TOOLS := $(DIR_ROOT)tools/
DIR_DATA := $(DIR_ROOT)data/
# Archivos fuente
PACK_SOURCES := $(DIR_TOOLS)pack_resources.cpp $(DIR_SOURCES)resource_pack.cpp
PACK_INCLUDES := -I$(DIR_ROOT)
# Compilador y flags
CXX := g++
CXXFLAGS := -std=c++17 -Wall -Os -ffunction-sections -fdata-sections
# Variables específicas por sistema operativo
ifeq ($(OS),Windows_NT)
# Windows
PACK_TOOL := pack_resources.exe
RESOURCE_PACK := ../resources.pack
TEST_PACK := test_resources.pack
LDFLAGS := -Wl,--gc-sections -static-libstdc++ -static-libgcc
RMFILE := del /Q
MKDIR := mkdir
FixPath = $(subst /,\,$1)
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
# Linux
PACK_TOOL := pack_resources
RESOURCE_PACK := ../resources.pack
TEST_PACK := test_resources.pack
LDFLAGS := -Wl,--gc-sections
RMFILE := rm -f
MKDIR := mkdir -p
FixPath = $1
endif
ifeq ($(UNAME_S),Darwin)
# macOS
PACK_TOOL := pack_resources
RESOURCE_PACK := ../resources.pack
TEST_PACK := test_resources.pack
LDFLAGS :=
RMFILE := rm -f
MKDIR := mkdir -p
FixPath = $1
endif
endif
# Detectar todos los archivos en data/ como dependencias
DATA_FILES := $(shell find ../data -type f 2>/dev/null)
# ============================================================================
# Targets principales
# ============================================================================
.PHONY: all pack_tool resource_pack test_pack clean help
# Target por defecto: compilar herramienta
all: pack_tool
# Compilar herramienta de empaquetado
pack_tool: $(PACK_TOOL)
$(PACK_TOOL): $(PACK_SOURCES)
@echo "=========================================="
@echo "Compilando herramienta de empaquetado..."
@echo "=========================================="
$(CXX) $(CXXFLAGS) $(PACK_INCLUDES) $(PACK_SOURCES) $(LDFLAGS) -o $(PACK_TOOL)
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
@echo "✓ Herramienta compilada: $(PACK_TOOL)"
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
# Crear pack de recursos final
resource_pack: $(PACK_TOOL)
@echo "=========================================="
@echo "Generando resources.pack..."
@echo "=========================================="
@echo "Directorio de datos: ../data"
@echo "Archivo de salida: $(RESOURCE_PACK)"
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
./$(PACK_TOOL) ../data $(RESOURCE_PACK)
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
@echo "✓ Pack de recursos creado exitosamente"
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
# Crear pack de recursos de prueba
test_pack: $(PACK_TOOL)
@echo "=========================================="
@echo "Generando pack de prueba..."
@echo "=========================================="
@echo "Directorio de datos: ../data"
@echo "Archivo de salida: $(TEST_PACK)"
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
./$(PACK_TOOL) ../data $(TEST_PACK)
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
@echo "✓ Pack de prueba creado: $(TEST_PACK)"
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
# Limpiar archivos generados
clean:
@echo "Limpiando archivos generados..."
ifeq ($(OS),Windows_NT)
@if exist "$(PACK_TOOL)" $(RMFILE) "$(PACK_TOOL)"
@if exist "$(TEST_PACK)" $(RMFILE) "$(TEST_PACK)"
else
@$(RMFILE) $(PACK_TOOL) $(TEST_PACK)
endif
@echo "✓ Limpieza completada"
# Mostrar ayuda
help:
@echo "=========================================="
@echo "ViBe3 Physics - Resource Packer Makefile"
@echo "=========================================="
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
@echo "Comandos disponibles:"
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
@echo " make - Compilar herramienta (equivalente a 'make pack_tool')"
@echo " make pack_tool - Compilar herramienta de empaquetado"
@echo " make resource_pack - Crear ../resources.pack desde ../data"
@echo " make test_pack - Crear pack de prueba (test_resources.pack)"
@echo " make clean - Limpiar archivos generados"
@echo " make help - Mostrar esta ayuda"
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
@echo "Ejemplos de uso:"
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif
@echo " cd tools"
@echo " make # Compilar herramienta"
@echo " make resource_pack # Crear pack de recursos"
@echo " ./$(PACK_TOOL) --help # Ver ayuda de la herramienta"
ifeq ($(OS),Windows_NT)
@echo.
else
@echo ""
endif

Binary file not shown.

View File

@@ -1,4 +1,4 @@
#include "../source/resource_pack.h"
#include "../source/resource_pack.hpp"
#include <iostream>
#include <filesystem>