Compare commits
15 Commits
2025-10-25
...
2026-03-18
| Author | SHA1 | Date | |
|---|---|---|---|
| 6aa4a1227e | |||
| 02fdcd4113 | |||
| 7db9e46f95 | |||
| ff6aaef7c6 | |||
| 8e2e681b2c | |||
| f06123feff | |||
| cbe6dc9744 | |||
| dfbd8a430b | |||
| ea27a771ab | |||
| 09303537a4 | |||
| df17e85a8a | |||
| ce5c4681b8 | |||
| b79f1c3424 | |||
| a65544e8b3 | |||
| b9264c96a1 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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/
|
||||
|
||||
@@ -59,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")
|
||||
|
||||
2
Makefile
2
Makefile
@@ -159,7 +159,7 @@ windows_release: force_resource_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)
|
||||
|
||||
BIN
release/dll/libwinpthread-1.dll
Normal file
BIN
release/dll/libwinpthread-1.dll
Normal file
Binary file not shown.
@@ -32,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
|
||||
@@ -52,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)
|
||||
|
||||
@@ -74,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;
|
||||
@@ -226,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
|
||||
@@ -282,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;
|
||||
@@ -540,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
|
||||
@@ -668,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)
|
||||
@@ -1282,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) {
|
||||
@@ -1292,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());
|
||||
@@ -1406,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
|
||||
@@ -1566,9 +1617,14 @@ void Engine::executeRandomizeOnDemoStart(bool is_lite) {
|
||||
// changeScenario() creará las pelotas y luego llamará a generateShape()
|
||||
}
|
||||
|
||||
// 2. Escenario (excluir índices 0, 6, 7) - AHORA con current_mode_ ya establecido correctamente
|
||||
int valid_scenarios[] = {1, 2, 3, 4, 5};
|
||||
int new_scenario = valid_scenarios[rand() % 5];
|
||||
// 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
|
||||
@@ -1614,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)
|
||||
@@ -1829,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
#include <SDL3/SDL_keycode.h> // for SDL_Keycode
|
||||
#include <string> // for std::string, std::to_string
|
||||
|
||||
#include "engine.hpp" // for Engine
|
||||
#include "defines.hpp" // for KIOSK_NOTIFICATION_TEXT
|
||||
#include "engine.hpp" // for Engine
|
||||
#include "external/mouse.hpp" // for Mouse namespace
|
||||
|
||||
bool InputHandler::processEvents(Engine& engine) {
|
||||
@@ -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)
|
||||
|
||||
@@ -16,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";
|
||||
@@ -24,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";
|
||||
@@ -34,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
|
||||
@@ -80,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];
|
||||
@@ -99,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();
|
||||
@@ -113,15 +150,26 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
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();
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
|
||||
// Inicializar el puntero estático
|
||||
// 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
|
||||
@@ -29,6 +31,7 @@ bool ResourceManager::init(const std::string& packFilePath) {
|
||||
}
|
||||
|
||||
void ResourceManager::shutdown() {
|
||||
cache_.clear();
|
||||
if (resourcePack_ != nullptr) {
|
||||
delete resourcePack_;
|
||||
resourcePack_ = nullptr;
|
||||
@@ -39,36 +42,41 @@ bool ResourceManager::loadResource(const std::string& resourcePath, unsigned cha
|
||||
data = nullptr;
|
||||
size = 0;
|
||||
|
||||
// 1. Intentar cargar desde pack (si está disponible)
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback: cargar desde disco
|
||||
// 3. Fallback: cargar desde disco
|
||||
std::ifstream file(resourcePath, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
// Intentar con "data/" como prefijo si no se encontró
|
||||
std::string dataPath = "data/" + resourcePath;
|
||||
file.open(dataPath, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
if (!file) { return false; }
|
||||
}
|
||||
|
||||
// Obtener tamaño del archivo
|
||||
size = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
// Alocar buffer y leer
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -79,4 +80,7 @@ private:
|
||||
|
||||
// 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_;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -313,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ui_manager.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "ball.hpp" // for Ball
|
||||
@@ -84,7 +85,7 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
||||
text_renderer_notifier_ = new TextRenderer();
|
||||
|
||||
// Inicializar renderers con tamaño dinámico
|
||||
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
|
||||
@@ -109,7 +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_);
|
||||
fps_text_ = "FPS: " + std::to_string(fps_current_);
|
||||
}
|
||||
|
||||
// Actualizar sistema de notificaciones
|
||||
@@ -180,7 +181,7 @@ void UIManager::updatePhysicalWindowSize(int width, int height) {
|
||||
|
||||
// Reinicializar text renderers con nuevo tamaño
|
||||
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_);
|
||||
@@ -204,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";
|
||||
@@ -252,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();
|
||||
@@ -294,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);
|
||||
@@ -322,83 +304,62 @@ 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) + "%");
|
||||
}
|
||||
|
||||
// --- 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;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user