From b93028396a924d5845dd4bf9cbd268a8d3dcb8b5 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 9 Oct 2025 11:01:41 +0200 Subject: [PATCH] Fix: Sistema de convergencia y flip timing en LOGO mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactoring semántico: - Renombrar rotoball_* → shape_* (variables y métodos) - Mejora legibilidad: aplica a todas las figuras 3D, no solo esfera Fixes críticos: - Fix convergencia: setShapeTarget2D() actualiza targets cada frame - Fix getDistanceToTarget(): siempre calcula distancia (sin guarda) - Fix lógica flip: destruir DURANTE flip N (no después de N flips) - Añadir display CONV en debug HUD (monitoreo convergencia) Mejoras timing: - Reducir PNG_IDLE_TIME_LOGO: 3-5s → 2-4s (flips más dinámicos) - Bajar CONVERGENCE_THRESHOLD: 0.8 → 0.4 (40% permite flips) Sistema flip-waiting (LOGO mode): - CAMINO A: Convergencia + tiempo (inmediato) - CAMINO B: Esperar 1-3 flips y destruir durante flip (20-80% progreso) - Tracking de flips con getFlipCount() y getFlipProgress() 🤖 Generated with Claude Code Co-Authored-By: Claude --- source/ball.cpp | 29 ++++---- source/ball.h | 28 +++---- source/defines.h | 11 ++- source/engine.cpp | 144 +++++++++++++++++++++++++++++++----- source/engine.h | 6 ++ source/shapes/png_shape.cpp | 44 ++++++++++- source/shapes/png_shape.h | 23 ++++++ source/shapes/shape.h | 5 ++ 8 files changed, 238 insertions(+), 52 deletions(-) diff --git a/source/ball.cpp b/source/ball.cpp index 82232b1..15c7884 100644 --- a/source/ball.cpp +++ b/source/ball.cpp @@ -42,7 +42,7 @@ Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr te // Coeficiente base IGUAL para todas las pelotas (solo variación por rebote individual) loss_ = BASE_BOUNCE_COEFFICIENT; // Coeficiente fijo para todas las pelotas - // Inicializar valores RotoBall + // Inicializar valores Shape (figuras 3D) pos_3d_x_ = 0.0f; pos_3d_y_ = 0.0f; pos_3d_z_ = 0.0f; @@ -50,7 +50,7 @@ Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr te target_y_ = pos_.y; depth_brightness_ = 1.0f; depth_scale_ = 1.0f; - rotoball_attraction_active_ = false; + shape_attraction_active_ = false; } // Actualiza la lógica de la clase @@ -267,19 +267,19 @@ void Ball::applyRandomLateralPush() { } } -// Funciones para modo RotoBall -void Ball::setRotoBallPosition3D(float x, float y, float z) { +// Funciones para modo Shape (figuras 3D) +void Ball::setShapePosition3D(float x, float y, float z) { pos_3d_x_ = x; pos_3d_y_ = y; pos_3d_z_ = z; } -void Ball::setRotoBallTarget2D(float x, float y) { +void Ball::setShapeTarget2D(float x, float y) { target_x_ = x; target_y_ = y; } -void Ball::setRotoBallScreenPosition(float x, float y) { +void Ball::setShapeScreenPosition(float x, float y) { pos_.x = x; pos_.y = y; sprite_->setPos({x, y}); @@ -293,9 +293,9 @@ void Ball::setDepthScale(float scale) { depth_scale_ = scale; } -// Activar/desactivar atracción física hacia esfera RotoBall -void Ball::enableRotoBallAttraction(bool enable) { - rotoball_attraction_active_ = enable; +// Activar/desactivar atracción física hacia figuras 3D +void Ball::enableShapeAttraction(bool enable) { + shape_attraction_active_ = enable; // Al activar atracción, resetear flags de superficie para permitir física completa if (enable) { @@ -305,18 +305,17 @@ void Ball::enableRotoBallAttraction(bool enable) { // Obtener distancia actual al punto objetivo (para calcular convergencia) float Ball::getDistanceToTarget() const { - if (!rotoball_attraction_active_) return 0.0f; - + // Siempre calcular distancia (útil para convergencia en LOGO mode) float dx = target_x_ - pos_.x; float dy = target_y_ - pos_.y; return sqrtf(dx * dx + dy * dy); } // Aplicar fuerza de resorte hacia punto objetivo en figuras 3D -void Ball::applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime, - float spring_k_base, float damping_base_base, float damping_near_base, - float near_threshold_base, float max_force_base) { - if (!rotoball_attraction_active_) return; +void Ball::applyShapeForce(float target_x, float target_y, float sphere_radius, float deltaTime, + float spring_k_base, float damping_base_base, float damping_near_base, + float near_threshold_base, float max_force_base) { + if (!shape_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 diff --git a/source/ball.h b/source/ball.h index 782c6b3..c5b80a6 100644 --- a/source/ball.h +++ b/source/ball.h @@ -22,12 +22,12 @@ class Ball { bool on_surface_; // Indica si la pelota est\u00e1 en la superficie (suelo/techo/pared) float loss_; // Coeficiente de rebote. Pérdida de energía en cada rebote - // Datos para modo RotoBall (esfera 3D) - float pos_3d_x_, pos_3d_y_, pos_3d_z_; // Posición 3D en la esfera + // Datos para modo Shape (figuras 3D) + float pos_3d_x_, pos_3d_y_, pos_3d_z_; // Posición 3D en la figura float target_x_, target_y_; // Posición destino 2D (proyección) float depth_brightness_; // Brillo según profundidad Z (0.0-1.0) float depth_scale_; // Escala según profundidad Z (0.5-1.5) - bool rotoball_attraction_active_; // ¿Está siendo atraída hacia la esfera? + bool shape_attraction_active_; // ¿Está siendo atraída hacia la figura? public: // Constructor @@ -80,22 +80,22 @@ class Ball { void updateSize(int new_size); // Actualizar tamaño de hitbox void setTexture(std::shared_ptr texture); // Cambiar textura del sprite - // Funciones para modo RotoBall - void setRotoBallPosition3D(float x, float y, float z); - void setRotoBallTarget2D(float x, float y); - void setRotoBallScreenPosition(float x, float y); // Establecer posición directa en pantalla + // Funciones para modo Shape (figuras 3D) + void setShapePosition3D(float x, float y, float z); + void setShapeTarget2D(float x, float y); + void setShapeScreenPosition(float x, float y); // Establecer posición directa en pantalla void setDepthBrightness(float brightness); float getDepthBrightness() const { return depth_brightness_; } void setDepthScale(float scale); float getDepthScale() const { return depth_scale_; } // Sistema de atracción física hacia figuras 3D - void enableRotoBallAttraction(bool enable); + void enableShapeAttraction(bool enable); float getDistanceToTarget() const; // Distancia actual al punto objetivo - void applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime, - float spring_k = SHAPE_SPRING_K, - float damping_base = SHAPE_DAMPING_BASE, - float damping_near = SHAPE_DAMPING_NEAR, - float near_threshold = SHAPE_NEAR_THRESHOLD, - float max_force = SHAPE_MAX_FORCE); + void applyShapeForce(float target_x, float target_y, float sphere_radius, float deltaTime, + float spring_k = SHAPE_SPRING_K, + float damping_base = SHAPE_DAMPING_BASE, + float damping_near = SHAPE_DAMPING_NEAR, + float near_threshold = SHAPE_NEAR_THRESHOLD, + float max_force = SHAPE_MAX_FORCE); }; \ No newline at end of file diff --git a/source/defines.h b/source/defines.h index 8ff0138..d3ca1b2 100644 --- a/source/defines.h +++ b/source/defines.h @@ -192,8 +192,8 @@ constexpr bool PNG_USE_EDGES_ONLY = false; // true = solo bordes, fals // Rotación "legible" (texto de frente con volteretas ocasionales) constexpr float PNG_IDLE_TIME_MIN = 0.5f; // Tiempo mínimo de frente (segundos) - modo MANUAL constexpr float PNG_IDLE_TIME_MAX = 2.0f; // Tiempo máximo de frente (segundos) - modo MANUAL -constexpr float PNG_IDLE_TIME_MIN_LOGO = 3.0f; // Tiempo mínimo de frente en LOGO MODE -constexpr float PNG_IDLE_TIME_MAX_LOGO = 5.0f; // Tiempo máximo de frente en LOGO MODE +constexpr float PNG_IDLE_TIME_MIN_LOGO = 2.0f; // Tiempo mínimo de frente en LOGO MODE +constexpr float PNG_IDLE_TIME_MAX_LOGO = 4.0f; // Tiempo máximo de frente en LOGO MODE constexpr float PNG_FLIP_SPEED = 3.0f; // Velocidad voltereta (rad/s) constexpr float PNG_FLIP_DURATION = 1.5f; // Duración voltereta (segundos) @@ -250,6 +250,13 @@ constexpr float LOGO_CONVERGENCE_DISTANCE = 20.0f; // Distancia (px) para consi constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO = 5; // 5% probabilidad en DEMO normal (más raro) constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO_LITE = 3; // 3% probabilidad en DEMO LITE (aún más raro) +// Sistema de espera de flips en LOGO MODE (camino alternativo de decisión) +constexpr int LOGO_FLIP_WAIT_MIN = 1; // Mínimo de flips a esperar antes de cambiar a PHYSICS +constexpr int LOGO_FLIP_WAIT_MAX = 3; // Máximo de flips a esperar +constexpr float LOGO_FLIP_TRIGGER_MIN = 0.20f; // 20% mínimo de progreso de flip para trigger +constexpr float LOGO_FLIP_TRIGGER_MAX = 0.80f; // 80% máximo de progreso de flip para trigger +constexpr int LOGO_FLIP_WAIT_PROBABILITY = 50; // 50% probabilidad de elegir el camino "esperar flip" + constexpr float PI = 3.14159265358979323846f; // Constante PI // Función auxiliar para obtener la ruta del directorio del ejecutable diff --git a/source/engine.cpp b/source/engine.cpp index 8295520..f4e2b04 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -823,6 +823,13 @@ void Engine::render() { } dbg_print(8, 72, mode_text.c_str(), 0, 255, 128); // Verde claro para modo + // Debug: Mostrar convergencia en modo LOGO (solo cuando está activo) + if (current_app_mode_ == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) { + int convergence_percent = static_cast(shape_convergence_ * 100.0f); + std::string convergence_text = "CONV " + std::to_string(convergence_percent); + dbg_print(8, 80, convergence_text.c_str(), 255, 128, 0); // Naranja para convergencia + } + // Debug: Mostrar modo DEMO/LOGO activo (siempre visible cuando debug está ON) if (current_app_mode_ == AppMode::LOGO) { int logo_text_width = 9 * 8; // "LOGO MODE" = 9 caracteres × 8 píxeles @@ -1831,12 +1838,43 @@ void Engine::updateDemoMode() { bool should_trigger = false; if (current_app_mode_ == AppMode::LOGO) { - // LOGO MODE: Esperar convergencia + tiempo mínimo (o timeout máximo) - bool min_time_reached = demo_timer_ >= logo_min_time_; - bool max_time_reached = demo_timer_ >= logo_max_time_; - bool convergence_ok = shape_convergence_ >= logo_convergence_threshold_; + // LOGO MODE: Dos caminos posibles + if (logo_waiting_for_flip_) { + // CAMINO B: Esperando a que ocurran flips + // Obtener referencia a PNGShape si está activa + PNGShape* png_shape = nullptr; + if (active_shape_ && current_mode_ == SimulationMode::SHAPE) { + png_shape = dynamic_cast(active_shape_.get()); + } - should_trigger = (min_time_reached && convergence_ok) || max_time_reached; + if (png_shape) { + int current_flip_count = png_shape->getFlipCount(); + + // Detectar nuevo flip completado + if (current_flip_count > logo_current_flip_count_) { + logo_current_flip_count_ = current_flip_count; + } + + // Si estamos EN o DESPUÉS del flip objetivo + // +1 porque queremos actuar DURANTE el flip N, no después de completarlo + if (logo_current_flip_count_ + 1 >= logo_target_flip_number_) { + // Monitorear progreso del flip actual + if (png_shape->isFlipping()) { + float flip_progress = png_shape->getFlipProgress(); + if (flip_progress >= logo_target_flip_percentage_) { + should_trigger = true; // ¡Trigger durante el flip! + } + } + } + } + } else { + // CAMINO A: Esperar convergencia + tiempo (comportamiento original) + bool min_time_reached = demo_timer_ >= logo_min_time_; + bool max_time_reached = demo_timer_ >= logo_max_time_; + bool convergence_ok = shape_convergence_ >= logo_convergence_threshold_; + + should_trigger = (min_time_reached && convergence_ok) || max_time_reached; + } } else { // DEMO/DEMO_LITE: Timer simple como antes should_trigger = demo_timer_ >= demo_next_action_time_; @@ -1850,19 +1888,71 @@ void Engine::updateDemoMode() { int action = rand() % 100; if (current_mode_ == SimulationMode::SHAPE) { - // Logo quieto (formado) → 2 opciones posibles - if (action < 50) { - // 50%: SHAPE → PHYSICS con gravedad ON (caída dramática) - toggleShapeMode(true); + // Logo quieto (formado) → Decidir camino a seguir + + // DECISIÓN BIFURCADA: ¿Cambio inmediato o esperar flips? + if (logo_waiting_for_flip_) { + // Ya estábamos esperando flips, y se disparó el trigger + // → Hacer el cambio SHAPE → PHYSICS ahora (durante el flip) + if (action < 50) { + toggleShapeMode(true); // Con gravedad ON + } else { + toggleShapeMode(false); // Con gravedad OFF + } + + // Resetear variables de espera de flips + logo_waiting_for_flip_ = false; + logo_current_flip_count_ = 0; + + // Resetear timer + demo_timer_ = 0.0f; + float interval_range = logo_max_time_ - logo_min_time_; + demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range; + } else if (rand() % 100 < LOGO_FLIP_WAIT_PROBABILITY) { + // CAMINO B (50%): Esperar a que ocurran 1-3 flips + logo_waiting_for_flip_ = true; + logo_target_flip_number_ = LOGO_FLIP_WAIT_MIN + rand() % (LOGO_FLIP_WAIT_MAX - LOGO_FLIP_WAIT_MIN + 1); + logo_target_flip_percentage_ = LOGO_FLIP_TRIGGER_MIN + (rand() % 1000) / 1000.0f * (LOGO_FLIP_TRIGGER_MAX - LOGO_FLIP_TRIGGER_MIN); + logo_current_flip_count_ = 0; + + // Resetear contador de flips en PNGShape + if (active_shape_) { + PNGShape* png_shape = dynamic_cast(active_shape_.get()); + if (png_shape) { + png_shape->resetFlipCount(); + } + } + + // NO hacer nada más este frame - esperar a que ocurran los flips + // El trigger se ejecutará en futuras iteraciones cuando se cumplan las condiciones } else { - // 50%: SHAPE → PHYSICS con gravedad OFF (dar vueltas sin caer) - toggleShapeMode(false); + // CAMINO A (50%): Cambio inmediato + if (action < 50) { + // 50%: SHAPE → PHYSICS con gravedad ON (caída dramática) + toggleShapeMode(true); + } else { + // 50%: SHAPE → PHYSICS con gravedad OFF (dar vueltas sin caer) + toggleShapeMode(false); + } + + // Resetear variables de espera de flips al cambiar a PHYSICS + logo_waiting_for_flip_ = false; + logo_current_flip_count_ = 0; + + // Resetear timer con intervalos escalados + demo_timer_ = 0.0f; + float interval_range = logo_max_time_ - logo_min_time_; + demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range; } } else { // Logo animado (PHYSICS) → 3 opciones posibles if (action < 60) { // 60%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones) toggleShapeMode(false); + + // Resetear variables de espera de flips al volver a SHAPE + logo_waiting_for_flip_ = false; + logo_current_flip_count_ = 0; } else if (action < 80) { // 20%: Forzar gravedad ON (empezar a caer mientras da vueltas) forceBallsGravityOn(); @@ -1870,12 +1960,12 @@ void Engine::updateDemoMode() { // 20%: Forzar gravedad OFF (flotar mientras da vueltas) forceBallsGravityOff(); } - } - // Resetear timer con intervalos escalados (logo_min_time_ y logo_max_time_) - demo_timer_ = 0.0f; - float interval_range = logo_max_time_ - logo_min_time_; - demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range; + // Resetear timer con intervalos escalados + demo_timer_ = 0.0f; + float interval_range = logo_max_time_ - logo_min_time_; + demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range; + } // Solo salir automáticamente si NO llegamos desde MANUAL // Probabilidad de salir: 60% en cada acción → sale rápido (relación DEMO:LOGO = 6:1) @@ -2224,9 +2314,16 @@ void Engine::enterLogoMode(bool from_demo) { PNGShape* png_shape = dynamic_cast(active_shape_.get()); if (png_shape) { png_shape->setLogoMode(true); + png_shape->resetFlipCount(); // Resetear contador de flips } } + // Resetear variables de espera de flips + logo_waiting_for_flip_ = false; + logo_target_flip_number_ = 0; + logo_target_flip_percentage_ = 0.0f; + logo_current_flip_count_ = 0; + // Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente) setState(AppMode::LOGO); } @@ -2321,7 +2418,7 @@ void Engine::updateBallSizes(int old_size, int new_size) { // Si ajustamos posición, aplicarla ahora if (ball->isOnSurface()) { - ball->setRotoBallScreenPosition(pos.x, pos.y); + ball->setShapeScreenPosition(pos.x, pos.y); } } } @@ -2389,7 +2486,7 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) { // Desactivar atracción y resetear escala de profundidad for (auto& ball : balls_) { - ball->enableRotoBallAttraction(false); + ball->enableShapeAttraction(false); ball->setDepthScale(1.0f); // Reset escala a 100% (evita "pop" visual) } @@ -2460,7 +2557,7 @@ void Engine::activateShape(ShapeType type) { // Activar atracción física en todas las pelotas for (auto& ball : balls_) { - ball->enableRotoBallAttraction(true); + ball->enableShapeAttraction(true); } // Mostrar texto informativo con nombre de figura (solo si NO estamos en modo demo o logo) @@ -2510,10 +2607,13 @@ void Engine::updateShape() { float target_x = center_x + x_3d; float target_y = center_y + y_3d; + // Actualizar target de la pelota para cálculo de convergencia + balls_[i]->setShapeTarget2D(target_x, target_y); + // Aplicar fuerza de atracción física hacia el punto rotado // Usar constantes SHAPE (mayor pegajosidad que ROTOBALL) float shape_size = scale_factor * 80.0f; // 80px = radio base - balls_[i]->applyRotoBallForce(target_x, target_y, shape_size, delta_time_, SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR, SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE); + balls_[i]->applyShapeForce(target_x, target_y, shape_size, delta_time_, SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR, SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE); // Calcular brillo según profundidad Z para renderizado // Normalizar Z al rango de la figura (asumiendo simetría ±shape_size) @@ -2539,6 +2639,10 @@ void Engine::updateShape() { } shape_convergence_ = static_cast(balls_near) / balls_.size(); + + // Notificar a la figura sobre el porcentaje de convergencia + // Esto permite que PNGShape decida cuándo empezar a contar para flips + active_shape_->setConvergence(shape_convergence_); } } diff --git a/source/engine.h b/source/engine.h index ba117c0..7b0f6c3 100644 --- a/source/engine.h +++ b/source/engine.h @@ -128,6 +128,12 @@ class Engine { float logo_min_time_ = 3.0f; // Tiempo mínimo escalado con resolución float logo_max_time_ = 5.0f; // Tiempo máximo escalado (backup) + // Sistema de espera de flips en LOGO MODE (camino alternativo) + bool logo_waiting_for_flip_ = false; // true si eligió el camino "esperar flip" + int logo_target_flip_number_ = 0; // En qué flip actuar (1, 2 o 3) + float logo_target_flip_percentage_ = 0.0f; // % de flip a esperar (0.2-0.8) + int logo_current_flip_count_ = 0; // Flips observados hasta ahora + // Estado previo antes de entrar a Logo Mode (para restaurar al salir) ColorTheme logo_previous_theme_ = ColorTheme::SUNSET; size_t logo_previous_texture_index_ = 0; diff --git a/source/shapes/png_shape.cpp b/source/shapes/png_shape.cpp index 2084a4b..99530d4 100644 --- a/source/shapes/png_shape.cpp +++ b/source/shapes/png_shape.cpp @@ -268,7 +268,15 @@ std::vector PNGShape::extractCornerVertices(const std::vector void PNGShape::update(float delta_time, float screen_width, float screen_height) { if (!is_flipping_) { // Estado IDLE: texto de frente con pivoteo sutil - idle_timer_ += delta_time; + + // Solo contar tiempo para flips si: + // - NO está en modo LOGO, O + // - Está en modo LOGO Y ha alcanzado umbral de convergencia (80%) + bool can_start_flip = !is_logo_mode_ || convergence_threshold_reached_; + + if (can_start_flip) { + idle_timer_ += delta_time; + } // Pivoteo sutil constante (movimiento orgánico) tilt_x_ += 0.4f * delta_time; // Velocidad sutil en X @@ -308,6 +316,12 @@ void PNGShape::update(float delta_time, float screen_width, float screen_height) angle_y_ = 0.0f; } } + + // Detectar transición de flip (de true a false) para incrementar contador + if (was_flipping_last_frame_ && !is_flipping_) { + flip_count_++; // Flip completado + } + was_flipping_last_frame_ = is_flipping_; } void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const { @@ -387,3 +401,31 @@ float PNGShape::getScaleFactor(float screen_height) const { // Escala dinámica según resolución return PNG_SIZE_FACTOR; } + +// Sistema de convergencia: notificar a la figura sobre el % de pelotas en posición +void PNGShape::setConvergence(float convergence) { + current_convergence_ = convergence; + + // Umbral de convergencia + constexpr float CONVERGENCE_THRESHOLD = 0.4f; + + // Activar threshold cuando convergencia supera el umbral + if (!convergence_threshold_reached_ && convergence >= CONVERGENCE_THRESHOLD) { + convergence_threshold_reached_ = true; + } + + // Desactivar threshold cuando convergencia cae por debajo del umbral + if (convergence < CONVERGENCE_THRESHOLD) { + convergence_threshold_reached_ = false; + } +} + +// Obtener progreso del flip actual (0.0 = inicio del flip, 1.0 = fin del flip) +float PNGShape::getFlipProgress() const { + if (!is_flipping_) { + return 0.0f; // No está flipping, progreso = 0 + } + + // Calcular progreso normalizado (0.0 - 1.0) + return flip_timer_ / PNG_FLIP_DURATION; +} diff --git a/source/shapes/png_shape.h b/source/shapes/png_shape.h index ac3fb5b..c431e59 100644 --- a/source/shapes/png_shape.h +++ b/source/shapes/png_shape.h @@ -43,6 +43,14 @@ private: // Modo LOGO (intervalos de flip más largos) bool is_logo_mode_ = false; // true = usar intervalos LOGO (más lentos) + // Sistema de convergencia (solo relevante en modo LOGO) + float current_convergence_ = 0.0f; // Porcentaje actual de convergencia (0.0-1.0) + bool convergence_threshold_reached_ = false; // true si ha alcanzado umbral mínimo (80%) + + // Sistema de tracking de flips (para modo LOGO - espera de flips) + int flip_count_ = 0; // Contador de flips completados (reset al entrar a LOGO) + bool was_flipping_last_frame_ = false; // Estado previo para detectar transiciones + // Dimensiones normalizadas float scale_factor_ = 1.0f; float center_offset_x_ = 0.0f; @@ -70,6 +78,18 @@ public: const char* getName() const override { return "PNG SHAPE"; } float getScaleFactor(float screen_height) const override; + // Consultar estado de flip + bool isFlipping() const { return is_flipping_; } + + // Obtener progreso del flip actual (0.0 = inicio, 1.0 = fin) + float getFlipProgress() const; + + // Obtener número de flips completados (para modo LOGO) + int getFlipCount() const { return flip_count_; } + + // Resetear contador de flips (llamar al entrar a LOGO MODE) + void resetFlipCount() { flip_count_ = 0; was_flipping_last_frame_ = false; } + // Control de modo LOGO (flip intervals más largos) void setLogoMode(bool enable) { is_logo_mode_ = enable; @@ -78,4 +98,7 @@ public: float idle_max = enable ? PNG_IDLE_TIME_MAX_LOGO : PNG_IDLE_TIME_MAX; next_idle_time_ = idle_min + (rand() % 1000) / 1000.0f * (idle_max - idle_min); } + + // Sistema de convergencia (override de Shape::setConvergence) + void setConvergence(float convergence) override; }; diff --git a/source/shapes/shape.h b/source/shapes/shape.h index 836c93d..7ce132a 100644 --- a/source/shapes/shape.h +++ b/source/shapes/shape.h @@ -27,4 +27,9 @@ public: // screen_height: altura actual de pantalla // Retorna: factor multiplicador para constantes de física (spring_k, damping, etc.) virtual float getScaleFactor(float screen_height) const = 0; + + // Notificar a la figura sobre el porcentaje de convergencia (pelotas cerca del objetivo) + // convergence: valor de 0.0 (0%) a 1.0 (100%) indicando cuántas pelotas están en posición + // Default: no-op (la mayoría de figuras no necesitan esta información) + virtual void setConvergence(float convergence) {} };