feat: Bordes como obstáculos + Variables BOIDS ajustables + Fix tecla G

**1. Bordes como obstáculos (no más wrapping):**
- Implementada fuerza de repulsión cuando boids se acercan a bordes
- Nueva regla: Boundary Avoidance (evitar bordes)
- Fuerza proporcional a cercanía (0% en margen, 100% en colisión)
- Constantes: BOID_BOUNDARY_MARGIN (50px), BOID_BOUNDARY_WEIGHT (7200 px/s²)

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

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

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-17 20:09:33 +02:00
parent 9909d4c12d
commit f6402084eb
5 changed files with 112 additions and 48 deletions

View File

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