Sistema de convergencia para LOGO MODE (resolución escalable)
Implementa sistema adaptativo que evita interrupciones prematuras en resoluciones altas. El timing ahora se ajusta según convergencia de partículas en lugar de usar intervalos fijos. Cambios: - Ball: getDistanceToTarget() para medir distancia a objetivo - Engine: shape_convergence_, logo_convergence_threshold_ y tiempos escalados - defines.h: LOGO_CONVERGENCE_MIN/MAX (75-100%) - updateShape(): Cálculo de % de pelotas convergidas - toggleShapeMode(): Genera threshold aleatorio al entrar en LOGO - setState(): Escala logo_min/max_time con resolución (base 720p) - updateDemoMode(): Dispara cuando (tiempo>=MIN AND convergencia>=threshold) OR tiempo>=MAX Funcionamiento: 1. Al entrar a SHAPE en LOGO: threshold random 75-100%, tiempos escalados con altura 2. Cada frame: calcula % pelotas cerca de objetivo (shape_convergence_) 3. Dispara acción cuando: (tiempo>=MIN AND convergencia>=threshold) OR tiempo>=MAX 4. Resultado: En 720p funciona como antes, en 1440p espera convergencia real 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -303,6 +303,15 @@ 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;
|
||||||
|
|
||||||
|
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
|
// 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::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 spring_k_base, float damping_base_base, float damping_near_base,
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ class Ball {
|
|||||||
|
|
||||||
// Sistema de atracción física hacia figuras 3D
|
// Sistema de atracción física hacia figuras 3D
|
||||||
void enableRotoBallAttraction(bool enable);
|
void enableRotoBallAttraction(bool enable);
|
||||||
|
float getDistanceToTarget() const; // Distancia actual al punto objetivo
|
||||||
void applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime,
|
void applyRotoBallForce(float target_x, float target_y, float sphere_radius, float deltaTime,
|
||||||
float spring_k = ROTOBALL_SPRING_K,
|
float spring_k = ROTOBALL_SPRING_K,
|
||||||
float damping_base = ROTOBALL_DAMPING_BASE,
|
float damping_base = ROTOBALL_DAMPING_BASE,
|
||||||
|
|||||||
@@ -211,10 +211,14 @@ constexpr int DEMO_LITE_WEIGHT_IMPULSE = 10; // Aplicar impulso (10%)
|
|||||||
// Configuración de Modo LOGO (easter egg - "marca de agua")
|
// Configuración de Modo LOGO (easter egg - "marca de agua")
|
||||||
constexpr int LOGO_MODE_MIN_BALLS = 500; // Mínimo de pelotas para activar modo logo
|
constexpr int LOGO_MODE_MIN_BALLS = 500; // Mínimo de pelotas para activar modo logo
|
||||||
constexpr float LOGO_MODE_SHAPE_SCALE = 1.2f; // Escala de figura en modo logo (120%)
|
constexpr float LOGO_MODE_SHAPE_SCALE = 1.2f; // Escala de figura en modo logo (120%)
|
||||||
constexpr float LOGO_ACTION_INTERVAL_MIN = 3.0f; // Tiempo mínimo entre alternancia SHAPE/PHYSICS
|
constexpr float LOGO_ACTION_INTERVAL_MIN = 3.0f; // Tiempo mínimo entre alternancia SHAPE/PHYSICS (escalado con resolución)
|
||||||
constexpr float LOGO_ACTION_INTERVAL_MAX = 5.0f; // Tiempo máximo entre alternancia SHAPE/PHYSICS (más corto que DEMO)
|
constexpr float LOGO_ACTION_INTERVAL_MAX = 5.0f; // Tiempo máximo entre alternancia SHAPE/PHYSICS (escalado con resolución)
|
||||||
constexpr int LOGO_WEIGHT_TOGGLE_PHYSICS = 100; // Único peso: alternar SHAPE ↔ PHYSICS (100%)
|
constexpr int LOGO_WEIGHT_TOGGLE_PHYSICS = 100; // Único peso: alternar SHAPE ↔ PHYSICS (100%)
|
||||||
|
|
||||||
|
// Sistema de convergencia para LOGO MODE (evita interrupciones prematuras en resoluciones altas)
|
||||||
|
constexpr float LOGO_CONVERGENCE_MIN = 0.75f; // 75% mínimo (permite algo de movimiento al disparar)
|
||||||
|
constexpr float LOGO_CONVERGENCE_MAX = 1.00f; // 100% máximo (completamente formado)
|
||||||
|
|
||||||
// Probabilidad de salto a Logo Mode desde DEMO/DEMO_LITE (%)
|
// Probabilidad de salto a Logo Mode desde DEMO/DEMO_LITE (%)
|
||||||
// Relación DEMO:LOGO = 6:1 (pasa 6x más tiempo en DEMO que en LOGO)
|
// Relación DEMO:LOGO = 6:1 (pasa 6x más tiempo en DEMO que en LOGO)
|
||||||
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)
|
||||||
|
|||||||
@@ -145,37 +145,37 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
|||||||
// Buscar todas las texturas PNG en data/balls/
|
// Buscar todas las texturas PNG en data/balls/
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
if (fs::exists(balls_dir) && fs::is_directory(balls_dir)) {
|
if (fs::exists(balls_dir) && fs::is_directory(balls_dir)) {
|
||||||
std::vector<std::pair<std::string, std::string>> texture_files; // (nombre, path)
|
struct TextureInfo {
|
||||||
|
std::string name;
|
||||||
|
std::string path;
|
||||||
|
int width;
|
||||||
|
};
|
||||||
|
std::vector<TextureInfo> texture_files;
|
||||||
|
|
||||||
|
// Cargar información de todas las texturas (incluyendo dimensiones)
|
||||||
for (const auto& entry : fs::directory_iterator(balls_dir)) {
|
for (const auto& entry : fs::directory_iterator(balls_dir)) {
|
||||||
if (entry.is_regular_file() && entry.path().extension() == ".png") {
|
if (entry.is_regular_file() && entry.path().extension() == ".png") {
|
||||||
std::string filename = entry.path().stem().string(); // Sin extensión
|
std::string filename = entry.path().stem().string();
|
||||||
std::string fullpath = entry.path().string();
|
std::string fullpath = entry.path().string();
|
||||||
texture_files.push_back({filename, fullpath});
|
|
||||||
|
// Cargar temporalmente para obtener dimensiones
|
||||||
|
auto temp_texture = std::make_shared<Texture>(renderer_, fullpath);
|
||||||
|
int width = temp_texture->getWidth();
|
||||||
|
|
||||||
|
texture_files.push_back({filename, fullpath, width});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ordenar alfabéticamente (normal.png será primero)
|
// Ordenar por tamaño (grande → pequeño): big(16) → normal(10) → small(6) → tiny(4)
|
||||||
std::sort(texture_files.begin(), texture_files.end());
|
std::sort(texture_files.begin(), texture_files.end(),
|
||||||
|
[](const TextureInfo& a, const TextureInfo& b) {
|
||||||
|
return a.width > b.width; // Descendente por tamaño
|
||||||
|
});
|
||||||
|
|
||||||
// Cargar texturas en orden (con normal.png primero si existe)
|
// Cargar todas las texturas en orden de tamaño (0=big, 1=normal, 2=small, 3=tiny)
|
||||||
int normal_index = -1;
|
for (const auto& info : texture_files) {
|
||||||
for (size_t i = 0; i < texture_files.size(); i++) {
|
textures_.push_back(std::make_shared<Texture>(renderer_, info.path));
|
||||||
if (texture_files[i].first == "normal") {
|
texture_names_.push_back(info.name);
|
||||||
normal_index = static_cast<int>(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Poner normal.png primero
|
|
||||||
if (normal_index > 0) {
|
|
||||||
std::swap(texture_files[0], texture_files[normal_index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cargar todas las texturas
|
|
||||||
for (const auto& [name, path] : texture_files) {
|
|
||||||
textures_.push_back(std::make_shared<Texture>(renderer_, path));
|
|
||||||
texture_names_.push_back(name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,8 +185,14 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
|||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establecer textura inicial (índice 0 = normal.png)
|
// Buscar índice de "normal" para usarlo como textura inicial (debería ser índice 1)
|
||||||
current_texture_index_ = 0;
|
current_texture_index_ = 0; // Fallback
|
||||||
|
for (size_t i = 0; i < texture_names_.size(); i++) {
|
||||||
|
if (texture_names_[i] == "normal") {
|
||||||
|
current_texture_index_ = i; // Iniciar en "normal" (índice 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
texture_ = textures_[current_texture_index_];
|
texture_ = textures_[current_texture_index_];
|
||||||
current_ball_size_ = texture_->getWidth(); // Obtener tamaño dinámicamente
|
current_ball_size_ = texture_->getWidth(); // Obtener tamaño dinámicamente
|
||||||
|
|
||||||
@@ -1411,8 +1417,13 @@ void Engine::setState(AppMode new_mode) {
|
|||||||
float min_interval, max_interval;
|
float min_interval, max_interval;
|
||||||
|
|
||||||
if (new_mode == AppMode::LOGO) {
|
if (new_mode == AppMode::LOGO) {
|
||||||
min_interval = LOGO_ACTION_INTERVAL_MIN;
|
// Escalar tiempos con resolución (720p como base)
|
||||||
max_interval = LOGO_ACTION_INTERVAL_MAX;
|
float resolution_scale = current_screen_height_ / 720.0f;
|
||||||
|
logo_min_time_ = LOGO_ACTION_INTERVAL_MIN * resolution_scale;
|
||||||
|
logo_max_time_ = LOGO_ACTION_INTERVAL_MAX * resolution_scale;
|
||||||
|
|
||||||
|
min_interval = logo_min_time_;
|
||||||
|
max_interval = logo_max_time_;
|
||||||
} else {
|
} else {
|
||||||
bool is_lite = (new_mode == AppMode::DEMO_LITE);
|
bool is_lite = (new_mode == AppMode::DEMO_LITE);
|
||||||
min_interval = is_lite ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN;
|
min_interval = is_lite ? DEMO_LITE_ACTION_INTERVAL_MIN : DEMO_ACTION_INTERVAL_MIN;
|
||||||
@@ -1434,8 +1445,23 @@ void Engine::updateDemoMode() {
|
|||||||
// Actualizar timer
|
// Actualizar timer
|
||||||
demo_timer_ += delta_time_;
|
demo_timer_ += delta_time_;
|
||||||
|
|
||||||
|
// Determinar si es hora de ejecutar acción (depende del modo)
|
||||||
|
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_;
|
||||||
|
|
||||||
|
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_;
|
||||||
|
}
|
||||||
|
|
||||||
// Si es hora de ejecutar acción
|
// Si es hora de ejecutar acción
|
||||||
if (demo_timer_ >= demo_next_action_time_) {
|
if (should_trigger) {
|
||||||
// MODO LOGO: Sistema de acciones variadas con gravedad dinámica
|
// MODO LOGO: Sistema de acciones variadas con gravedad dinámica
|
||||||
if (current_app_mode_ == AppMode::LOGO) {
|
if (current_app_mode_ == AppMode::LOGO) {
|
||||||
// Elegir acción aleatoria ponderada
|
// Elegir acción aleatoria ponderada
|
||||||
@@ -1464,10 +1490,10 @@ void Engine::updateDemoMode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resetear timer con intervalos de Logo Mode
|
// Resetear timer con intervalos escalados (logo_min_time_ y logo_max_time_)
|
||||||
demo_timer_ = 0.0f;
|
demo_timer_ = 0.0f;
|
||||||
float interval_range = LOGO_ACTION_INTERVAL_MAX - LOGO_ACTION_INTERVAL_MIN;
|
float interval_range = logo_max_time_ - logo_min_time_;
|
||||||
demo_next_action_time_ = LOGO_ACTION_INTERVAL_MIN + (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)
|
||||||
@@ -1968,6 +1994,13 @@ void Engine::toggleShapeMode(bool force_gravity_on_exit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Si estamos en LOGO MODE, generar threshold aleatorio de convergencia (75-100%)
|
||||||
|
if (current_app_mode_ == AppMode::LOGO) {
|
||||||
|
logo_convergence_threshold_ = LOGO_CONVERGENCE_MIN +
|
||||||
|
(rand() % 1000) / 1000.0f * (LOGO_CONVERGENCE_MAX - LOGO_CONVERGENCE_MIN);
|
||||||
|
shape_convergence_ = 0.0f; // Reset convergencia al entrar
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Volver a modo física normal
|
// Volver a modo física normal
|
||||||
current_mode_ = SimulationMode::PHYSICS;
|
current_mode_ = SimulationMode::PHYSICS;
|
||||||
@@ -2111,6 +2144,20 @@ void Engine::updateShape() {
|
|||||||
float depth_scale = depth_zoom_enabled_ ? (0.5f + z_normalized * 1.0f) : 1.0f;
|
float depth_scale = depth_zoom_enabled_ ? (0.5f + z_normalized * 1.0f) : 1.0f;
|
||||||
balls_[i]->setDepthScale(depth_scale);
|
balls_[i]->setDepthScale(depth_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calcular convergencia en LOGO MODE (% de pelotas cerca de su objetivo)
|
||||||
|
if (current_app_mode_ == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) {
|
||||||
|
int balls_near = 0;
|
||||||
|
float distance_threshold = SHAPE_NEAR_THRESHOLD * scale_factor;
|
||||||
|
|
||||||
|
for (const auto& ball : balls_) {
|
||||||
|
if (ball->getDistanceToTarget() < distance_threshold) {
|
||||||
|
balls_near++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shape_convergence_ = static_cast<float>(balls_near) / balls_.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limitar escala de figura para evitar que se salga de pantalla
|
// Limitar escala de figura para evitar que se salga de pantalla
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ class Engine {
|
|||||||
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
|
float demo_timer_ = 0.0f; // Contador de tiempo para próxima acción
|
||||||
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
|
float demo_next_action_time_ = 0.0f; // Tiempo aleatorio hasta próxima acción (segundos)
|
||||||
|
|
||||||
|
// Sistema de convergencia para LOGO MODE (escala con resolución)
|
||||||
|
float shape_convergence_ = 0.0f; // % de pelotas cerca del objetivo (0.0-1.0)
|
||||||
|
float logo_convergence_threshold_ = 0.90f; // Threshold aleatorio (75-100%)
|
||||||
|
float logo_min_time_ = 3.0f; // Tiempo mínimo escalado con resolución
|
||||||
|
float logo_max_time_ = 5.0f; // Tiempo máximo escalado (backup)
|
||||||
|
|
||||||
// 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user