Implementar física de atracción con resorte en RotoBall

Reemplazar interpolación lineal artificial por sistema de fuerzas físicamente
realista usando Ley de Hooke con amortiguación variable.

## Cambios Principales

### Sistema de Física Implementado

**Fuerza de Resorte (Hooke's Law):**
```cpp
F_spring = k * (target - position)
F_damping = c * velocity
F_total = F_spring - F_damping
acceleration = F_total / mass
```

**Constantes (defines.h):**
- `ROTOBALL_SPRING_K = 300.0f`: Rigidez del resorte
- `ROTOBALL_DAMPING_BASE = 15.0f`: Amortiguación lejos del punto
- `ROTOBALL_DAMPING_NEAR = 50.0f`: Amortiguación cerca (estabilización)
- `ROTOBALL_NEAR_THRESHOLD = 5.0f`: Distancia considerada "cerca"
- `ROTOBALL_MAX_FORCE = 1000.0f`: Límite de seguridad

### Nuevas Funciones (Ball class)

- `enableRotoBallAttraction(bool)`: Activa/desactiva atracción física
- `applyRotoBallForce(target_x, target_y, deltaTime)`: Aplica fuerza de resorte

### Comportamiento Físico

**Al entrar (PHYSICS → ROTOBALL):**
1. Pelotas mantienen velocidad actual (vx, vy)
2. Fuerza de atracción las acelera hacia puntos en esfera rotante
3. Amortiguación variable evita oscilaciones infinitas
4. Convergen al punto con aceleración natural

**Durante RotoBall:**
- Punto destino rota constantemente
- Fuerza se recalcula cada frame hacia posición rotada
- Pelotas "persiguen" su punto móvil
- Efecto: Convergencia orgánica con ligera oscilación

**Al salir (ROTOBALL → PHYSICS):**
1. Atracción se desactiva
2. Pelotas conservan velocidad tangencial actual
3. Gravedad vuelve a aplicarse
4. Caen con la inercia que traían de la esfera

### Archivos Modificados

- `defines.h`: 5 nuevas constantes físicas
- `ball.h/cpp`: Sistema de resorte completo
- `engine.cpp`: Enable/disable atracción en toggle, updateRotoBall() usa física
- `CLAUDE.md`: Documentación técnica completa

## Ventajas del Sistema

 Física realista con conservación de momento
 Transición orgánica (no artificial)
 Inercia preservada entrada/salida
 Amortiguación automática (no oscila infinito)
 Constantes ajustables para tuning
 Performance: O(1) por pelota

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-03 13:24:07 +02:00
parent 22e3356f80
commit 91ab6487b3
5 changed files with 192 additions and 38 deletions

View File

@@ -861,6 +861,12 @@ void Engine::toggleRotoBallMode() {
// Generar esfera 3D
generateRotoBallSphere();
// Activar atracción física en todas las pelotas
// Las pelotas mantienen su velocidad actual y son atraídas hacia la esfera
for (auto& ball : balls_) {
ball->enableRotoBallAttraction(true);
}
// Mostrar texto informativo
text_ = "MODO ROTOBALL";
int text_width = static_cast<int>(text_.length() * 8);
@@ -868,11 +874,16 @@ void Engine::toggleRotoBallMode() {
text_init_time_ = SDL_GetTicks();
show_text_ = true;
} else {
// Volver a modo física
// Volver a modo física normal
current_mode_ = SimulationMode::PHYSICS;
rotoball_.transitioning = false;
rotoball_.transition_progress = 0.0f;
// Desactivar atracción - las pelotas conservan su velocidad tangencial actual
for (auto& ball : balls_) {
ball->enableRotoBallAttraction(false);
}
// Mostrar texto informativo
text_ = "MODO FISICA";
int text_width = static_cast<int>(text_.length() * 8);
@@ -916,20 +927,11 @@ void Engine::generateRotoBallSphere() {
}
}
// Actualizar esfera RotoBall (rotación + proyección)
// Actualizar esfera RotoBall con física de atracción
void Engine::updateRotoBall() {
if (current_mode_ != SimulationMode::ROTOBALL) return;
// Actualizar transición si está activa
if (rotoball_.transitioning) {
rotoball_.transition_progress += delta_time_ / ROTOBALL_TRANSITION_TIME;
if (rotoball_.transition_progress >= 1.0f) {
rotoball_.transition_progress = 1.0f;
rotoball_.transitioning = false;
}
}
// Actualizar ángulos de rotación
// Actualizar ángulos de rotación de la esfera
rotoball_.angle_y += ROTOBALL_ROTATION_SPEED_Y * delta_time_;
rotoball_.angle_x += ROTOBALL_ROTATION_SPEED_X * delta_time_;
@@ -937,15 +939,8 @@ void Engine::updateRotoBall() {
float center_x = current_screen_width_ / 2.0f;
float center_y = current_screen_height_ / 2.0f;
// Actualizar cada pelota
// Actualizar cada pelota con física de atracción
for (size_t i = 0; i < balls_.size(); i++) {
// Obtener posición 3D original (almacenada en generateRotoBallSphere)
SDL_FRect pos = balls_[i]->getPosition();
// Reconstruir coordenadas 3D originales desde los datos almacenados
// En generateRotoBallSphere guardamos: x, y, z en setRotoBallPosition3D
// Pero necesitamos acceder a esos datos... por ahora recalcularemos
// Recalcular posición 3D original usando Fibonacci sphere
int num_points = static_cast<int>(balls_.size());
const float golden_ratio = (1.0f + sqrtf(5.0f)) / 2.0f;
@@ -971,26 +966,16 @@ void Engine::updateRotoBall() {
float y_rot = y * cos_x - z_rot * sin_x;
float z_final = y * sin_x + z_rot * cos_x;
// Proyección 2D ortográfica
float screen_x = center_x + x_rot;
float screen_y = center_y + y_rot;
// Proyección 2D ortográfica (punto objetivo móvil)
float target_x = center_x + x_rot;
float target_y = center_y + y_rot;
// Calcular brillo según profundidad Z (normalizado 0-1)
// Aplicar fuerza de atracción física hacia el punto rotado
balls_[i]->applyRotoBallForce(target_x, target_y, delta_time_);
// Calcular brillo según profundidad Z para renderizado
float z_normalized = (z_final + ROTOBALL_RADIUS) / (2.0f * ROTOBALL_RADIUS);
z_normalized = std::max(0.0f, std::min(1.0f, z_normalized));
// Guardar brillo para usar en render
balls_[i]->setDepthBrightness(z_normalized);
// Transición suave desde posición actual a posición de esfera
if (rotoball_.transitioning) {
// Interpolar desde posición actual hacia posición de esfera
float lerp_x = pos.x + (screen_x - pos.x) * rotoball_.transition_progress;
float lerp_y = pos.y + (screen_y - pos.y) * rotoball_.transition_progress;
balls_[i]->setRotoBallScreenPosition(lerp_x, lerp_y);
} else {
// Ya en esfera, actualizar directamente
balls_[i]->setRotoBallScreenPosition(screen_x, screen_y);
}
}
}