feat: Convertir BOIDS a sistema time-based (independiente de framerate)
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -57,9 +57,9 @@ void BoidManager::activateBoids() {
|
|||||||
float vx, vy;
|
float vx, vy;
|
||||||
ball->getVelocity(vx, vy);
|
ball->getVelocity(vx, vy);
|
||||||
if (vx == 0.0f && vy == 0.0f) {
|
if (vx == 0.0f && vy == 0.0f) {
|
||||||
// Velocidad aleatoria entre -1 y 1
|
// Velocidad aleatoria entre -60 y +60 px/s (time-based)
|
||||||
vx = (rand() % 200 - 100) / 100.0f;
|
vx = ((rand() % 200 - 100) / 100.0f) * 60.0f;
|
||||||
vy = (rand() % 200 - 100) / 100.0f;
|
vy = ((rand() % 200 - 100) / 100.0f) * 60.0f;
|
||||||
ball->setVelocity(vx, vy);
|
ball->setVelocity(vx, vy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,14 +118,14 @@ void BoidManager::update(float delta_time) {
|
|||||||
limitSpeed(ball.get());
|
limitSpeed(ball.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar posiciones con velocidades resultantes
|
// Actualizar posiciones con velocidades resultantes (time-based)
|
||||||
for (auto& ball : balls) {
|
for (auto& ball : balls) {
|
||||||
float vx, vy;
|
float vx, vy;
|
||||||
ball->getVelocity(vx, vy);
|
ball->getVelocity(vx, vy);
|
||||||
|
|
||||||
SDL_FRect pos = ball->getPosition();
|
SDL_FRect pos = ball->getPosition();
|
||||||
pos.x += vx;
|
pos.x += vx * delta_time; // time-based
|
||||||
pos.y += vy;
|
pos.y += vy * delta_time;
|
||||||
|
|
||||||
ball->setPosition(pos.x, pos.y);
|
ball->setPosition(pos.x, pos.y);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
constexpr int LOGO_FLIP_WAIT_PROBABILITY = 50; // 50% probabilidad de elegir el camino "esperar flip"
|
||||||
|
|
||||||
// Configuración de Modo BOIDS (comportamiento de enjambre)
|
// 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_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_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_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_SEPARATION_WEIGHT = 5400.0f; // Aceleración de separación (px/s²) [era 1.5 × 3600]
|
||||||
constexpr float BOID_ALIGNMENT_WEIGHT = 1.0f; // Peso de alineación
|
constexpr float BOID_ALIGNMENT_WEIGHT = 60.0f; // Steering de alineación (proporcional) [era 1.0 × 60]
|
||||||
constexpr float BOID_COHESION_WEIGHT = 0.001f; // Peso de cohesión (MICRO - 1000x menor por falta de normalización)
|
constexpr float BOID_COHESION_WEIGHT = 3.6f; // Aceleración de cohesión (px/s²) [era 0.001 × 3600]
|
||||||
constexpr float BOID_MAX_SPEED = 2.5f; // Velocidad máxima (píxeles/frame - REDUCIDA)
|
constexpr float BOID_MAX_SPEED = 150.0f; // Velocidad máxima (px/s) [era 2.5 × 60]
|
||||||
constexpr float BOID_MAX_FORCE = 0.05f; // Fuerza máxima de steering (REDUCIDA para evitar aceleración excesiva)
|
constexpr float BOID_MAX_FORCE = 3.0f; // Fuerza máxima de steering (px/s) [era 0.05 × 60]
|
||||||
constexpr float BOID_MIN_SPEED = 0.3f; // Velocidad mínima (evita boids estáticos)
|
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)
|
// 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)
|
constexpr float BOID_GRID_CELL_SIZE = 100.0f; // Tamaño de celda del grid (píxeles)
|
||||||
|
|||||||
@@ -364,18 +364,19 @@ void Engine::handleGravityToggle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Engine::handleGravityDirectionChange(GravityDirection direction, const char* notification_text) {
|
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) {
|
if (current_mode_ == SimulationMode::BOIDS) {
|
||||||
toggleBoidsMode(); // Esto cambia a PHYSICS y activa gravedad
|
current_mode_ = SimulationMode::PHYSICS;
|
||||||
// Continuar para aplicar la dirección de gravedad
|
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
|
// 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)
|
toggleShapeModeInternal(); // Desactivar figura (activa gravedad automáticamente)
|
||||||
} else {
|
} else {
|
||||||
scene_manager_->enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
scene_manager_->enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||||
}
|
}
|
||||||
|
|
||||||
scene_manager_->changeGravityDirection(direction);
|
scene_manager_->changeGravityDirection(direction);
|
||||||
showNotificationForAction(notification_text);
|
showNotificationForAction(notification_text);
|
||||||
}
|
}
|
||||||
@@ -437,9 +438,9 @@ void Engine::toggleDepthZoom() {
|
|||||||
// Boids (comportamiento de enjambre)
|
// Boids (comportamiento de enjambre)
|
||||||
void Engine::toggleBoidsMode() {
|
void Engine::toggleBoidsMode() {
|
||||||
if (current_mode_ == SimulationMode::BOIDS) {
|
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;
|
current_mode_ = SimulationMode::PHYSICS;
|
||||||
boid_manager_->deactivateBoids();
|
boid_manager_->deactivateBoids(false); // NO activar gravedad (preservar momentum)
|
||||||
} else {
|
} else {
|
||||||
// Entrar al modo boids (desde PHYSICS o SHAPE)
|
// Entrar al modo boids (desde PHYSICS o SHAPE)
|
||||||
if (current_mode_ == SimulationMode::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 valid_scenarios[] = {1, 2, 3, 4, 5};
|
||||||
int new_scenario = valid_scenarios[rand() % 5];
|
int new_scenario = valid_scenarios[rand() % 5];
|
||||||
scene_manager_->changeScenario(new_scenario, current_mode_);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1573,6 +1586,15 @@ void Engine::executeExitLogoMode() {
|
|||||||
clampShapeScale();
|
clampShapeScale();
|
||||||
generateShape();
|
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)
|
// Desactivar modo LOGO en PNG_SHAPE (volver a flip intervals normales)
|
||||||
if (active_shape_) {
|
if (active_shape_) {
|
||||||
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
||||||
|
|||||||
@@ -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
|
text_renderer_debug_->printAbsolute(margin, left_y, simmode_text.c_str(), {0, 255, 255, 255}); // Cian
|
||||||
left_y += line_height;
|
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
|
// V-Sync
|
||||||
text_renderer_debug_->printAbsolute(margin, left_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian
|
text_renderer_debug_->printAbsolute(margin, left_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian
|
||||||
left_y += line_height;
|
left_y += line_height;
|
||||||
|
|||||||
Reference in New Issue
Block a user