Refactor fase 10: Implementar BoidManager completo
Cambios realizados: - Creado BoidManager (source/boids_mgr/) con algoritmo de Reynolds (1987) * Separación: Evitar colisiones con vecinos cercanos * Alineación: Seguir dirección promedio del grupo * Cohesión: Moverse hacia centro de masa del grupo * Wrapping boundaries (teletransporte en bordes) * Velocidad y fuerza limitadas (steering behavior) - Añadido BOIDS a enum SimulationMode (defines.h) - Añadidas constantes de configuración boids (defines.h) - Integrado BoidManager en Engine (inicialización, update, toggle) - Añadido binding de tecla J para toggleBoidsMode() (input_handler.cpp) - Añadidos helpers en Ball: getVelocity(), setVelocity(), setPosition() - Actualizado CMakeLists.txt para incluir source/boids_mgr/*.cpp Arquitectura: - BoidManager sigue el patrón establecido (similar a ShapeManager) - Gestión independiente del comportamiento de enjambre - Tres reglas de Reynolds implementadas correctamente - Compatible con sistema de resolución dinámica Estado: Compilación exitosa, BoidManager funcional Próximo paso: Testing y ajuste de parámetros boids 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
314
source/boids_mgr/boid_manager.cpp
Normal file
314
source/boids_mgr/boid_manager.cpp
Normal file
@@ -0,0 +1,314 @@
|
||||
#include "boid_manager.h"
|
||||
|
||||
#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
|
||||
|
||||
BoidManager::BoidManager()
|
||||
: engine_(nullptr)
|
||||
, scene_mgr_(nullptr)
|
||||
, ui_mgr_(nullptr)
|
||||
, state_mgr_(nullptr)
|
||||
, screen_width_(0)
|
||||
, screen_height_(0)
|
||||
, boids_active_(false) {
|
||||
}
|
||||
|
||||
BoidManager::~BoidManager() {
|
||||
}
|
||||
|
||||
void BoidManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr,
|
||||
StateManager* state_mgr, int screen_width, int screen_height) {
|
||||
engine_ = engine;
|
||||
scene_mgr_ = scene_mgr;
|
||||
ui_mgr_ = ui_mgr;
|
||||
state_mgr_ = state_mgr;
|
||||
screen_width_ = screen_width;
|
||||
screen_height_ = screen_height;
|
||||
}
|
||||
|
||||
void BoidManager::updateScreenSize(int width, int height) {
|
||||
screen_width_ = width;
|
||||
screen_height_ = height;
|
||||
}
|
||||
|
||||
void BoidManager::activateBoids() {
|
||||
boids_active_ = true;
|
||||
|
||||
// Desactivar gravedad al entrar en modo boids
|
||||
scene_mgr_->forceBallsGravityOff();
|
||||
|
||||
// Inicializar velocidades aleatorias para los boids
|
||||
auto& balls = scene_mgr_->getBallsMutable();
|
||||
for (auto& ball : balls) {
|
||||
// Dar velocidad inicial aleatoria si está quieto
|
||||
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;
|
||||
ball->setVelocity(vx, vy);
|
||||
}
|
||||
}
|
||||
|
||||
// Mostrar notificación (solo si NO estamos en modo demo o logo)
|
||||
if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
ui_mgr_->showNotification("Modo Boids");
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::deactivateBoids(bool force_gravity_on) {
|
||||
if (!boids_active_) return;
|
||||
|
||||
boids_active_ = false;
|
||||
|
||||
// Activar gravedad al salir (si se especifica)
|
||||
if (force_gravity_on) {
|
||||
scene_mgr_->forceBallsGravityOn();
|
||||
}
|
||||
|
||||
// Mostrar notificación (solo si NO estamos en modo demo o logo)
|
||||
if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
ui_mgr_->showNotification("Modo Física");
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::toggleBoidsMode(bool force_gravity_on) {
|
||||
if (boids_active_) {
|
||||
deactivateBoids(force_gravity_on);
|
||||
} else {
|
||||
activateBoids();
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::update(float delta_time) {
|
||||
if (!boids_active_) return;
|
||||
|
||||
auto& balls = scene_mgr_->getBallsMutable();
|
||||
|
||||
// Aplicar las tres reglas de Reynolds a cada boid
|
||||
for (auto& ball : balls) {
|
||||
applySeparation(ball.get(), delta_time);
|
||||
applyAlignment(ball.get(), delta_time);
|
||||
applyCohesion(ball.get(), delta_time);
|
||||
applyBoundaries(ball.get());
|
||||
limitSpeed(ball.get());
|
||||
}
|
||||
|
||||
// Actualizar posiciones con velocidades resultantes
|
||||
for (auto& ball : balls) {
|
||||
float vx, vy;
|
||||
ball->getVelocity(vx, vy);
|
||||
|
||||
SDL_FRect pos = ball->getPosition();
|
||||
pos.x += vx;
|
||||
pos.y += vy;
|
||||
|
||||
ball->setPosition(pos.x, pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// REGLAS DE REYNOLDS (1987)
|
||||
// ============================================================================
|
||||
|
||||
void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
||||
// Regla 1: Separación - Evitar colisiones con vecinos cercanos
|
||||
float steer_x = 0.0f;
|
||||
float steer_y = 0.0f;
|
||||
int count = 0;
|
||||
|
||||
SDL_FRect pos = boid->getPosition();
|
||||
float center_x = pos.x + pos.w / 2.0f;
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
|
||||
const auto& balls = scene_mgr_->getBalls();
|
||||
for (const auto& other : balls) {
|
||||
if (other.get() == boid) continue; // Ignorar a sí mismo
|
||||
|
||||
SDL_FRect other_pos = other->getPosition();
|
||||
float other_x = other_pos.x + other_pos.w / 2.0f;
|
||||
float other_y = other_pos.y + other_pos.h / 2.0f;
|
||||
|
||||
float dx = center_x - other_x;
|
||||
float dy = center_y - other_y;
|
||||
float distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance > 0.0f && distance < BOID_SEPARATION_RADIUS) {
|
||||
// Vector normalizado apuntando lejos del vecino, ponderado por cercanía
|
||||
steer_x += (dx / distance) / distance;
|
||||
steer_y += (dy / distance) / distance;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
// Promedio
|
||||
steer_x /= count;
|
||||
steer_y /= count;
|
||||
|
||||
// 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;
|
||||
boid->setVelocity(vx, vy);
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
||||
// Regla 2: Alineación - Seguir dirección promedio del grupo
|
||||
float avg_vx = 0.0f;
|
||||
float avg_vy = 0.0f;
|
||||
int count = 0;
|
||||
|
||||
SDL_FRect pos = boid->getPosition();
|
||||
float center_x = pos.x + pos.w / 2.0f;
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
|
||||
const auto& balls = scene_mgr_->getBalls();
|
||||
for (const auto& other : balls) {
|
||||
if (other.get() == boid) continue;
|
||||
|
||||
SDL_FRect other_pos = other->getPosition();
|
||||
float other_x = other_pos.x + other_pos.w / 2.0f;
|
||||
float other_y = other_pos.y + other_pos.h / 2.0f;
|
||||
|
||||
float dx = center_x - other_x;
|
||||
float dy = center_y - other_y;
|
||||
float distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < BOID_ALIGNMENT_RADIUS) {
|
||||
float other_vx, other_vy;
|
||||
other->getVelocity(other_vx, other_vy);
|
||||
avg_vx += other_vx;
|
||||
avg_vy += other_vy;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
// Velocidad promedio del grupo
|
||||
avg_vx /= count;
|
||||
avg_vy /= count;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
vx += steer_x;
|
||||
vy += steer_y;
|
||||
boid->setVelocity(vx, vy);
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
||||
// Regla 3: Cohesión - Moverse hacia el centro de masa del grupo
|
||||
float center_of_mass_x = 0.0f;
|
||||
float center_of_mass_y = 0.0f;
|
||||
int count = 0;
|
||||
|
||||
SDL_FRect pos = boid->getPosition();
|
||||
float center_x = pos.x + pos.w / 2.0f;
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
|
||||
const auto& balls = scene_mgr_->getBalls();
|
||||
for (const auto& other : balls) {
|
||||
if (other.get() == boid) continue;
|
||||
|
||||
SDL_FRect other_pos = other->getPosition();
|
||||
float other_x = other_pos.x + other_pos.w / 2.0f;
|
||||
float other_y = other_pos.y + other_pos.h / 2.0f;
|
||||
|
||||
float dx = center_x - other_x;
|
||||
float dy = center_y - other_y;
|
||||
float distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < BOID_COHESION_RADIUS) {
|
||||
center_of_mass_x += other_x;
|
||||
center_of_mass_y += other_y;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
// Centro de masa del grupo
|
||||
center_of_mass_x /= count;
|
||||
center_of_mass_y /= count;
|
||||
|
||||
// Dirección hacia el centro
|
||||
float steer_x = (center_of_mass_x - center_x) * BOID_COHESION_WEIGHT * delta_time;
|
||||
float steer_y = (center_of_mass_y - center_y) * BOID_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;
|
||||
}
|
||||
|
||||
float vx, vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
vx += steer_x;
|
||||
vy += steer_y;
|
||||
boid->setVelocity(vx, vy);
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::applyBoundaries(Ball* boid) {
|
||||
// Mantener boids dentro de los límites de la pantalla
|
||||
// Comportamiento "wrapping" (teletransporte al otro lado)
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (wrapped) {
|
||||
boid->setPosition(pos.x, pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
void BoidManager::limitSpeed(Ball* boid) {
|
||||
// Limitar velocidad máxima del boid
|
||||
float vx, vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
|
||||
float speed = std::sqrt(vx * vx + vy * vy);
|
||||
if (speed > BOID_MAX_SPEED) {
|
||||
vx = (vx / speed) * BOID_MAX_SPEED;
|
||||
vy = (vy / speed) * BOID_MAX_SPEED;
|
||||
boid->setVelocity(vx, vy);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user