From f6402084eb750ec400c33d58084b0643d339339d Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 17 Oct 2025 20:09:33 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Bordes=20como=20obst=C3=A1culos=20+=20V?= =?UTF-8?q?ariables=20BOIDS=20ajustables=20+=20Fix=20tecla=20G?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **1. Bordes como obstáculos (no más wrapping):** - Implementada fuerza de repulsión cuando boids se acercan a bordes - Nueva regla: Boundary Avoidance (evitar bordes) - Fuerza proporcional a cercanía (0% en margen, 100% en colisión) - Constantes: BOID_BOUNDARY_MARGIN (50px), BOID_BOUNDARY_WEIGHT (7200 px/s²) **2. Variables ajustables en runtime:** - Añadidas 11 variables miembro en BoidManager (inicializadas con defines.h) - Permite modificar comportamiento sin recompilar - Variables: radios (separation/alignment/cohesion), weights, speeds, boundary - Base para futuras herramientas de debug/tweaking visual **3. Fix tecla G (BOIDS → PHYSICS):** - Corregido: toggleBoidsMode() ahora acepta parámetro force_gravity_on - handleGravityToggle() pasa explícitamente false para preservar inercia - Transición BOIDS→PHYSICS ahora mantiene gravedad OFF correctamente **Implementación:** - defines.h: +2 constantes (BOUNDARY_MARGIN, BOUNDARY_WEIGHT) - boid_manager.h: +11 variables miembro ajustables - boid_manager.cpp: - Constructor inicializa variables - Todas las funciones usan variables en lugar de constantes - applyBoundaries() completamente reescrito (repulsión vs wrapping) - engine.h/cpp: toggleBoidsMode() con parámetro opcional 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- source/boids_mgr/boid_manager.cpp | 134 ++++++++++++++++++++---------- source/boids_mgr/boid_manager.h | 16 +++- source/defines.h | 2 + source/engine.cpp | 6 +- source/engine.h | 2 +- 5 files changed, 112 insertions(+), 48 deletions(-) diff --git a/source/boids_mgr/boid_manager.cpp b/source/boids_mgr/boid_manager.cpp index 8dc1b30..e4928f1 100644 --- a/source/boids_mgr/boid_manager.cpp +++ b/source/boids_mgr/boid_manager.cpp @@ -17,7 +17,18 @@ BoidManager::BoidManager() , screen_width_(0) , screen_height_(0) , boids_active_(false) - , spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) { // Tamaño por defecto, se actualiza en initialize() + , spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) // Tamaño por defecto, se actualiza en initialize() + , separation_radius_(BOID_SEPARATION_RADIUS) + , alignment_radius_(BOID_ALIGNMENT_RADIUS) + , cohesion_radius_(BOID_COHESION_RADIUS) + , separation_weight_(BOID_SEPARATION_WEIGHT) + , alignment_weight_(BOID_ALIGNMENT_WEIGHT) + , cohesion_weight_(BOID_COHESION_WEIGHT) + , max_speed_(BOID_MAX_SPEED) + , min_speed_(BOID_MIN_SPEED) + , max_force_(BOID_MAX_FORCE) + , boundary_margin_(BOID_BOUNDARY_MARGIN) + , boundary_weight_(BOID_BOUNDARY_WEIGHT) { } BoidManager::~BoidManager() { @@ -146,7 +157,7 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) { float center_y = pos.y + pos.h / 2.0f; // FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n)) - auto neighbors = spatial_grid_.queryRadius(center_x, center_y, BOID_SEPARATION_RADIUS); + auto neighbors = spatial_grid_.queryRadius(center_x, center_y, separation_radius_); for (Ball* other : neighbors) { if (other == boid) continue; // Ignorar a sí mismo @@ -159,10 +170,10 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) { float dy = center_y - other_y; float distance = std::sqrt(dx * dx + dy * dy); - if (distance > 0.0f && distance < BOID_SEPARATION_RADIUS) { + if (distance > 0.0f && distance < separation_radius_) { // FASE 1.3: Separación más fuerte cuando más cerca (inversamente proporcional a distancia) // Fuerza proporcional a cercanía: 0% en radio máximo, 100% en colisión - float separation_strength = (BOID_SEPARATION_RADIUS - distance) / BOID_SEPARATION_RADIUS; + float separation_strength = (separation_radius_ - distance) / separation_radius_; steer_x += (dx / distance) * separation_strength; steer_y += (dy / distance) * separation_strength; count++; @@ -177,8 +188,8 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) { // Aplicar fuerza de separación float vx, vy; boid->getVelocity(vx, vy); - vx += steer_x * BOID_SEPARATION_WEIGHT * delta_time; - vy += steer_y * BOID_SEPARATION_WEIGHT * delta_time; + vx += steer_x * separation_weight_ * delta_time; + vy += steer_y * separation_weight_ * delta_time; boid->setVelocity(vx, vy); } } @@ -194,7 +205,7 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) { float center_y = pos.y + pos.h / 2.0f; // FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n)) - auto neighbors = spatial_grid_.queryRadius(center_x, center_y, BOID_ALIGNMENT_RADIUS); + auto neighbors = spatial_grid_.queryRadius(center_x, center_y, alignment_radius_); for (Ball* other : neighbors) { if (other == boid) continue; @@ -207,7 +218,7 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) { float dy = center_y - other_y; float distance = std::sqrt(dx * dx + dy * dy); - if (distance < BOID_ALIGNMENT_RADIUS) { + if (distance < alignment_radius_) { float other_vx, other_vy; other->getVelocity(other_vx, other_vy); avg_vx += other_vx; @@ -224,14 +235,14 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) { // Steering hacia la velocidad promedio float vx, vy; boid->getVelocity(vx, vy); - float steer_x = (avg_vx - vx) * BOID_ALIGNMENT_WEIGHT * delta_time; - float steer_y = (avg_vy - vy) * BOID_ALIGNMENT_WEIGHT * delta_time; + float steer_x = (avg_vx - vx) * alignment_weight_ * delta_time; + float steer_y = (avg_vy - vy) * alignment_weight_ * delta_time; // Limitar fuerza máxima de steering float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y); - if (steer_mag > BOID_MAX_FORCE) { - steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE; - steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE; + if (steer_mag > max_force_) { + steer_x = (steer_x / steer_mag) * max_force_; + steer_y = (steer_y / steer_mag) * max_force_; } vx += steer_x; @@ -251,7 +262,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) { float center_y = pos.y + pos.h / 2.0f; // FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n)) - auto neighbors = spatial_grid_.queryRadius(center_x, center_y, BOID_COHESION_RADIUS); + auto neighbors = spatial_grid_.queryRadius(center_x, center_y, cohesion_radius_); for (Ball* other : neighbors) { if (other == boid) continue; @@ -264,7 +275,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) { float dy = center_y - other_y; float distance = std::sqrt(dx * dx + dy * dy); - if (distance < BOID_COHESION_RADIUS) { + if (distance < cohesion_radius_) { center_of_mass_x += other_x; center_of_mass_y += other_y; count++; @@ -284,14 +295,14 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) { // Solo aplicar si hay distancia al centro (evitar división por cero) if (distance_to_center > 0.1f) { // Normalizar vector dirección (fuerza independiente de distancia) - float steer_x = (dx_to_center / distance_to_center) * BOID_COHESION_WEIGHT * delta_time; - float steer_y = (dy_to_center / distance_to_center) * BOID_COHESION_WEIGHT * delta_time; + float steer_x = (dx_to_center / distance_to_center) * cohesion_weight_ * delta_time; + float steer_y = (dy_to_center / distance_to_center) * cohesion_weight_ * delta_time; // Limitar fuerza máxima de steering float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y); - if (steer_mag > BOID_MAX_FORCE) { - steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE; - steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE; + if (steer_mag > max_force_) { + steer_x = (steer_x / steer_mag) * max_force_; + steer_y = (steer_y / steer_mag) * max_force_; } float vx, vy; @@ -304,32 +315,69 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) { } void BoidManager::applyBoundaries(Ball* boid) { - // Mantener boids dentro de los límites de la pantalla - // Comportamiento "wrapping" (teletransporte al otro lado) + // NUEVA IMPLEMENTACIÓN: Bordes como obstáculos (repulsión en lugar de wrapping) + // Cuando un boid se acerca a un borde, se aplica una fuerza alejándolo SDL_FRect pos = boid->getPosition(); float center_x = pos.x + pos.w / 2.0f; float center_y = pos.y + pos.h / 2.0f; - bool wrapped = false; + float steer_x = 0.0f; + float steer_y = 0.0f; - if (center_x < 0) { - pos.x = screen_width_ - pos.w / 2.0f; - wrapped = true; - } else if (center_x > screen_width_) { - pos.x = -pos.w / 2.0f; - wrapped = true; + // Borde izquierdo (x < boundary_margin_) + if (center_x < boundary_margin_) { + float distance = center_x; // Distancia al borde (0 = colisión) + if (distance < boundary_margin_) { + // Fuerza proporcional a cercanía: 0% en margen, 100% en colisión + float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_; + steer_x += repulsion_strength; // Empujar hacia la derecha + } } - if (center_y < 0) { - pos.y = screen_height_ - pos.h / 2.0f; - wrapped = true; - } else if (center_y > screen_height_) { - pos.y = -pos.h / 2.0f; - wrapped = true; + // Borde derecho (x > screen_width_ - boundary_margin_) + if (center_x > screen_width_ - boundary_margin_) { + float distance = screen_width_ - center_x; + if (distance < boundary_margin_) { + float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_; + steer_x -= repulsion_strength; // Empujar hacia la izquierda + } } - if (wrapped) { - boid->setPosition(pos.x, pos.y); + // Borde superior (y < boundary_margin_) + if (center_y < boundary_margin_) { + float distance = center_y; + if (distance < boundary_margin_) { + float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_; + steer_y += repulsion_strength; // Empujar hacia abajo + } + } + + // Borde inferior (y > screen_height_ - boundary_margin_) + if (center_y > screen_height_ - boundary_margin_) { + float distance = screen_height_ - center_y; + if (distance < boundary_margin_) { + float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_; + steer_y -= repulsion_strength; // Empujar hacia arriba + } + } + + // Aplicar fuerza de repulsión si hay alguna + if (steer_x != 0.0f || steer_y != 0.0f) { + float vx, vy; + boid->getVelocity(vx, vy); + + // Normalizar fuerza de repulsión (para que todas las direcciones tengan la misma intensidad) + float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y); + if (steer_mag > 0.0f) { + steer_x /= steer_mag; + steer_y /= steer_mag; + } + + // Aplicar aceleración de repulsión (time-based) + // boundary_weight_ es más fuerte que separation para garantizar que no escapen + vx += steer_x * boundary_weight_ * (1.0f / 60.0f); // Simular delta_time fijo para independencia + vy += steer_y * boundary_weight_ * (1.0f / 60.0f); + boid->setVelocity(vx, vy); } } @@ -341,16 +389,16 @@ void BoidManager::limitSpeed(Ball* boid) { float speed = std::sqrt(vx * vx + vy * vy); // Limitar velocidad máxima - if (speed > BOID_MAX_SPEED) { - vx = (vx / speed) * BOID_MAX_SPEED; - vy = (vy / speed) * BOID_MAX_SPEED; + if (speed > max_speed_) { + vx = (vx / speed) * max_speed_; + vy = (vy / speed) * max_speed_; boid->setVelocity(vx, vy); } // FASE 1.2: Aplicar velocidad mínima (evitar boids estáticos) - if (speed > 0.0f && speed < BOID_MIN_SPEED) { - vx = (vx / speed) * BOID_MIN_SPEED; - vy = (vy / speed) * BOID_MIN_SPEED; + if (speed > 0.0f && speed < min_speed_) { + vx = (vx / speed) * min_speed_; + vy = (vy / speed) * min_speed_; boid->setVelocity(vx, vy); } } diff --git a/source/boids_mgr/boid_manager.h b/source/boids_mgr/boid_manager.h index 1fe6c30..50241dc 100644 --- a/source/boids_mgr/boid_manager.h +++ b/source/boids_mgr/boid_manager.h @@ -103,10 +103,24 @@ class BoidManager { // FASE 2: Grid reutilizable para búsquedas de vecinos SpatialGrid spatial_grid_; + // === Parámetros ajustables en runtime (inicializados con valores de defines.h) === + // Permite modificar comportamiento sin recompilar (para tweaking/debug visual) + float separation_radius_; // Radio de separación (evitar colisiones) + float alignment_radius_; // Radio de alineación (matching de velocidad) + float cohesion_radius_; // Radio de cohesión (centro de masa) + float separation_weight_; // Peso fuerza de separación (aceleración px/s²) + float alignment_weight_; // Peso fuerza de alineación (steering proporcional) + float cohesion_weight_; // Peso fuerza de cohesión (aceleración px/s²) + float max_speed_; // Velocidad máxima (px/s) + float min_speed_; // Velocidad mínima (px/s) + float max_force_; // Fuerza máxima de steering (px/s) + float boundary_margin_; // Margen para repulsión de bordes (px) + float boundary_weight_; // Peso fuerza de repulsión de bordes (aceleración px/s²) + // Métodos privados para las reglas de Reynolds void applySeparation(Ball* boid, float delta_time); void applyAlignment(Ball* boid, float delta_time); void applyCohesion(Ball* boid, float delta_time); - void applyBoundaries(Ball* boid); // Mantener boids dentro de pantalla + void applyBoundaries(Ball* boid); // Repulsión de bordes (ya no wrapping) void limitSpeed(Ball* boid); // Limitar velocidad máxima }; diff --git a/source/defines.h b/source/defines.h index 9ada812..aa1a75e 100644 --- a/source/defines.h +++ b/source/defines.h @@ -304,6 +304,8 @@ constexpr float BOID_COHESION_WEIGHT = 3.6f; // Aceleración de cohesió constexpr float BOID_MAX_SPEED = 150.0f; // Velocidad máxima (px/s) [era 2.5 × 60] constexpr float BOID_MAX_FORCE = 3.0f; // Fuerza máxima de steering (px/s) [era 0.05 × 60] constexpr float BOID_MIN_SPEED = 18.0f; // Velocidad mínima (px/s) [era 0.3 × 60] +constexpr float BOID_BOUNDARY_MARGIN = 50.0f; // Distancia a borde para activar repulsión (píxeles) +constexpr float BOID_BOUNDARY_WEIGHT = 7200.0f; // Aceleración de repulsión de bordes (px/s²) [más fuerte que separation] // FASE 2: Spatial Hash Grid para optimización O(n²) → O(n) constexpr float BOID_GRID_CELL_SIZE = 100.0f; // Tamaño de celda del grid (píxeles) diff --git a/source/engine.cpp b/source/engine.cpp index 13cda39..9796fb1 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -343,7 +343,7 @@ void Engine::handleGravityToggle() { // Si estamos en modo boids, salir a modo física CON GRAVEDAD OFF // Según RULES.md: "BOIDS a PHYSICS: Pulsando la tecla G: Gravedad OFF" if (current_mode_ == SimulationMode::BOIDS) { - toggleBoidsMode(); // Cambiar a PHYSICS (preserva inercia, gravedad ya está OFF desde activateBoids) + toggleBoidsMode(false); // Cambiar a PHYSICS sin activar gravedad (preserva inercia) // NO llamar a forceBallsGravityOff() porque aplica impulsos que destruyen la inercia de BOIDS // La gravedad ya está desactivada por BoidManager::activateBoids() y se mantiene al salir showNotificationForAction("Modo Física - Gravedad Off"); @@ -436,11 +436,11 @@ void Engine::toggleDepthZoom() { } // Boids (comportamiento de enjambre) -void Engine::toggleBoidsMode() { +void Engine::toggleBoidsMode(bool force_gravity_on) { if (current_mode_ == SimulationMode::BOIDS) { // Salir del modo boids (velocidades ya son time-based, no requiere conversión) current_mode_ = SimulationMode::PHYSICS; - boid_manager_->deactivateBoids(false); // NO activar gravedad (preservar momentum) + boid_manager_->deactivateBoids(force_gravity_on); // Pasar parámetro para control preciso } else { // Entrar al modo boids (desde PHYSICS o SHAPE) if (current_mode_ == SimulationMode::SHAPE) { diff --git a/source/engine.h b/source/engine.h index 0b0ad0a..c25d1b6 100644 --- a/source/engine.h +++ b/source/engine.h @@ -49,7 +49,7 @@ class Engine { void toggleDepthZoom(); // Boids (comportamiento de enjambre) - void toggleBoidsMode(); + void toggleBoidsMode(bool force_gravity_on = true); // Temas de colores void cycleTheme(bool forward);