Hacer RotoBall totalmente escalable con resolución de pantalla

Problema:
- Radio fijo de 80px funcionaba bien en 320x240
- En F4 fullscreen (1920x1080), radio de 360px era correcto visualmente
- PERO las fuerzas físicas (spring_k, damping) seguían siendo para 80px
- Resultado: pelotas nunca llegaban a pegarse en resoluciones altas

Solución:
1. Radio proporcional a altura de pantalla (ROTOBALL_RADIUS_FACTOR = 0.333)
2. Escalar TODAS las constantes de física proporcionalmente al radio
3. Fórmula: scale = sphere_radius / BASE_RADIUS (80px)

Cambios técnicos:
- defines.h: ROTOBALL_RADIUS → ROTOBALL_RADIUS_FACTOR (0.333)
- engine.cpp: Calcular radius dinámicamente en generate/update
- ball.h: applyRotoBallForce() ahora recibe sphere_radius
- ball.cpp: Escalar spring_k, damping_base, damping_near, near_threshold, max_force

Resultado:
- 320x240: Radio 80px, scale=1.0 (idéntico a antes)
- 640x480: Radio 160px, scale=2.0 (fuerzas 2x)
- 1920x1080: Radio 360px, scale=4.5 (fuerzas 4.5x)

Comportamiento físico IDÉNTICO en todas las resoluciones 

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-03 19:48:52 +02:00
parent 535c397be2
commit b196683e4a
4 changed files with 39 additions and 21 deletions

View File

@@ -309,9 +309,21 @@ void Ball::enableRotoBallAttraction(bool enable) {
}
// Aplicar fuerza de resorte hacia punto objetivo en esfera rotante
void Ball::applyRotoBallForce(float target_x, float target_y, float deltaTime) {
void Ball::applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime) {
if (!rotoball_attraction_active_) return;
// Calcular factor de escala basado en el radio (radio base = 80px)
// Si radius=80 → scale=1.0, si radius=160 → scale=2.0, si radius=360 → scale=4.5
const float BASE_RADIUS = 80.0f;
float scale = sphere_radius / BASE_RADIUS;
// Escalar constantes de física proporcionalmente
float spring_k = ROTOBALL_SPRING_K * scale;
float damping_base = ROTOBALL_DAMPING_BASE * scale;
float damping_near = ROTOBALL_DAMPING_NEAR * scale;
float near_threshold = ROTOBALL_NEAR_THRESHOLD * scale;
float max_force = ROTOBALL_MAX_FORCE * scale;
// Calcular vector diferencia (dirección hacia el target)
float diff_x = target_x - pos_.x;
float diff_y = target_y - pos_.y;
@@ -320,13 +332,13 @@ void Ball::applyRotoBallForce(float target_x, float target_y, float deltaTime) {
float distance = sqrtf(diff_x * diff_x + diff_y * diff_y);
// Fuerza de resorte (Ley de Hooke: F = -k * x)
float spring_force_x = ROTOBALL_SPRING_K * diff_x;
float spring_force_y = ROTOBALL_SPRING_K * diff_y;
float spring_force_x = spring_k * diff_x;
float spring_force_y = spring_k * diff_y;
// Amortiguación variable: más cerca del punto = más amortiguación (estabilización)
float damping = (distance < ROTOBALL_NEAR_THRESHOLD)
? ROTOBALL_DAMPING_NEAR
: ROTOBALL_DAMPING_BASE;
float damping = (distance < near_threshold)
? damping_near
: damping_base;
// Fuerza de amortiguación (proporcional a la velocidad)
float damping_force_x = damping * vx_;
@@ -338,10 +350,10 @@ void Ball::applyRotoBallForce(float target_x, float target_y, float deltaTime) {
// Limitar magnitud de fuerza (evitar explosiones numéricas)
float force_magnitude = sqrtf(total_force_x * total_force_x + total_force_y * total_force_y);
if (force_magnitude > ROTOBALL_MAX_FORCE) {
float scale = ROTOBALL_MAX_FORCE / force_magnitude;
total_force_x *= scale;
total_force_y *= scale;
if (force_magnitude > max_force) {
float scale_limit = max_force / force_magnitude;
total_force_x *= scale_limit;
total_force_y *= scale_limit;
}
// Aplicar aceleración (F = ma, asumiendo m = 1 para simplificar)

View File

@@ -85,5 +85,5 @@ class Ball {
// Sistema de atracción física hacia esfera RotoBall
void enableRotoBallAttraction(bool enable);
void applyRotoBallForce(float target_x, float target_y, float deltaTime);
void applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime);
};

View File

@@ -65,7 +65,7 @@ enum class SimulationMode {
};
// Configuración de RotoBall (esfera 3D rotante)
constexpr float ROTOBALL_RADIUS = 80.0f; // Radio de la esfera (píxeles)
constexpr float ROTOBALL_RADIUS_FACTOR = 0.333f; // Radio como proporción de altura de pantalla (80/240 ≈ 0.333)
constexpr float ROTOBALL_ROTATION_SPEED_Y = 1.5f; // Velocidad rotación eje Y (rad/s)
constexpr float ROTOBALL_ROTATION_SPEED_X = 0.8f; // Velocidad rotación eje X (rad/s)
constexpr float ROTOBALL_TRANSITION_TIME = 1.5f; // Tiempo de transición (segundos)

View File

@@ -980,6 +980,9 @@ void Engine::generateRotoBallSphere() {
int num_points = static_cast<int>(balls_.size());
if (num_points == 0) return;
// Calcular radio dinámico proporcional a la altura de pantalla
float radius = current_screen_height_ * ROTOBALL_RADIUS_FACTOR;
// Constante Golden Ratio para Fibonacci sphere
const float golden_ratio = (1.0f + sqrtf(5.0f)) / 2.0f;
const float angle_increment = PI * 2.0f * golden_ratio;
@@ -991,9 +994,9 @@ void Engine::generateRotoBallSphere() {
float theta = angle_increment * static_cast<float>(i); // Longitud
// Convertir coordenadas esféricas a cartesianas
float x = cosf(theta) * sinf(phi) * ROTOBALL_RADIUS;
float y = sinf(theta) * sinf(phi) * ROTOBALL_RADIUS;
float z = cosf(phi) * ROTOBALL_RADIUS;
float x = cosf(theta) * sinf(phi) * radius;
float y = sinf(theta) * sinf(phi) * radius;
float z = cosf(phi) * radius;
// Guardar posición 3D en la pelota
balls_[i]->setRotoBallPosition3D(x, y, z);
@@ -1004,7 +1007,7 @@ void Engine::generateRotoBallSphere() {
balls_[i]->setRotoBallTarget2D(center_x + x, center_y + y);
// Calcular brillo inicial según profundidad Z
float z_normalized = (z + ROTOBALL_RADIUS) / (2.0f * ROTOBALL_RADIUS);
float z_normalized = (z + radius) / (2.0f * radius);
balls_[i]->setDepthBrightness(z_normalized);
}
}
@@ -1013,6 +1016,9 @@ void Engine::generateRotoBallSphere() {
void Engine::updateRotoBall() {
if (current_mode_ != SimulationMode::ROTOBALL) return;
// Calcular radio dinámico proporcional a la altura de pantalla
float radius = current_screen_height_ * ROTOBALL_RADIUS_FACTOR;
// 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_;
@@ -1032,9 +1038,9 @@ void Engine::updateRotoBall() {
float phi = acosf(1.0f - 2.0f * t);
float theta = angle_increment * static_cast<float>(i);
float x = cosf(theta) * sinf(phi) * ROTOBALL_RADIUS;
float y = sinf(theta) * sinf(phi) * ROTOBALL_RADIUS;
float z = cosf(phi) * ROTOBALL_RADIUS;
float x = cosf(theta) * sinf(phi) * radius;
float y = sinf(theta) * sinf(phi) * radius;
float z = cosf(phi) * radius;
// Aplicar rotación en eje Y
float cos_y = cosf(rotoball_.angle_y);
@@ -1053,10 +1059,10 @@ void Engine::updateRotoBall() {
float target_y = center_y + y_rot;
// Aplicar fuerza de atracción física hacia el punto rotado
balls_[i]->applyRotoBallForce(target_x, target_y, delta_time_);
balls_[i]->applyRotoBallForce(target_x, target_y, radius, delta_time_);
// Calcular brillo según profundidad Z para renderizado
float z_normalized = (z_final + ROTOBALL_RADIUS) / (2.0f * ROTOBALL_RADIUS);
float z_normalized = (z_final + radius) / (2.0f * radius);
z_normalized = std::max(0.0f, std::min(1.0f, z_normalized));
balls_[i]->setDepthBrightness(z_normalized);
}