Fix: Sistema de convergencia y flip timing en LOGO mode
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 <noreply@anthropic.com>
This commit is contained in:
@@ -42,7 +42,7 @@ Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> te
|
|||||||
// Coeficiente base IGUAL para todas las pelotas (solo variación por rebote individual)
|
// Coeficiente base IGUAL para todas las pelotas (solo variación por rebote individual)
|
||||||
loss_ = BASE_BOUNCE_COEFFICIENT; // Coeficiente fijo para todas las pelotas
|
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_x_ = 0.0f;
|
||||||
pos_3d_y_ = 0.0f;
|
pos_3d_y_ = 0.0f;
|
||||||
pos_3d_z_ = 0.0f;
|
pos_3d_z_ = 0.0f;
|
||||||
@@ -50,7 +50,7 @@ Ball::Ball(float x, float vx, float vy, Color color, std::shared_ptr<Texture> te
|
|||||||
target_y_ = pos_.y;
|
target_y_ = pos_.y;
|
||||||
depth_brightness_ = 1.0f;
|
depth_brightness_ = 1.0f;
|
||||||
depth_scale_ = 1.0f;
|
depth_scale_ = 1.0f;
|
||||||
rotoball_attraction_active_ = false;
|
shape_attraction_active_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza la lógica de la clase
|
// Actualiza la lógica de la clase
|
||||||
@@ -267,19 +267,19 @@ void Ball::applyRandomLateralPush() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funciones para modo RotoBall
|
// Funciones para modo Shape (figuras 3D)
|
||||||
void Ball::setRotoBallPosition3D(float x, float y, float z) {
|
void Ball::setShapePosition3D(float x, float y, float z) {
|
||||||
pos_3d_x_ = x;
|
pos_3d_x_ = x;
|
||||||
pos_3d_y_ = y;
|
pos_3d_y_ = y;
|
||||||
pos_3d_z_ = z;
|
pos_3d_z_ = z;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ball::setRotoBallTarget2D(float x, float y) {
|
void Ball::setShapeTarget2D(float x, float y) {
|
||||||
target_x_ = x;
|
target_x_ = x;
|
||||||
target_y_ = y;
|
target_y_ = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ball::setRotoBallScreenPosition(float x, float y) {
|
void Ball::setShapeScreenPosition(float x, float y) {
|
||||||
pos_.x = x;
|
pos_.x = x;
|
||||||
pos_.y = y;
|
pos_.y = y;
|
||||||
sprite_->setPos({x, y});
|
sprite_->setPos({x, y});
|
||||||
@@ -293,9 +293,9 @@ void Ball::setDepthScale(float scale) {
|
|||||||
depth_scale_ = scale;
|
depth_scale_ = scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activar/desactivar atracción física hacia esfera RotoBall
|
// Activar/desactivar atracción física hacia figuras 3D
|
||||||
void Ball::enableRotoBallAttraction(bool enable) {
|
void Ball::enableShapeAttraction(bool enable) {
|
||||||
rotoball_attraction_active_ = enable;
|
shape_attraction_active_ = enable;
|
||||||
|
|
||||||
// Al activar atracción, resetear flags de superficie para permitir física completa
|
// Al activar atracción, resetear flags de superficie para permitir física completa
|
||||||
if (enable) {
|
if (enable) {
|
||||||
@@ -305,18 +305,17 @@ void Ball::enableRotoBallAttraction(bool enable) {
|
|||||||
|
|
||||||
// Obtener distancia actual al punto objetivo (para calcular convergencia)
|
// Obtener distancia actual al punto objetivo (para calcular convergencia)
|
||||||
float Ball::getDistanceToTarget() const {
|
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 dx = target_x_ - pos_.x;
|
||||||
float dy = target_y_ - pos_.y;
|
float dy = target_y_ - pos_.y;
|
||||||
return sqrtf(dx * dx + dy * dy);
|
return sqrtf(dx * dx + dy * dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aplicar fuerza de resorte hacia punto objetivo en figuras 3D
|
// Aplicar fuerza de resorte hacia punto objetivo en figuras 3D
|
||||||
void Ball::applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime,
|
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 spring_k_base, float damping_base_base, float damping_near_base,
|
||||||
float near_threshold_base, float max_force_base) {
|
float near_threshold_base, float max_force_base) {
|
||||||
if (!rotoball_attraction_active_) return;
|
if (!shape_attraction_active_) return;
|
||||||
|
|
||||||
// Calcular factor de escala basado en el radio (radio base = 80px)
|
// 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
|
// Si radius=80 → scale=1.0, si radius=160 → scale=2.0, si radius=360 → scale=4.5
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ class Ball {
|
|||||||
bool on_surface_; // Indica si la pelota est\u00e1 en la superficie (suelo/techo/pared)
|
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
|
float loss_; // Coeficiente de rebote. Pérdida de energía en cada rebote
|
||||||
|
|
||||||
// Datos para modo RotoBall (esfera 3D)
|
// Datos para modo Shape (figuras 3D)
|
||||||
float pos_3d_x_, pos_3d_y_, pos_3d_z_; // Posición 3D en la esfera
|
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 target_x_, target_y_; // Posición destino 2D (proyección)
|
||||||
float depth_brightness_; // Brillo según profundidad Z (0.0-1.0)
|
float depth_brightness_; // Brillo según profundidad Z (0.0-1.0)
|
||||||
float depth_scale_; // Escala según profundidad Z (0.5-1.5)
|
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:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
@@ -80,22 +80,22 @@ class Ball {
|
|||||||
void updateSize(int new_size); // Actualizar tamaño de hitbox
|
void updateSize(int new_size); // Actualizar tamaño de hitbox
|
||||||
void setTexture(std::shared_ptr<Texture> texture); // Cambiar textura del sprite
|
void setTexture(std::shared_ptr<Texture> texture); // Cambiar textura del sprite
|
||||||
|
|
||||||
// Funciones para modo RotoBall
|
// Funciones para modo Shape (figuras 3D)
|
||||||
void setRotoBallPosition3D(float x, float y, float z);
|
void setShapePosition3D(float x, float y, float z);
|
||||||
void setRotoBallTarget2D(float x, float y);
|
void setShapeTarget2D(float x, float y);
|
||||||
void setRotoBallScreenPosition(float x, float y); // Establecer posición directa en pantalla
|
void setShapeScreenPosition(float x, float y); // Establecer posición directa en pantalla
|
||||||
void setDepthBrightness(float brightness);
|
void setDepthBrightness(float brightness);
|
||||||
float getDepthBrightness() const { return depth_brightness_; }
|
float getDepthBrightness() const { return depth_brightness_; }
|
||||||
void setDepthScale(float scale);
|
void setDepthScale(float scale);
|
||||||
float getDepthScale() const { return depth_scale_; }
|
float getDepthScale() const { return depth_scale_; }
|
||||||
|
|
||||||
// Sistema de atracción física hacia figuras 3D
|
// 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
|
float getDistanceToTarget() const; // Distancia actual al punto objetivo
|
||||||
void applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime,
|
void applyShapeForce(float target_x, float target_y, float sphere_radius, float deltaTime,
|
||||||
float spring_k = SHAPE_SPRING_K,
|
float spring_k = SHAPE_SPRING_K,
|
||||||
float damping_base = SHAPE_DAMPING_BASE,
|
float damping_base = SHAPE_DAMPING_BASE,
|
||||||
float damping_near = SHAPE_DAMPING_NEAR,
|
float damping_near = SHAPE_DAMPING_NEAR,
|
||||||
float near_threshold = SHAPE_NEAR_THRESHOLD,
|
float near_threshold = SHAPE_NEAR_THRESHOLD,
|
||||||
float max_force = SHAPE_MAX_FORCE);
|
float max_force = SHAPE_MAX_FORCE);
|
||||||
};
|
};
|
||||||
@@ -192,8 +192,8 @@ constexpr bool PNG_USE_EDGES_ONLY = false; // true = solo bordes, fals
|
|||||||
// Rotación "legible" (texto de frente con volteretas ocasionales)
|
// 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_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_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_MIN_LOGO = 2.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_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_SPEED = 3.0f; // Velocidad voltereta (rad/s)
|
||||||
constexpr float PNG_FLIP_DURATION = 1.5f; // Duración voltereta (segundos)
|
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 = 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)
|
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
|
constexpr float PI = 3.14159265358979323846f; // Constante PI
|
||||||
|
|
||||||
// Función auxiliar para obtener la ruta del directorio del ejecutable
|
// Función auxiliar para obtener la ruta del directorio del ejecutable
|
||||||
|
|||||||
@@ -823,6 +823,13 @@ void Engine::render() {
|
|||||||
}
|
}
|
||||||
dbg_print(8, 72, mode_text.c_str(), 0, 255, 128); // Verde claro para modo
|
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<int>(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)
|
// Debug: Mostrar modo DEMO/LOGO activo (siempre visible cuando debug está ON)
|
||||||
if (current_app_mode_ == AppMode::LOGO) {
|
if (current_app_mode_ == AppMode::LOGO) {
|
||||||
int logo_text_width = 9 * 8; // "LOGO MODE" = 9 caracteres × 8 píxeles
|
int logo_text_width = 9 * 8; // "LOGO MODE" = 9 caracteres × 8 píxeles
|
||||||
@@ -1831,12 +1838,43 @@ void Engine::updateDemoMode() {
|
|||||||
bool should_trigger = false;
|
bool should_trigger = false;
|
||||||
|
|
||||||
if (current_app_mode_ == AppMode::LOGO) {
|
if (current_app_mode_ == AppMode::LOGO) {
|
||||||
// LOGO MODE: Esperar convergencia + tiempo mínimo (o timeout máximo)
|
// LOGO MODE: Dos caminos posibles
|
||||||
bool min_time_reached = demo_timer_ >= logo_min_time_;
|
if (logo_waiting_for_flip_) {
|
||||||
bool max_time_reached = demo_timer_ >= logo_max_time_;
|
// CAMINO B: Esperando a que ocurran flips
|
||||||
bool convergence_ok = shape_convergence_ >= logo_convergence_threshold_;
|
// Obtener referencia a PNGShape si está activa
|
||||||
|
PNGShape* png_shape = nullptr;
|
||||||
|
if (active_shape_ && current_mode_ == SimulationMode::SHAPE) {
|
||||||
|
png_shape = dynamic_cast<PNGShape*>(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 {
|
} else {
|
||||||
// DEMO/DEMO_LITE: Timer simple como antes
|
// DEMO/DEMO_LITE: Timer simple como antes
|
||||||
should_trigger = demo_timer_ >= demo_next_action_time_;
|
should_trigger = demo_timer_ >= demo_next_action_time_;
|
||||||
@@ -1850,19 +1888,71 @@ void Engine::updateDemoMode() {
|
|||||||
int action = rand() % 100;
|
int action = rand() % 100;
|
||||||
|
|
||||||
if (current_mode_ == SimulationMode::SHAPE) {
|
if (current_mode_ == SimulationMode::SHAPE) {
|
||||||
// Logo quieto (formado) → 2 opciones posibles
|
// Logo quieto (formado) → Decidir camino a seguir
|
||||||
if (action < 50) {
|
|
||||||
// 50%: SHAPE → PHYSICS con gravedad ON (caída dramática)
|
// DECISIÓN BIFURCADA: ¿Cambio inmediato o esperar flips?
|
||||||
toggleShapeMode(true);
|
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<PNGShape*>(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 {
|
} else {
|
||||||
// 50%: SHAPE → PHYSICS con gravedad OFF (dar vueltas sin caer)
|
// CAMINO A (50%): Cambio inmediato
|
||||||
toggleShapeMode(false);
|
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 {
|
} else {
|
||||||
// Logo animado (PHYSICS) → 3 opciones posibles
|
// Logo animado (PHYSICS) → 3 opciones posibles
|
||||||
if (action < 60) {
|
if (action < 60) {
|
||||||
// 60%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
|
// 60%: PHYSICS → SHAPE (reconstruir logo y ver rotaciones)
|
||||||
toggleShapeMode(false);
|
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) {
|
} else if (action < 80) {
|
||||||
// 20%: Forzar gravedad ON (empezar a caer mientras da vueltas)
|
// 20%: Forzar gravedad ON (empezar a caer mientras da vueltas)
|
||||||
forceBallsGravityOn();
|
forceBallsGravityOn();
|
||||||
@@ -1870,12 +1960,12 @@ void Engine::updateDemoMode() {
|
|||||||
// 20%: Forzar gravedad OFF (flotar mientras da vueltas)
|
// 20%: Forzar gravedad OFF (flotar mientras da vueltas)
|
||||||
forceBallsGravityOff();
|
forceBallsGravityOff();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Resetear timer con intervalos escalados (logo_min_time_ y logo_max_time_)
|
// Resetear timer con intervalos escalados
|
||||||
demo_timer_ = 0.0f;
|
demo_timer_ = 0.0f;
|
||||||
float interval_range = logo_max_time_ - logo_min_time_;
|
float interval_range = logo_max_time_ - logo_min_time_;
|
||||||
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
|
demo_next_action_time_ = logo_min_time_ + (rand() % 1000) / 1000.0f * interval_range;
|
||||||
|
}
|
||||||
|
|
||||||
// Solo salir automáticamente si NO llegamos desde MANUAL
|
// 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)
|
// 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<PNGShape*>(active_shape_.get());
|
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
||||||
if (png_shape) {
|
if (png_shape) {
|
||||||
png_shape->setLogoMode(true);
|
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)
|
// Cambiar a modo LOGO (guarda previous_app_mode_ automáticamente)
|
||||||
setState(AppMode::LOGO);
|
setState(AppMode::LOGO);
|
||||||
}
|
}
|
||||||
@@ -2321,7 +2418,7 @@ void Engine::updateBallSizes(int old_size, int new_size) {
|
|||||||
|
|
||||||
// Si ajustamos posición, aplicarla ahora
|
// Si ajustamos posición, aplicarla ahora
|
||||||
if (ball->isOnSurface()) {
|
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
|
// Desactivar atracción y resetear escala de profundidad
|
||||||
for (auto& ball : balls_) {
|
for (auto& ball : balls_) {
|
||||||
ball->enableRotoBallAttraction(false);
|
ball->enableShapeAttraction(false);
|
||||||
ball->setDepthScale(1.0f); // Reset escala a 100% (evita "pop" visual)
|
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
|
// Activar atracción física en todas las pelotas
|
||||||
for (auto& ball : balls_) {
|
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)
|
// 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_x = center_x + x_3d;
|
||||||
float target_y = center_y + y_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
|
// Aplicar fuerza de atracción física hacia el punto rotado
|
||||||
// Usar constantes SHAPE (mayor pegajosidad que ROTOBALL)
|
// Usar constantes SHAPE (mayor pegajosidad que ROTOBALL)
|
||||||
float shape_size = scale_factor * 80.0f; // 80px = radio base
|
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
|
// Calcular brillo según profundidad Z para renderizado
|
||||||
// Normalizar Z al rango de la figura (asumiendo simetría ±shape_size)
|
// Normalizar Z al rango de la figura (asumiendo simetría ±shape_size)
|
||||||
@@ -2539,6 +2639,10 @@ void Engine::updateShape() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shape_convergence_ = static_cast<float>(balls_near) / balls_.size();
|
shape_convergence_ = static_cast<float>(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_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ class Engine {
|
|||||||
float logo_min_time_ = 3.0f; // Tiempo mínimo escalado con resolución
|
float logo_min_time_ = 3.0f; // Tiempo mínimo escalado con resolución
|
||||||
float logo_max_time_ = 5.0f; // Tiempo máximo escalado (backup)
|
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)
|
// Estado previo antes de entrar a Logo Mode (para restaurar al salir)
|
||||||
ColorTheme logo_previous_theme_ = ColorTheme::SUNSET;
|
ColorTheme logo_previous_theme_ = ColorTheme::SUNSET;
|
||||||
size_t logo_previous_texture_index_ = 0;
|
size_t logo_previous_texture_index_ = 0;
|
||||||
|
|||||||
@@ -268,7 +268,15 @@ std::vector<PNGShape::Point2D> PNGShape::extractCornerVertices(const std::vector
|
|||||||
void PNGShape::update(float delta_time, float screen_width, float screen_height) {
|
void PNGShape::update(float delta_time, float screen_width, float screen_height) {
|
||||||
if (!is_flipping_) {
|
if (!is_flipping_) {
|
||||||
// Estado IDLE: texto de frente con pivoteo sutil
|
// 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)
|
// Pivoteo sutil constante (movimiento orgánico)
|
||||||
tilt_x_ += 0.4f * delta_time; // Velocidad sutil en X
|
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;
|
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 {
|
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
|
// Escala dinámica según resolución
|
||||||
return PNG_SIZE_FACTOR;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ private:
|
|||||||
// Modo LOGO (intervalos de flip más largos)
|
// Modo LOGO (intervalos de flip más largos)
|
||||||
bool is_logo_mode_ = false; // true = usar intervalos LOGO (más lentos)
|
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
|
// Dimensiones normalizadas
|
||||||
float scale_factor_ = 1.0f;
|
float scale_factor_ = 1.0f;
|
||||||
float center_offset_x_ = 0.0f;
|
float center_offset_x_ = 0.0f;
|
||||||
@@ -70,6 +78,18 @@ public:
|
|||||||
const char* getName() const override { return "PNG SHAPE"; }
|
const char* getName() const override { return "PNG SHAPE"; }
|
||||||
float getScaleFactor(float screen_height) const override;
|
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)
|
// Control de modo LOGO (flip intervals más largos)
|
||||||
void setLogoMode(bool enable) {
|
void setLogoMode(bool enable) {
|
||||||
is_logo_mode_ = enable;
|
is_logo_mode_ = enable;
|
||||||
@@ -78,4 +98,7 @@ public:
|
|||||||
float idle_max = enable ? PNG_IDLE_TIME_MAX_LOGO : PNG_IDLE_TIME_MAX;
|
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);
|
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,4 +27,9 @@ public:
|
|||||||
// screen_height: altura actual de pantalla
|
// screen_height: altura actual de pantalla
|
||||||
// Retorna: factor multiplicador para constantes de física (spring_k, damping, etc.)
|
// Retorna: factor multiplicador para constantes de física (spring_k, damping, etc.)
|
||||||
virtual float getScaleFactor(float screen_height) const = 0;
|
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) {}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user