Files
vibe3_physics/BOIDS_ROADMAP.md
Sergio Valor b73e77e9bc Boids Fase 1: Corregir bug de clustering crítico
PROBLEMA RESUELTO:
Los boids colapsaban al mismo punto dentro de cada grupo, haciendo
el sistema visualmente inutilizable.

CAMBIOS IMPLEMENTADOS:

1. BOIDS_ROADMAP.md creado (NEW FILE)
   - Roadmap completo de 6 fases para mejora de boids
   - Diagnóstico detallado de problemas actuales
   - Plan de implementación con métricas de éxito
   - Fase 1 (crítica): Fix clustering
   - Fase 2 (alto impacto): Spatial Hash Grid O(n²)→O(n)
   - Fases 3-6: Mejoras visuales, comportamientos avanzados

2. defines.h - Rebalanceo de parámetros (Fase 1.1)
   - BOID_SEPARATION_RADIUS: 30→25px
   - BOID_COHESION_RADIUS: 80→60px (REDUCIDO 25%)
   - BOID_SEPARATION_WEIGHT: 1.5→3.0 (TRIPLICADO)
   - BOID_COHESION_WEIGHT: 0.8→0.5 (REDUCIDO 37%)
   - BOID_MAX_FORCE: 0.1→0.5 (QUINTUPLICADO)
   - BOID_MIN_SPEED: 0.5 (NUEVO - evita boids estáticos)

3. boid_manager.cpp - Mejoras físicas
   - Fase 1.2: Velocidad mínima en limitSpeed()
     * Evita boids completamente estáticos
     * Mantiene movimiento continuo
   - Fase 1.3: Fuerza de separación proporcional a cercanía
     * Antes: dividir por distance² (muy débil)
     * Ahora: proporcional a (RADIUS - distance) / RADIUS
     * Resultado: 100% fuerza en colisión, 0% en radio máximo

RESULTADO ESPERADO:
 Separación domina sobre cohesión (peso 3.0 vs 0.5)
 Boids mantienen distancia personal (~10-15px)
 Sin colapso a puntos únicos
 Movimiento continuo sin boids estáticos

PRÓXIMOS PASOS:
- Testing manual con 100, 1000 boids
- Validar comportamiento disperso sin clustering
- Fase 2: Spatial Hash Grid para rendimiento O(n)

Estado: Compilación exitosa, listo para testing
Rama: boids_development

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 22:04:20 +02:00

19 KiB
Raw Blame History

BOIDS ROADMAP - Plan de Mejora Completo

Proyecto: ViBe3 Physics - Sistema de Boids (Flocking Behavior) Fecha de creación: 2025-01-XX Estado actual: Implementación básica funcional pero con problemas críticos


📊 Diagnóstico de Problemas Actuales

🔴 CRÍTICO: Bug de Clustering (Colapso a Punto Único)

Problema observado:

  • Los boids se agrupan correctamente en grupos separados
  • PERO dentro de cada grupo, todos colapsan al mismo punto exacto
  • Las pelotas se superponen completamente, formando una "masa" sin espacio entre ellas

Causa raíz identificada:

  1. Desbalance de fuerzas: Cohesión (80px radio) domina sobre Separación (30px radio)
  2. Aplicación de fuerzas: Se aplican fuerzas cada frame sin velocidad mínima
  3. Fuerza máxima muy baja: BOID_MAX_FORCE = 0.1 es insuficiente para separación efectiva
  4. Sin velocidad mínima: Los boids pueden quedarse completamente estáticos (vx=0, vy=0)

Impacto: Sistema de boids inutilizable visualmente


🔴 CRÍTICO: Rendimiento O(n²) Inaceptable

Problema observado:

  • 100 boids: ~60 FPS
  • 1,000 boids: ~15-20 FPS (caída del 70%)
  • 5,000+ boids: < 5 FPS (completamente inutilizable)

Causa raíz identificada:

// Cada boid revisa TODOS los demás boids (3 veces: separation, alignment, cohesion)
for (auto& boid : balls) {
    applySeparation(boid);   // O(n) - itera todos los balls
    applyAlignment(boid);     // O(n) - itera todos los balls
    applyCohesion(boid);      // O(n) - itera todos los balls
}
// Complejidad total: O(n²) × 3 = O(3n²)

Cálculos de complejidad:

  • 100 boids: 100 × 100 × 3 = 30,000 checks/frame
  • 1,000 boids: 1,000 × 1,000 × 3 = 3,000,000 checks/frame (100x más lento)
  • 10,000 boids: 10,000 × 10,000 × 3 = 300,000,000 checks/frame (imposible)

Impacto: No escalable más allá de ~500 boids


🟡 MEDIO: Comportamiento Visual Pobre

Problemas identificados:

  1. Sin variedad visual: Todos los boids idénticos (mismo tamaño, color)
  2. Movimiento robótico: Steering demasiado directo, sin suavizado
  3. Wrapping abrupto: Teletransporte visible rompe inmersión
  4. Sin personalidad: Todos los boids se comportan idénticamente

Impacto: Resultado visual poco interesante y repetitivo


🎯 Plan de Fases de Mejora


FASE 1: Fix Clustering Bug (CRÍTICO) ⚠️

Objetivo: Eliminar el colapso a punto único, mantener grupos dispersos

Prioridad: CRÍTICA Tiempo estimado: 2-3 horas Complejidad: Baja (ajustes de parámetros + lógica mínima)

Cambios a Implementar

1.1 Rebalanceo de Radios y Pesos

Problema actual:

// defines.h - VALORES ACTUALES (INCORRECTOS)
BOID_SEPARATION_RADIUS = 30.0f;    // Radio muy pequeño
BOID_ALIGNMENT_RADIUS = 50.0f;
BOID_COHESION_RADIUS = 80.0f;      // Radio muy grande (domina)
BOID_SEPARATION_WEIGHT = 1.5f;     // Peso insuficiente
BOID_ALIGNMENT_WEIGHT = 1.0f;
BOID_COHESION_WEIGHT = 0.8f;
BOID_MAX_FORCE = 0.1f;             // Fuerza máxima muy débil
BOID_MAX_SPEED = 3.0f;

Solución propuesta:

// defines.h - VALORES CORREGIDOS
BOID_SEPARATION_RADIUS = 25.0f;    // Radio pequeño pero suficiente
BOID_ALIGNMENT_RADIUS = 40.0f;
BOID_COHESION_RADIUS = 60.0f;      // Reducido (menos dominante)
BOID_SEPARATION_WEIGHT = 3.0f;     // TRIPLICADO (alta prioridad)
BOID_ALIGNMENT_WEIGHT = 1.0f;      // Sin cambios
BOID_COHESION_WEIGHT = 0.5f;       // REDUCIDO a la mitad
BOID_MAX_FORCE = 0.5f;             // QUINTUPLICADO (permite reacción rápida)
BOID_MAX_SPEED = 3.0f;             // Sin cambios
BOID_MIN_SPEED = 0.5f;             // NUEVO: velocidad mínima

Justificación:

  • Separation dominante: Evita colapso con peso 3x mayor
  • Cohesion reducida: Radio 60px (antes 80px) + peso 0.5 (antes 0.8)
  • Max force aumentada: Permite correcciones rápidas
  • Min speed añadida: Evita boids estáticos

1.2 Implementar Velocidad Mínima

Archivo: source/boids_mgr/boid_manager.cpp

Añadir al final de limitSpeed():

void BoidManager::limitSpeed(Ball* boid) {
    float vx, vy;
    boid->getVelocity(vx, vy);

    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;
        boid->setVelocity(vx, vy);
    }

    // NUEVO: 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;
        boid->setVelocity(vx, vy);
    }
}

1.3 Mejorar Aplicación de Fuerza de Separación

Problema actual: Separación se divide por distancia² (muy débil cuando cerca)

Archivo: source/boids_mgr/boid_manager.cpp::applySeparation()

Cambio:

// ANTES (línea 145):
steer_x += (dx / distance) / distance;  // Dividir por distance² hace fuerza muy débil
steer_y += (dy / distance) / distance;

// DESPUÉS:
// Separación más fuerte cuando más cerca (inversa de distancia, no cuadrado)
float separation_strength = (BOID_SEPARATION_RADIUS - distance) / BOID_SEPARATION_RADIUS;
steer_x += (dx / distance) * separation_strength;
steer_y += (dy / distance) * separation_strength;

Justificación: Fuerza de separación ahora es proporcional a cercanía (0% en radio máximo, 100% en colisión)

Testing de Fase 1

Checklist de validación:

  • Con 100 boids: Grupos visibles con espacio entre boids individuales
  • Con 1000 boids: Sin colapso a puntos únicos
  • Ningún boid completamente estático (velocidad > 0.5)
  • Distancia mínima entre boids vecinos: ~10-15px
  • FPS con 1000 boids: ~15-20 FPS (sin mejorar, pero funcional)

Criterio de éxito: Los boids mantienen distancia personal dentro de grupos sin colapsar


FASE 2: Spatial Hash Grid (ALTO IMPACTO) 🚀

Objetivo: O(n²) → O(n) mediante optimización espacial

Prioridad: ALTA Tiempo estimado: 4-6 horas Complejidad: Media (nueva estructura de datos)

Concepto: Spatial Hash Grid

Problema actual:

Cada boid revisa TODOS los demás boids
→ 1000 boids × 1000 checks = 1,000,000 comparaciones

Solución:

Dividir espacio en grid de celdas
Cada boid solo revisa boids en celdas vecinas (3×3 = 9 celdas)
→ 1000 boids × ~10 vecinos = 10,000 comparaciones (100x más rápido)

Implementación

2.1 Crear Estructura de Spatial Grid

Nuevo archivo: source/boids_mgr/spatial_grid.h

#pragma once
#include <vector>
#include <unordered_map>

class Ball;

// Clase para optimización espacial de búsqueda de vecinos
class SpatialGrid {
public:
    SpatialGrid(int screen_width, int screen_height, float cell_size);

    void clear();
    void insert(Ball* boid);
    std::vector<Ball*> getNearby(Ball* boid, float radius);

private:
    int screen_width_;
    int screen_height_;
    float cell_size_;
    int grid_width_;
    int grid_height_;

    // Hash map: cell_id → vector de boids en esa celda
    std::unordered_map<int, std::vector<Ball*>> grid_;

    int getCellId(float x, float y) const;
    void getCellCoords(int cell_id, int& cx, int& cy) const;
};

Nuevo archivo: source/boids_mgr/spatial_grid.cpp

#include "spatial_grid.h"
#include "../ball.h"
#include <cmath>

SpatialGrid::SpatialGrid(int screen_width, int screen_height, float cell_size)
    : screen_width_(screen_width)
    , screen_height_(screen_height)
    , cell_size_(cell_size)
    , grid_width_(static_cast<int>(std::ceil(screen_width / cell_size)))
    , grid_height_(static_cast<int>(std::ceil(screen_height / cell_size))) {
}

void SpatialGrid::clear() {
    grid_.clear();
}

void SpatialGrid::insert(Ball* boid) {
    SDL_FRect pos = boid->getPosition();
    float center_x = pos.x + pos.w / 2.0f;
    float center_y = pos.y + pos.h / 2.0f;

    int cell_id = getCellId(center_x, center_y);
    grid_[cell_id].push_back(boid);
}

std::vector<Ball*> SpatialGrid::getNearby(Ball* boid, float radius) {
    std::vector<Ball*> nearby;

    SDL_FRect pos = boid->getPosition();
    float center_x = pos.x + pos.w / 2.0f;
    float center_y = pos.y + pos.h / 2.0f;

    // Calcular rango de celdas a revisar (3x3 en el peor caso)
    int min_cx = static_cast<int>((center_x - radius) / cell_size_);
    int max_cx = static_cast<int>((center_x + radius) / cell_size_);
    int min_cy = static_cast<int>((center_y - radius) / cell_size_);
    int max_cy = static_cast<int>((center_y + radius) / cell_size_);

    // Clamp a límites de grid
    min_cx = std::max(0, min_cx);
    max_cx = std::min(grid_width_ - 1, max_cx);
    min_cy = std::max(0, min_cy);
    max_cy = std::min(grid_height_ - 1, max_cy);

    // Recopilar boids de celdas vecinas
    for (int cy = min_cy; cy <= max_cy; ++cy) {
        for (int cx = min_cx; cx <= max_cx; ++cx) {
            int cell_id = cy * grid_width_ + cx;
            auto it = grid_.find(cell_id);
            if (it != grid_.end()) {
                for (Ball* other : it->second) {
                    if (other != boid) {
                        nearby.push_back(other);
                    }
                }
            }
        }
    }

    return nearby;
}

int SpatialGrid::getCellId(float x, float y) const {
    int cx = static_cast<int>(x / cell_size_);
    int cy = static_cast<int>(y / cell_size_);
    cx = std::max(0, std::min(grid_width_ - 1, cx));
    cy = std::max(0, std::min(grid_height_ - 1, cy));
    return cy * grid_width_ + cx;
}

void SpatialGrid::getCellCoords(int cell_id, int& cx, int& cy) const {
    cx = cell_id % grid_width_;
    cy = cell_id / grid_width_;
}

2.2 Integrar SpatialGrid en BoidManager

Archivo: source/boids_mgr/boid_manager.h

#include "spatial_grid.h"

class BoidManager {
private:
    // ... miembros existentes ...
    std::unique_ptr<SpatialGrid> spatial_grid_;  // NUEVO
};

Archivo: source/boids_mgr/boid_manager.cpp

Modificar initialize():

void BoidManager::initialize(...) {
    // ... código existente ...

    // Crear spatial grid con tamaño de celda = radio máximo de búsqueda
    float max_radius = std::max({BOID_SEPARATION_RADIUS, BOID_ALIGNMENT_RADIUS, BOID_COHESION_RADIUS});
    spatial_grid_ = std::make_unique<SpatialGrid>(screen_width, screen_height, max_radius);
}

Modificar update():

void BoidManager::update(float delta_time) {
    if (!boids_active_) return;

    auto& balls = scene_mgr_->getBallsMutable();

    // NUEVO: Reconstruir spatial grid cada frame
    spatial_grid_->clear();
    for (auto& ball : balls) {
        spatial_grid_->insert(ball.get());
    }

    // Aplicar reglas (ahora con grid optimizado)
    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());
    }

    // ... resto del código ...
}

Modificar applySeparation(), applyAlignment(), applyCohesion():

ANTES:

const auto& balls = scene_mgr_->getBalls();
for (const auto& other : balls) {  // O(n) - itera TODOS

DESPUÉS:

// O(1) amortizado - solo vecinos cercanos
auto nearby = spatial_grid_->getNearby(boid, BOID_SEPARATION_RADIUS);
for (Ball* other : nearby) {  // Solo ~10-50 boids

Testing de Fase 2

Métricas de rendimiento esperadas:

Cantidad Boids FPS Antes FPS Después Mejora
100 60 60 1x (sin cambio)
1,000 15-20 60+ 3-4x
5,000 <5 40-50 10x+
10,000 <1 20-30 30x+
50,000 imposible 5-10 funcional

Checklist de validación:

  • FPS con 1000 boids: >50 FPS
  • FPS con 5000 boids: >30 FPS
  • FPS con 10000 boids: >15 FPS
  • Comportamiento visual idéntico a Fase 1
  • Sin boids "perdidos" (todos actualizados correctamente)

Criterio de éxito: Mejora de rendimiento 10x+ para 5000+ boids


FASE 3: Mejoras Visuales y de Comportamiento 🎨

Objetivo: Hacer el comportamiento más interesante y natural

Prioridad: MEDIA Tiempo estimado: 3-4 horas Complejidad: Baja-Media

3.1 Variedad Visual por Boid

Añadir propiedades individuales:

// En ball.h (si no existen ya)
struct BoidProperties {
    float size_scale;      // 0.8-1.2 (variación de tamaño)
    float speed_factor;    // 0.9-1.1 (algunos más rápidos)
    Color original_color;  // Color base individual
};

Aplicar al activar boids:

  • Tamaños variados (80%-120% del tamaño base)
  • Velocidades máximas ligeramente diferentes
  • Colores con variación de tinte

3.2 Steering Suavizado

Problema: Fuerzas aplicadas directamente causan movimiento robótico

Solución: Interpolación exponencial (smoothing)

// Aplicar smooth steering
float smooth_factor = 0.3f;  // 0-1 (menor = más suave)
vx += steer_x * smooth_factor;
vy += steer_y * smooth_factor;

3.3 Boundaries Suaves (Soft Wrapping)

Problema actual: Teletransporte abrupto visible

Solución: "Avoid edges" behavior

void BoidManager::applyEdgeAvoidance(Ball* boid, float delta_time) {
    SDL_FRect pos = boid->getPosition();
    float center_x = pos.x + pos.w / 2.0f;
    float center_y = pos.y + pos.h / 2.0f;

    float margin = 50.0f;  // Margen de detección de borde
    float turn_force = 0.5f;

    float steer_x = 0.0f, steer_y = 0.0f;

    if (center_x < margin) steer_x += turn_force;
    if (center_x > screen_width_ - margin) steer_x -= turn_force;
    if (center_y < margin) steer_y += turn_force;
    if (center_y > screen_height_ - margin) steer_y -= turn_force;

    if (steer_x != 0.0f || steer_y != 0.0f) {
        float vx, vy;
        boid->getVelocity(vx, vy);
        vx += steer_x * delta_time;
        vy += steer_y * delta_time;
        boid->setVelocity(vx, vy);
    }
}

Testing de Fase 3

Checklist de validación:

  • Boids con tamaños variados visibles
  • Movimiento más orgánico y fluido
  • Giros en bordes de pantalla suaves (no teletransporte)
  • Variación de colores perceptible

FASE 4: Comportamientos Avanzados 🎮

Objetivo: Añadir interactividad y dinámicas interesantes

Prioridad: BAJA (opcional) Tiempo estimado: 4-6 horas Complejidad: Media-Alta

4.1 Obstacle Avoidance (Ratón)

Funcionalidad:

  • Mouse position actúa como "predador"
  • Boids huyen del cursor en un radio de 100px

Implementación:

void BoidManager::applyMouseAvoidance(Ball* boid, int mouse_x, int mouse_y) {
    SDL_FRect pos = boid->getPosition();
    float center_x = pos.x + pos.w / 2.0f;
    float center_y = pos.y + pos.h / 2.0f;

    float dx = center_x - mouse_x;
    float dy = center_y - mouse_y;
    float distance = std::sqrt(dx * dx + dy * dy);

    const float AVOID_RADIUS = 100.0f;
    const float AVOID_STRENGTH = 2.0f;

    if (distance < AVOID_RADIUS && distance > 0.0f) {
        float flee_x = (dx / distance) * AVOID_STRENGTH;
        float flee_y = (dy / distance) * AVOID_STRENGTH;

        float vx, vy;
        boid->getVelocity(vx, vy);
        vx += flee_x;
        vy += flee_y;
        boid->setVelocity(vx, vy);
    }
}

4.2 Predator/Prey Dynamics

Concepto:

  • 10% de boids son "predadores" (color rojo)
  • 90% son "presas" (color normal)
  • Predadores persiguen presas
  • Presas huyen de predadores

4.3 Leader Following

Concepto:

  • Un boid aleatorio es designado "líder"
  • Otros boids tienen peso adicional hacia el líder
  • El líder se mueve con input del usuario (teclas WASD)

FASE 5: Optimizaciones Avanzadas

Objetivo: Rendimiento extremo para 50K+ boids

Prioridad: MUY BAJA (solo si necesario) Tiempo estimado: 8-12 horas Complejidad: Alta

5.1 Multi-threading (Parallel Processing)

Concepto: Dividir trabajo entre múltiples hilos CPU

Complejidad: Alta (requiere thread-safety, atomic ops, etc.)

5.2 SIMD Vectorization

Concepto: Procesar 4-8 boids simultáneamente con instrucciones SSE/AVX

Complejidad: Muy Alta (requiere conocimiento de intrinsics)

5.3 GPU Compute Shaders

Concepto: Mover toda la física de boids a GPU

Complejidad: Extrema (requiere OpenGL compute o Vulkan)


FASE 6: Integración y Pulido

Objetivo: Integrar boids con sistemas existentes

Prioridad: MEDIA Tiempo estimado: 2-3 horas Complejidad: Baja

6.1 Integración con Modo DEMO

Añadir boids al repertorio de acciones aleatorias:

// En defines.h
constexpr int DEMO_WEIGHT_BOIDS = 8;  // 8% probabilidad de activar boids

// En state_manager.cpp
case Action::ACTIVATE_BOIDS:
    engine_->toggleBoidsMode();
    break;

6.2 Debug Visualization

Funcionalidad: Tecla "H" muestra overlay de debug:

  • Radios de separación/alignment/cohesion (círculos)
  • Vectores de velocidad (flechas)
  • Spatial grid (líneas de celdas)
  • ID de boid y vecinos

6.3 Configuración Runtime

Sistema de "presets" de comportamiento:

  • Preset 1: "Tight Flocks" (cohesión alta)
  • Preset 2: "Loose Swarms" (separación alta)
  • Preset 3: "Chaotic" (todos los pesos bajos)
  • Preset 4: "Fast" (velocidad alta)

Controles:

  • Numpad 1-4 (en modo boids) para cambiar preset
  • Shift+Numpad +/- para ajustar parámetros en vivo

📈 Métricas de Éxito del Roadmap Completo

Funcionalidad

  • Sin clustering (grupos dispersos correctamente)
  • Comportamiento natural y orgánico
  • Transiciones suaves (no teletransporte visible)

Rendimiento

  • 1,000 boids: >50 FPS
  • 5,000 boids: >30 FPS
  • 10,000 boids: >15 FPS

Visual

  • Variedad perceptible entre boids
  • Movimiento fluido y dinámico
  • Efectos visuales opcionales funcionales

Integración

  • Compatible con modo DEMO
  • Debug overlay útil y claro
  • Configuración runtime funcional

🔧 Orden de Implementación Recomendado

Mínimo Viable (MVP)

  1. FASE 1 (CRÍTICO) - Fix clustering
  2. FASE 2 (ALTO) - Spatial grid

Resultado: Boids funcionales y performantes para 1K-5K boids

Producto Completo

  1. FASE 3 (MEDIO) - Mejoras visuales
  2. FASE 6 (MEDIO) - Integración y debug

Resultado: Experiencia pulida y profesional

Opcional (Si hay tiempo)

  1. FASE 4 (BAJO) - Comportamientos avanzados
  2. FASE 5 (MUY BAJO) - Optimizaciones extremas

📝 Notas de Implementación

Archivos a Modificar (Fase 1-2)

  • source/defines.h - Constantes de boids
  • source/boids_mgr/boid_manager.h - Header del manager
  • source/boids_mgr/boid_manager.cpp - Implementación
  • source/boids_mgr/spatial_grid.h - NUEVO archivo
  • source/boids_mgr/spatial_grid.cpp - NUEVO archivo
  • CMakeLists.txt - Sin cambios (glob ya incluye boids_mgr/*.cpp)

Estrategia de Testing

  1. Compilar después de cada cambio
  2. Probar con 100 boids primero (debug rápido)
  3. Escalar a 1000, 5000, 10000 (validar rendimiento)
  4. Usar modo debug (tecla H) para visualizar parámetros

Compatibilidad con Sistema Actual

  • No interfiere con modo PHYSICS
  • No interfiere con modo SHAPE
  • Compatible con todos los temas
  • Compatible con cambio de resolución
  • Compatible con modo DEMO/LOGO

FIN DEL ROADMAP

Documento vivo - Se actualizará según avance la implementación