From 9909d4c12dbf0bcd7f1a8fdc7e476fc3487fb3b1 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 17 Oct 2025 20:05:49 +0200 Subject: [PATCH] feat: Convertir BOIDS a sistema time-based (independiente de framerate) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Conversión completa de físicas BOIDS de frame-based a time-based - Velocidades: ×60 (px/frame → px/s) - Aceleraciones (Separation, Cohesion): ×3600 (px/frame² → px/s²) - Steering proporcional (Alignment): ×60 - Límites de velocidad: ×60 Constantes actualizadas en defines.h: - BOID_SEPARATION_WEIGHT: 1.5 → 5400.0 (aceleración) - BOID_COHESION_WEIGHT: 0.001 → 3.6 (aceleración) - BOID_ALIGNMENT_WEIGHT: 1.0 → 60.0 (steering) - BOID_MAX_SPEED: 2.5 → 150.0 px/s - BOID_MIN_SPEED: 0.3 → 18.0 px/s - BOID_MAX_FORCE: 0.05 → 3.0 px/s Física ahora consistente en 60Hz, 144Hz, 240Hz screens. Transiciones BOIDS↔PHYSICS preservan velocidad correctamente. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- source/boids_mgr/boid_manager.cpp | 12 +++++------ source/defines.h | 19 ++++++++++------ source/engine.cpp | 36 +++++++++++++++++++++++++------ source/ui/ui_manager.cpp | 21 ++++++++++++++++++ 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/source/boids_mgr/boid_manager.cpp b/source/boids_mgr/boid_manager.cpp index 114a380..8dc1b30 100644 --- a/source/boids_mgr/boid_manager.cpp +++ b/source/boids_mgr/boid_manager.cpp @@ -57,9 +57,9 @@ void BoidManager::activateBoids() { float vx, vy; ball->getVelocity(vx, vy); if (vx == 0.0f && vy == 0.0f) { - // Velocidad aleatoria entre -1 y 1 - vx = (rand() % 200 - 100) / 100.0f; - vy = (rand() % 200 - 100) / 100.0f; + // Velocidad aleatoria entre -60 y +60 px/s (time-based) + vx = ((rand() % 200 - 100) / 100.0f) * 60.0f; + vy = ((rand() % 200 - 100) / 100.0f) * 60.0f; ball->setVelocity(vx, vy); } } @@ -118,14 +118,14 @@ void BoidManager::update(float delta_time) { limitSpeed(ball.get()); } - // Actualizar posiciones con velocidades resultantes + // Actualizar posiciones con velocidades resultantes (time-based) for (auto& ball : balls) { float vx, vy; ball->getVelocity(vx, vy); SDL_FRect pos = ball->getPosition(); - pos.x += vx; - pos.y += vy; + pos.x += vx * delta_time; // time-based + pos.y += vy * delta_time; ball->setPosition(pos.x, pos.y); } diff --git a/source/defines.h b/source/defines.h index 8d7a553..9ada812 100644 --- a/source/defines.h +++ b/source/defines.h @@ -289,16 +289,21 @@ constexpr float LOGO_FLIP_TRIGGER_MAX = 0.80f; // 80% máximo de progres constexpr int LOGO_FLIP_WAIT_PROBABILITY = 50; // 50% probabilidad de elegir el camino "esperar flip" // Configuración de Modo BOIDS (comportamiento de enjambre) -// FASE 1.1 REVISADA: Parámetros ajustados tras detectar cohesión mal normalizada +// TIME-BASED CONVERSION (frame-based → time-based): +// - Radios: sin cambios (píxeles) +// - Velocidades (MAX_SPEED, MIN_SPEED): ×60 (px/frame → px/s) +// - Aceleraciones puras (SEPARATION, COHESION): ×60² = ×3600 (px/frame² → px/s²) +// - Steering proporcional (ALIGNMENT): ×60 (proporcional a velocidad) +// - Límite velocidad (MAX_FORCE): ×60 (px/frame → px/s) constexpr float BOID_SEPARATION_RADIUS = 30.0f; // Radio para evitar colisiones (píxeles) constexpr float BOID_ALIGNMENT_RADIUS = 50.0f; // Radio para alinear velocidad con vecinos constexpr float BOID_COHESION_RADIUS = 80.0f; // Radio para moverse hacia centro del grupo -constexpr float BOID_SEPARATION_WEIGHT = 1.5f; // Peso de separación -constexpr float BOID_ALIGNMENT_WEIGHT = 1.0f; // Peso de alineación -constexpr float BOID_COHESION_WEIGHT = 0.001f; // Peso de cohesión (MICRO - 1000x menor por falta de normalización) -constexpr float BOID_MAX_SPEED = 2.5f; // Velocidad máxima (píxeles/frame - REDUCIDA) -constexpr float BOID_MAX_FORCE = 0.05f; // Fuerza máxima de steering (REDUCIDA para evitar aceleración excesiva) -constexpr float BOID_MIN_SPEED = 0.3f; // Velocidad mínima (evita boids estáticos) +constexpr float BOID_SEPARATION_WEIGHT = 5400.0f; // Aceleración de separación (px/s²) [era 1.5 × 3600] +constexpr float BOID_ALIGNMENT_WEIGHT = 60.0f; // Steering de alineación (proporcional) [era 1.0 × 60] +constexpr float BOID_COHESION_WEIGHT = 3.6f; // Aceleración de cohesión (px/s²) [era 0.001 × 3600] +constexpr float BOID_MAX_SPEED = 150.0f; // Velocidad máxima (px/s) [era 2.5 × 60] +constexpr float BOID_MAX_FORCE = 3.0f; // Fuerza máxima de steering (px/s) [era 0.05 × 60] +constexpr float BOID_MIN_SPEED = 18.0f; // Velocidad mínima (px/s) [era 0.3 × 60] // FASE 2: Spatial Hash Grid para optimización O(n²) → O(n) constexpr float BOID_GRID_CELL_SIZE = 100.0f; // Tamaño de celda del grid (píxeles) diff --git a/source/engine.cpp b/source/engine.cpp index 66855fa..13cda39 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -364,18 +364,19 @@ void Engine::handleGravityToggle() { } void Engine::handleGravityDirectionChange(GravityDirection direction, const char* notification_text) { - // Si estamos en modo boids, salir a modo física primero + // Si estamos en modo boids, salir a modo física primero PRESERVANDO VELOCIDAD if (current_mode_ == SimulationMode::BOIDS) { - toggleBoidsMode(); // Esto cambia a PHYSICS y activa gravedad - // Continuar para aplicar la dirección de gravedad + current_mode_ = SimulationMode::PHYSICS; + boid_manager_->deactivateBoids(false); // NO activar gravedad aún (preservar momentum) + scene_manager_->forceBallsGravityOn(); // Activar gravedad SIN impulsos (preserva velocidad) } - // Si estamos en modo figura, salir a modo física CON gravedad - if (current_mode_ == SimulationMode::SHAPE) { + else if (current_mode_ == SimulationMode::SHAPE) { toggleShapeModeInternal(); // Desactivar figura (activa gravedad automáticamente) } else { scene_manager_->enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF } + scene_manager_->changeGravityDirection(direction); showNotificationForAction(notification_text); } @@ -437,9 +438,9 @@ void Engine::toggleDepthZoom() { // Boids (comportamiento de enjambre) void Engine::toggleBoidsMode() { if (current_mode_ == SimulationMode::BOIDS) { - // Salir del modo boids + // Salir del modo boids (velocidades ya son time-based, no requiere conversión) current_mode_ = SimulationMode::PHYSICS; - boid_manager_->deactivateBoids(); + boid_manager_->deactivateBoids(false); // NO activar gravedad (preservar momentum) } else { // Entrar al modo boids (desde PHYSICS o SHAPE) if (current_mode_ == SimulationMode::SHAPE) { @@ -1359,6 +1360,18 @@ void Engine::executeDemoAction(bool is_lite) { int valid_scenarios[] = {1, 2, 3, 4, 5}; int new_scenario = valid_scenarios[rand() % 5]; scene_manager_->changeScenario(new_scenario, current_mode_); + + // Si estamos en modo SHAPE, regenerar la figura con nuevo número de pelotas + if (current_mode_ == SimulationMode::SHAPE) { + generateShape(); + + // Activar atracción física en las bolas nuevas (crítico tras changeScenario) + auto& balls = scene_manager_->getBallsMutable(); + for (auto& ball : balls) { + ball->enableShapeAttraction(true); + } + } + return; } @@ -1573,6 +1586,15 @@ void Engine::executeExitLogoMode() { clampShapeScale(); generateShape(); + // Activar atracción física si estamos en modo SHAPE + // (crítico para que las bolas se muevan hacia la figura restaurada) + if (current_mode_ == SimulationMode::SHAPE) { + auto& balls = scene_manager_->getBallsMutable(); + for (auto& ball : balls) { + ball->enableShapeAttraction(true); + } + } + // Desactivar modo LOGO en PNG_SHAPE (volver a flip intervals normales) if (active_shape_) { PNGShape* png_shape = dynamic_cast(active_shape_.get()); diff --git a/source/ui/ui_manager.cpp b/source/ui/ui_manager.cpp index 7b5c544..df05d45 100644 --- a/source/ui/ui_manager.cpp +++ b/source/ui/ui_manager.cpp @@ -277,6 +277,27 @@ void UIManager::renderDebugHUD(const Engine* engine, 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) + 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(); + for (int i = 0; i < digits; i++) { + 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;