Compare commits
6 Commits
boids_deve
...
c91cb1ca56
| Author | SHA1 | Date | |
|---|---|---|---|
| c91cb1ca56 | |||
| 8d608357b4 | |||
| f73a133756 | |||
| de23327861 | |||
| f6402084eb | |||
| 9909d4c12d |
BIN
data/logo/logo.png
Normal file
BIN
data/logo/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
BIN
data/logo/logo2.png
Normal file
BIN
data/logo/logo2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
635
source/app_logo.cpp
Normal file
635
source/app_logo.cpp
Normal file
@@ -0,0 +1,635 @@
|
||||
#include "app_logo.h"
|
||||
|
||||
#include <SDL3/SDL_render.h> // for SDL_SCALEMODE_LINEAR, SDL_RenderGeometry
|
||||
#include <cmath> // for powf, sinf, cosf
|
||||
|
||||
#include "external/sprite.h" // for Sprite
|
||||
#include "external/texture.h" // for Texture
|
||||
|
||||
bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) {
|
||||
renderer_ = renderer;
|
||||
screen_width_ = screen_width;
|
||||
screen_height_ = screen_height;
|
||||
|
||||
std::string resources_dir = getResourcesDirectory();
|
||||
|
||||
// ========================================================================
|
||||
// Cargar LOGO1 desde data/logo/logo.png
|
||||
// ========================================================================
|
||||
std::string logo1_path = resources_dir + "/data/logo/logo.png";
|
||||
logo1_texture_ = std::make_shared<Texture>(renderer, logo1_path);
|
||||
if (logo1_texture_->getWidth() == 0 || logo1_texture_->getHeight() == 0) {
|
||||
// Error al cargar textura logo1
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configurar filtrado LINEAR para suavizado
|
||||
logo1_texture_->setScaleMode(SDL_SCALEMODE_LINEAR);
|
||||
|
||||
// Crear sprite con la textura
|
||||
logo1_sprite_ = std::make_unique<Sprite>(logo1_texture_);
|
||||
|
||||
// Configurar el clip para que use toda la textura
|
||||
float logo1_width = static_cast<float>(logo1_texture_->getWidth());
|
||||
float logo1_height = static_cast<float>(logo1_texture_->getHeight());
|
||||
logo1_sprite_->setClip({0.0f, 0.0f, logo1_width, logo1_height});
|
||||
|
||||
// ========================================================================
|
||||
// Cargar LOGO2 desde data/logo/logo2.png
|
||||
// ========================================================================
|
||||
std::string logo2_path = resources_dir + "/data/logo/logo2.png";
|
||||
logo2_texture_ = std::make_shared<Texture>(renderer, logo2_path);
|
||||
if (logo2_texture_->getWidth() == 0 || logo2_texture_->getHeight() == 0) {
|
||||
// Error al cargar textura logo2
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configurar filtrado LINEAR para suavizado
|
||||
logo2_texture_->setScaleMode(SDL_SCALEMODE_LINEAR);
|
||||
|
||||
// Crear sprite con la textura
|
||||
logo2_sprite_ = std::make_unique<Sprite>(logo2_texture_);
|
||||
|
||||
// Configurar el clip para que use toda la textura
|
||||
float logo2_width = static_cast<float>(logo2_texture_->getWidth());
|
||||
float logo2_height = static_cast<float>(logo2_texture_->getHeight());
|
||||
logo2_sprite_->setClip({0.0f, 0.0f, logo2_width, logo2_height});
|
||||
|
||||
// ========================================================================
|
||||
// Calcular tamaño base (asumimos mismo tamaño para ambos logos)
|
||||
// El logo debe tener una altura de APPLOGO_HEIGHT_PERCENT (40%) de la pantalla
|
||||
// ========================================================================
|
||||
float target_height = screen_height_ * APPLOGO_HEIGHT_PERCENT;
|
||||
float scale = target_height / logo1_height;
|
||||
|
||||
base_width_ = logo1_width * scale;
|
||||
base_height_ = target_height; // = logo1_height * scale
|
||||
|
||||
// Aplicar escala inicial a ambos sprites
|
||||
logo1_sprite_->setSize(base_width_, base_height_);
|
||||
logo2_sprite_->setSize(base_width_, base_height_);
|
||||
|
||||
// Posicionar ambos logos en el centro del cuadrante inferior derecho (superpuestos)
|
||||
updateLogoPosition();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppLogo::update(float delta_time, AppMode current_mode) {
|
||||
// Si estamos en SANDBOX, resetear y no hacer nada (logo desactivado)
|
||||
if (current_mode == AppMode::SANDBOX) {
|
||||
state_ = AppLogoState::HIDDEN;
|
||||
timer_ = 0.0f;
|
||||
current_alpha_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Máquina de estados con fade in/out
|
||||
timer_ += delta_time;
|
||||
|
||||
switch (state_) {
|
||||
case AppLogoState::HIDDEN:
|
||||
// Esperando el intervalo de espera
|
||||
if (timer_ >= APPLOGO_DISPLAY_INTERVAL) {
|
||||
state_ = AppLogoState::FADE_IN;
|
||||
timer_ = 0.0f;
|
||||
current_alpha_ = 0;
|
||||
// Elegir animaciones de entrada aleatorias (independientes para cada logo)
|
||||
logo1_entry_animation_ = getRandomAnimation();
|
||||
logo2_entry_animation_ = getRandomAnimation();
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoState::FADE_IN:
|
||||
// Fade in: alpha de 0 a 255, animaciones independientes para logo1 y logo2
|
||||
{
|
||||
float fade_progress = timer_ / APPLOGO_FADE_DURATION;
|
||||
if (fade_progress >= 1.0f) {
|
||||
// Fade in completado
|
||||
state_ = AppLogoState::VISIBLE;
|
||||
timer_ = 0.0f;
|
||||
current_alpha_ = 255;
|
||||
// Resetear variables de ambos logos
|
||||
logo1_scale_ = 1.0f;
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
logo2_scale_ = 1.0f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
} else {
|
||||
// Interpolar alpha linealmente (0 → 255) - compartido
|
||||
current_alpha_ = static_cast<int>(fade_progress * 255.0f);
|
||||
|
||||
// ================================================================
|
||||
// Aplicar animación de LOGO1 según logo1_entry_animation_
|
||||
// ================================================================
|
||||
switch (logo1_entry_animation_) {
|
||||
case AppLogoAnimationType::ZOOM_ONLY:
|
||||
logo1_scale_ = 1.2f - (fade_progress * 0.2f);
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ELASTIC_STICK:
|
||||
{
|
||||
float elastic_t = easeOutElastic(fade_progress);
|
||||
logo1_scale_ = 1.2f - (elastic_t * 0.2f);
|
||||
float squash_t = easeOutBack(fade_progress);
|
||||
logo1_squash_y_ = 0.6f + (squash_t * 0.4f);
|
||||
logo1_stretch_x_ = 1.0f + (1.0f - logo1_squash_y_) * 0.5f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||
{
|
||||
float ease_t = easeInOutQuad(fade_progress);
|
||||
logo1_scale_ = 0.3f + (ease_t * 0.7f);
|
||||
logo1_rotation_ = (1.0f - fade_progress) * 6.28f;
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||
{
|
||||
float bounce_t = easeOutBounce(fade_progress);
|
||||
logo1_scale_ = 1.0f;
|
||||
float squash_amount = (1.0f - bounce_t) * 0.3f;
|
||||
logo1_squash_y_ = 1.0f - squash_amount;
|
||||
logo1_stretch_x_ = 1.0f + squash_amount * 0.5f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Aplicar animación de LOGO2 según logo2_entry_animation_
|
||||
// ================================================================
|
||||
switch (logo2_entry_animation_) {
|
||||
case AppLogoAnimationType::ZOOM_ONLY:
|
||||
logo2_scale_ = 1.2f - (fade_progress * 0.2f);
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ELASTIC_STICK:
|
||||
{
|
||||
float elastic_t = easeOutElastic(fade_progress);
|
||||
logo2_scale_ = 1.2f - (elastic_t * 0.2f);
|
||||
float squash_t = easeOutBack(fade_progress);
|
||||
logo2_squash_y_ = 0.6f + (squash_t * 0.4f);
|
||||
logo2_stretch_x_ = 1.0f + (1.0f - logo2_squash_y_) * 0.5f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||
{
|
||||
float ease_t = easeInOutQuad(fade_progress);
|
||||
logo2_scale_ = 0.3f + (ease_t * 0.7f);
|
||||
logo2_rotation_ = (1.0f - fade_progress) * 6.28f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||
{
|
||||
float bounce_t = easeOutBounce(fade_progress);
|
||||
logo2_scale_ = 1.0f;
|
||||
float squash_amount = (1.0f - bounce_t) * 0.3f;
|
||||
logo2_squash_y_ = 1.0f - squash_amount;
|
||||
logo2_stretch_x_ = 1.0f + squash_amount * 0.5f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoState::VISIBLE:
|
||||
// Logo completamente visible, esperando duración
|
||||
if (timer_ >= APPLOGO_DISPLAY_DURATION) {
|
||||
state_ = AppLogoState::FADE_OUT;
|
||||
timer_ = 0.0f;
|
||||
current_alpha_ = 255;
|
||||
// Elegir animaciones de salida aleatorias (independientes para cada logo)
|
||||
logo1_exit_animation_ = getRandomAnimation();
|
||||
logo2_exit_animation_ = getRandomAnimation();
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoState::FADE_OUT:
|
||||
// Fade out: alpha de 255 a 0, animaciones independientes para logo1 y logo2
|
||||
{
|
||||
float fade_progress = timer_ / APPLOGO_FADE_DURATION;
|
||||
if (fade_progress >= 1.0f) {
|
||||
// Fade out completado, volver a HIDDEN
|
||||
state_ = AppLogoState::HIDDEN;
|
||||
timer_ = 0.0f;
|
||||
current_alpha_ = 0;
|
||||
// Resetear variables de ambos logos
|
||||
logo1_scale_ = 1.0f;
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
logo2_scale_ = 1.0f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
} else {
|
||||
// Interpolar alpha linealmente (255 → 0) - compartido
|
||||
current_alpha_ = static_cast<int>((1.0f - fade_progress) * 255.0f);
|
||||
|
||||
// ================================================================
|
||||
// Aplicar animación de LOGO1 según logo1_exit_animation_
|
||||
// ================================================================
|
||||
switch (logo1_exit_animation_) {
|
||||
case AppLogoAnimationType::ZOOM_ONLY:
|
||||
logo1_scale_ = 1.0f + (fade_progress * 0.2f);
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
logo1_rotation_ = 0.0f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ELASTIC_STICK:
|
||||
logo1_scale_ = 1.0f + (fade_progress * fade_progress * 0.2f);
|
||||
logo1_squash_y_ = 1.0f + (fade_progress * 0.3f);
|
||||
logo1_stretch_x_ = 1.0f - (fade_progress * 0.2f);
|
||||
logo1_rotation_ = fade_progress * 0.1f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||
{
|
||||
float ease_t = easeInOutQuad(fade_progress);
|
||||
logo1_scale_ = 1.0f - (ease_t * 0.7f);
|
||||
logo1_rotation_ = fade_progress * 6.28f;
|
||||
logo1_squash_y_ = 1.0f;
|
||||
logo1_stretch_x_ = 1.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||
{
|
||||
if (fade_progress < 0.2f) {
|
||||
float squash_t = fade_progress / 0.2f;
|
||||
logo1_squash_y_ = 1.0f - (squash_t * 0.3f);
|
||||
logo1_stretch_x_ = 1.0f + (squash_t * 0.2f);
|
||||
} else {
|
||||
float jump_t = (fade_progress - 0.2f) / 0.8f;
|
||||
logo1_squash_y_ = 0.7f + (jump_t * 0.5f);
|
||||
logo1_stretch_x_ = 1.2f - (jump_t * 0.2f);
|
||||
}
|
||||
logo1_scale_ = 1.0f + (fade_progress * 0.3f);
|
||||
logo1_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Aplicar animación de LOGO2 según logo2_exit_animation_
|
||||
// ================================================================
|
||||
switch (logo2_exit_animation_) {
|
||||
case AppLogoAnimationType::ZOOM_ONLY:
|
||||
logo2_scale_ = 1.0f + (fade_progress * 0.2f);
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
logo2_rotation_ = 0.0f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ELASTIC_STICK:
|
||||
logo2_scale_ = 1.0f + (fade_progress * fade_progress * 0.2f);
|
||||
logo2_squash_y_ = 1.0f + (fade_progress * 0.3f);
|
||||
logo2_stretch_x_ = 1.0f - (fade_progress * 0.2f);
|
||||
logo2_rotation_ = fade_progress * 0.1f;
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::ROTATE_SPIRAL:
|
||||
{
|
||||
float ease_t = easeInOutQuad(fade_progress);
|
||||
logo2_scale_ = 1.0f - (ease_t * 0.7f);
|
||||
logo2_rotation_ = fade_progress * 6.28f;
|
||||
logo2_squash_y_ = 1.0f;
|
||||
logo2_stretch_x_ = 1.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case AppLogoAnimationType::BOUNCE_SQUASH:
|
||||
{
|
||||
if (fade_progress < 0.2f) {
|
||||
float squash_t = fade_progress / 0.2f;
|
||||
logo2_squash_y_ = 1.0f - (squash_t * 0.3f);
|
||||
logo2_stretch_x_ = 1.0f + (squash_t * 0.2f);
|
||||
} else {
|
||||
float jump_t = (fade_progress - 0.2f) / 0.8f;
|
||||
logo2_squash_y_ = 0.7f + (jump_t * 0.5f);
|
||||
logo2_stretch_x_ = 1.2f - (jump_t * 0.2f);
|
||||
}
|
||||
logo2_scale_ = 1.0f + (fade_progress * 0.3f);
|
||||
logo2_rotation_ = 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Aplicar alpha a ambos logos (compartido - sincronizado)
|
||||
if (logo1_texture_) {
|
||||
logo1_texture_->setAlpha(current_alpha_);
|
||||
}
|
||||
if (logo2_texture_) {
|
||||
logo2_texture_->setAlpha(current_alpha_);
|
||||
}
|
||||
|
||||
// Aplicar escala animada INDEPENDIENTE a cada logo
|
||||
if (logo1_sprite_) {
|
||||
float scaled_width = base_width_ * logo1_scale_;
|
||||
float scaled_height = base_height_ * logo1_scale_;
|
||||
logo1_sprite_->setSize(scaled_width, scaled_height);
|
||||
}
|
||||
|
||||
if (logo2_sprite_) {
|
||||
float scaled_width = base_width_ * logo2_scale_;
|
||||
float scaled_height = base_height_ * logo2_scale_;
|
||||
logo2_sprite_->setSize(scaled_width, scaled_height);
|
||||
}
|
||||
|
||||
// Recentrar ambos logos (están superpuestos, misma posición)
|
||||
updateLogoPosition();
|
||||
}
|
||||
|
||||
void AppLogo::render() {
|
||||
// Renderizar si NO está en estado HIDDEN (incluye FADE_IN, VISIBLE, FADE_OUT)
|
||||
if (state_ != AppLogoState::HIDDEN) {
|
||||
// Determinar animaciones actuales para cada logo
|
||||
AppLogoAnimationType logo1_anim = (state_ == AppLogoState::FADE_IN) ? logo1_entry_animation_ : logo1_exit_animation_;
|
||||
AppLogoAnimationType logo2_anim = (state_ == AppLogoState::FADE_IN) ? logo2_entry_animation_ : logo2_exit_animation_;
|
||||
|
||||
// ====================================================================
|
||||
// Renderizar LOGO1 primero (fondo)
|
||||
// ====================================================================
|
||||
if (logo1_anim != AppLogoAnimationType::ZOOM_ONLY) {
|
||||
// Usar renderizado con geometría para deformaciones/rotación
|
||||
renderWithGeometry(1);
|
||||
} else if (logo1_sprite_) {
|
||||
// Usar renderizado simple con Sprite (solo ZOOM_ONLY)
|
||||
logo1_sprite_->render();
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Renderizar LOGO2 después (encima de logo1)
|
||||
// ====================================================================
|
||||
if (logo2_anim != AppLogoAnimationType::ZOOM_ONLY) {
|
||||
// Usar renderizado con geometría para deformaciones/rotación
|
||||
renderWithGeometry(2);
|
||||
} else if (logo2_sprite_) {
|
||||
// Usar renderizado simple con Sprite (solo ZOOM_ONLY)
|
||||
logo2_sprite_->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppLogo::updateScreenSize(int screen_width, int screen_height) {
|
||||
screen_width_ = screen_width;
|
||||
screen_height_ = screen_height;
|
||||
|
||||
// Recalcular tamaño base para la nueva resolución (asumimos mismo tamaño para ambos logos)
|
||||
if (logo1_sprite_ && logo1_texture_) {
|
||||
float logo_width = static_cast<float>(logo1_texture_->getWidth());
|
||||
float logo_height = static_cast<float>(logo1_texture_->getHeight());
|
||||
|
||||
// El logo debe tener una altura de APPLOGO_HEIGHT_PERCENT (40%) de la pantalla
|
||||
float target_height = screen_height_ * APPLOGO_HEIGHT_PERCENT;
|
||||
float scale = target_height / logo_height;
|
||||
|
||||
// Recalcular tamaño base
|
||||
base_width_ = logo_width * scale;
|
||||
base_height_ = target_height; // = logo_height * scale
|
||||
|
||||
// Aplicar escala actual a AMBOS logos (respeta la animación en curso)
|
||||
if (logo1_sprite_) {
|
||||
float scaled_width = base_width_ * logo1_scale_;
|
||||
float scaled_height = base_height_ * logo1_scale_;
|
||||
logo1_sprite_->setSize(scaled_width, scaled_height);
|
||||
}
|
||||
|
||||
if (logo2_sprite_) {
|
||||
float scaled_width = base_width_ * logo2_scale_;
|
||||
float scaled_height = base_height_ * logo2_scale_;
|
||||
logo2_sprite_->setSize(scaled_width, scaled_height);
|
||||
}
|
||||
|
||||
// Reposicionar ambos logos
|
||||
updateLogoPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void AppLogo::updateLogoPosition() {
|
||||
// Calcular padding desde bordes derecho e inferior
|
||||
float padding_x = screen_width_ * APPLOGO_PADDING_PERCENT;
|
||||
float padding_y = screen_height_ * APPLOGO_PADDING_PERCENT;
|
||||
|
||||
// Posicionar LOGO1 (anclado a esquina inferior derecha con padding)
|
||||
if (logo1_sprite_) {
|
||||
float logo1_width = base_width_ * logo1_scale_;
|
||||
float logo1_height = base_height_ * logo1_scale_;
|
||||
float pos_x = screen_width_ - logo1_width - padding_x;
|
||||
float pos_y = screen_height_ - logo1_height - padding_y;
|
||||
logo1_sprite_->setPos({pos_x, pos_y});
|
||||
}
|
||||
|
||||
// Posicionar LOGO2 (anclado a esquina inferior derecha con padding, superpuesto a logo1)
|
||||
if (logo2_sprite_) {
|
||||
float logo2_width = base_width_ * logo2_scale_;
|
||||
float logo2_height = base_height_ * logo2_scale_;
|
||||
float pos_x = screen_width_ - logo2_width - padding_x;
|
||||
float pos_y = screen_height_ - logo2_height - padding_y;
|
||||
logo2_sprite_->setPos({pos_x, pos_y});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Funciones de easing para animaciones
|
||||
// ============================================================================
|
||||
|
||||
float AppLogo::easeOutElastic(float t) {
|
||||
// Elastic easing out: bounce elástico al final
|
||||
const float c4 = (2.0f * 3.14159f) / 3.0f;
|
||||
|
||||
if (t == 0.0f) return 0.0f;
|
||||
if (t == 1.0f) return 1.0f;
|
||||
|
||||
return powf(2.0f, -10.0f * t) * sinf((t * 10.0f - 0.75f) * c4) + 1.0f;
|
||||
}
|
||||
|
||||
float AppLogo::easeOutBack(float t) {
|
||||
// Back easing out: overshoot suave al final
|
||||
const float c1 = 1.70158f;
|
||||
const float c3 = c1 + 1.0f;
|
||||
|
||||
return 1.0f + c3 * powf(t - 1.0f, 3.0f) + c1 * powf(t - 1.0f, 2.0f);
|
||||
}
|
||||
|
||||
float AppLogo::easeOutBounce(float t) {
|
||||
// Bounce easing out: rebotes decrecientes (para BOUNCE_SQUASH)
|
||||
const float n1 = 7.5625f;
|
||||
const float d1 = 2.75f;
|
||||
|
||||
if (t < 1.0f / d1) {
|
||||
return n1 * t * t;
|
||||
} else if (t < 2.0f / d1) {
|
||||
t -= 1.5f / d1;
|
||||
return n1 * t * t + 0.75f;
|
||||
} else if (t < 2.5f / d1) {
|
||||
t -= 2.25f / d1;
|
||||
return n1 * t * t + 0.9375f;
|
||||
} else {
|
||||
t -= 2.625f / d1;
|
||||
return n1 * t * t + 0.984375f;
|
||||
}
|
||||
}
|
||||
|
||||
float AppLogo::easeInOutQuad(float t) {
|
||||
// Quadratic easing in/out: aceleración suave (para ROTATE_SPIRAL)
|
||||
if (t < 0.5f) {
|
||||
return 2.0f * t * t;
|
||||
} else {
|
||||
return 1.0f - powf(-2.0f * t + 2.0f, 2.0f) / 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Función auxiliar para aleatorización
|
||||
// ============================================================================
|
||||
|
||||
AppLogoAnimationType AppLogo::getRandomAnimation() {
|
||||
// Generar número aleatorio entre 0 y 3 (4 tipos de animación)
|
||||
int random_value = rand() % 4;
|
||||
|
||||
switch (random_value) {
|
||||
case 0:
|
||||
return AppLogoAnimationType::ZOOM_ONLY;
|
||||
case 1:
|
||||
return AppLogoAnimationType::ELASTIC_STICK;
|
||||
case 2:
|
||||
return AppLogoAnimationType::ROTATE_SPIRAL;
|
||||
case 3:
|
||||
default:
|
||||
return AppLogoAnimationType::BOUNCE_SQUASH;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Renderizado con geometría deformada (para animación ELASTIC_STICK)
|
||||
// ============================================================================
|
||||
|
||||
void AppLogo::renderWithGeometry(int logo_index) {
|
||||
if (!renderer_) return;
|
||||
|
||||
// Seleccionar variables según el logo_index (1 = logo1, 2 = logo2)
|
||||
std::shared_ptr<Texture> texture;
|
||||
float scale, squash_y, stretch_x, rotation;
|
||||
|
||||
if (logo_index == 1) {
|
||||
if (!logo1_texture_) return;
|
||||
texture = logo1_texture_;
|
||||
scale = logo1_scale_;
|
||||
squash_y = logo1_squash_y_;
|
||||
stretch_x = logo1_stretch_x_;
|
||||
rotation = logo1_rotation_;
|
||||
} else if (logo_index == 2) {
|
||||
if (!logo2_texture_) return;
|
||||
texture = logo2_texture_;
|
||||
scale = logo2_scale_;
|
||||
squash_y = logo2_squash_y_;
|
||||
stretch_x = logo2_stretch_x_;
|
||||
rotation = logo2_rotation_;
|
||||
} else {
|
||||
return; // Índice inválido
|
||||
}
|
||||
|
||||
// Calcular tamaño con escala y deformaciones aplicadas
|
||||
float width = base_width_ * scale * stretch_x;
|
||||
float height = base_height_ * scale * squash_y;
|
||||
|
||||
// Calcular padding desde bordes derecho e inferior
|
||||
float padding_x = screen_width_ * APPLOGO_PADDING_PERCENT;
|
||||
float padding_y = screen_height_ * APPLOGO_PADDING_PERCENT;
|
||||
|
||||
// Calcular esquina del logo (anclado a esquina inferior derecha con padding)
|
||||
float corner_x = screen_width_ - width - padding_x;
|
||||
float corner_y = screen_height_ - height - padding_y;
|
||||
|
||||
// Centro del logo (para rotación) = esquina + mitad del tamaño
|
||||
float center_x = corner_x + (width / 2.0f);
|
||||
float center_y = corner_y + (height / 2.0f);
|
||||
|
||||
// Pre-calcular seno y coseno de rotación
|
||||
float cos_rot = cosf(rotation);
|
||||
float sin_rot = sinf(rotation);
|
||||
|
||||
// Crear 4 vértices del quad (centrado en center_x, center_y)
|
||||
SDL_Vertex vertices[4];
|
||||
|
||||
// Offset desde el centro
|
||||
float half_w = width / 2.0f;
|
||||
float half_h = height / 2.0f;
|
||||
|
||||
// Vértice superior izquierdo (rotado)
|
||||
{
|
||||
float local_x = -half_w;
|
||||
float local_y = -half_h;
|
||||
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
||||
float rotated_y = local_x * sin_rot + local_y * cos_rot;
|
||||
vertices[0].position = {center_x + rotated_x, center_y + rotated_y};
|
||||
vertices[0].tex_coord = {0.0f, 0.0f};
|
||||
vertices[0].color = {1.0f, 1.0f, 1.0f, 1.0f}; // Color blanco (textura se modula con alpha)
|
||||
}
|
||||
|
||||
// Vértice superior derecho (rotado)
|
||||
{
|
||||
float local_x = half_w;
|
||||
float local_y = -half_h;
|
||||
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
||||
float rotated_y = local_x * sin_rot + local_y * cos_rot;
|
||||
vertices[1].position = {center_x + rotated_x, center_y + rotated_y};
|
||||
vertices[1].tex_coord = {1.0f, 0.0f};
|
||||
vertices[1].color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
}
|
||||
|
||||
// Vértice inferior derecho (rotado)
|
||||
{
|
||||
float local_x = half_w;
|
||||
float local_y = half_h;
|
||||
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
||||
float rotated_y = local_x * sin_rot + local_y * cos_rot;
|
||||
vertices[2].position = {center_x + rotated_x, center_y + rotated_y};
|
||||
vertices[2].tex_coord = {1.0f, 1.0f};
|
||||
vertices[2].color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
}
|
||||
|
||||
// Vértice inferior izquierdo (rotado)
|
||||
{
|
||||
float local_x = -half_w;
|
||||
float local_y = half_h;
|
||||
float rotated_x = local_x * cos_rot - local_y * sin_rot;
|
||||
float rotated_y = local_x * sin_rot + local_y * cos_rot;
|
||||
vertices[3].position = {center_x + rotated_x, center_y + rotated_y};
|
||||
vertices[3].tex_coord = {0.0f, 1.0f};
|
||||
vertices[3].color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
}
|
||||
|
||||
// Índices para 2 triángulos
|
||||
int indices[6] = {0, 1, 2, 2, 3, 0};
|
||||
|
||||
// Renderizar con la textura del logo correspondiente
|
||||
SDL_RenderGeometry(renderer_, texture->getSDLTexture(), vertices, 4, indices, 6);
|
||||
}
|
||||
97
source/app_logo.h
Normal file
97
source/app_logo.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_render.h> // for SDL_Renderer
|
||||
|
||||
#include <memory> // for unique_ptr, shared_ptr
|
||||
|
||||
#include "defines.h" // for AppMode
|
||||
|
||||
class Texture;
|
||||
class Sprite;
|
||||
|
||||
// Estados de la máquina de estados del logo
|
||||
enum class AppLogoState {
|
||||
HIDDEN, // Logo oculto, esperando APPLOGO_DISPLAY_INTERVAL
|
||||
FADE_IN, // Apareciendo (alpha 0 → 255)
|
||||
VISIBLE, // Completamente visible, esperando APPLOGO_DISPLAY_DURATION
|
||||
FADE_OUT // Desapareciendo (alpha 255 → 0)
|
||||
};
|
||||
|
||||
// Tipo de animación de entrada/salida
|
||||
enum class AppLogoAnimationType {
|
||||
ZOOM_ONLY, // A: Solo zoom simple (120% → 100% → 120%)
|
||||
ELASTIC_STICK, // B: Zoom + deformación elástica tipo "pegatina"
|
||||
ROTATE_SPIRAL, // C: Rotación en espiral (entra girando, sale girando)
|
||||
BOUNCE_SQUASH // D: Rebote con aplastamiento (cae rebotando, salta)
|
||||
};
|
||||
|
||||
class AppLogo {
|
||||
public:
|
||||
AppLogo() = default;
|
||||
~AppLogo() = default;
|
||||
|
||||
// Inicializar textura y sprite del logo
|
||||
bool initialize(SDL_Renderer* renderer, int screen_width, int screen_height);
|
||||
|
||||
// Actualizar temporizadores y estado de visibilidad
|
||||
void update(float delta_time, AppMode current_mode);
|
||||
|
||||
// Renderizar logo si está visible
|
||||
void render();
|
||||
|
||||
// Actualizar tamaño de pantalla (reposicionar logo)
|
||||
void updateScreenSize(int screen_width, int screen_height);
|
||||
|
||||
private:
|
||||
// Texturas y sprites (x2 - logo1 y logo2 superpuestos)
|
||||
std::shared_ptr<Texture> logo1_texture_; // Textura del logo1 (data/logo/logo.png)
|
||||
std::unique_ptr<Sprite> logo1_sprite_; // Sprite para renderizar logo1
|
||||
std::shared_ptr<Texture> logo2_texture_; // Textura del logo2 (data/logo/logo2.png)
|
||||
std::unique_ptr<Sprite> logo2_sprite_; // Sprite para renderizar logo2
|
||||
|
||||
// Variables COMPARTIDAS (sincronización de ambos logos)
|
||||
AppLogoState state_ = AppLogoState::HIDDEN; // Estado actual de la máquina de estados
|
||||
float timer_ = 0.0f; // Contador de tiempo para estado actual
|
||||
int current_alpha_ = 0; // Alpha actual (0-255)
|
||||
|
||||
// Animaciones INDEPENDIENTES para cada logo
|
||||
AppLogoAnimationType logo1_entry_animation_ = AppLogoAnimationType::ZOOM_ONLY;
|
||||
AppLogoAnimationType logo1_exit_animation_ = AppLogoAnimationType::ZOOM_ONLY;
|
||||
AppLogoAnimationType logo2_entry_animation_ = AppLogoAnimationType::ZOOM_ONLY;
|
||||
AppLogoAnimationType logo2_exit_animation_ = AppLogoAnimationType::ZOOM_ONLY;
|
||||
|
||||
// Variables de deformación INDEPENDIENTES para logo1
|
||||
float logo1_scale_ = 1.0f; // Escala actual de logo1 (1.0 = 100%)
|
||||
float logo1_squash_y_ = 1.0f; // Factor de aplastamiento vertical logo1
|
||||
float logo1_stretch_x_ = 1.0f; // Factor de estiramiento horizontal logo1
|
||||
float logo1_rotation_ = 0.0f; // Rotación en radianes logo1
|
||||
|
||||
// Variables de deformación INDEPENDIENTES para logo2
|
||||
float logo2_scale_ = 1.0f; // Escala actual de logo2 (1.0 = 100%)
|
||||
float logo2_squash_y_ = 1.0f; // Factor de aplastamiento vertical logo2
|
||||
float logo2_stretch_x_ = 1.0f; // Factor de estiramiento horizontal logo2
|
||||
float logo2_rotation_ = 0.0f; // Rotación en radianes logo2
|
||||
|
||||
int screen_width_ = 0; // Ancho de pantalla (para centrar)
|
||||
int screen_height_ = 0; // Alto de pantalla (para centrar)
|
||||
|
||||
// Tamaño base del logo (calculado una vez)
|
||||
float base_width_ = 0.0f;
|
||||
float base_height_ = 0.0f;
|
||||
|
||||
// SDL renderer (necesario para renderizado con geometría)
|
||||
SDL_Renderer* renderer_ = nullptr;
|
||||
|
||||
// Métodos privados auxiliares
|
||||
void updateLogoPosition(); // Centrar ambos logos en pantalla (superpuestos)
|
||||
void renderWithGeometry(int logo_index); // Renderizar logo con vértices deformados (1 o 2)
|
||||
|
||||
// Funciones de easing
|
||||
float easeOutElastic(float t); // Elastic bounce out
|
||||
float easeOutBack(float t); // Overshoot out
|
||||
float easeOutBounce(float t); // Bounce easing (para BOUNCE_SQUASH)
|
||||
float easeInOutQuad(float t); // Quadratic easing (para ROTATE_SPIRAL)
|
||||
|
||||
// Función auxiliar para elegir animación aleatoria
|
||||
AppLogoAnimationType getRandomAnimation();
|
||||
};
|
||||
@@ -17,7 +17,18 @@ BoidManager::BoidManager()
|
||||
, screen_width_(0)
|
||||
, screen_height_(0)
|
||||
, boids_active_(false)
|
||||
, spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) { // Tamaño por defecto, se actualiza en initialize()
|
||||
, spatial_grid_(800, 600, BOID_GRID_CELL_SIZE) // Tamaño por defecto, se actualiza en initialize()
|
||||
, separation_radius_(BOID_SEPARATION_RADIUS)
|
||||
, alignment_radius_(BOID_ALIGNMENT_RADIUS)
|
||||
, cohesion_radius_(BOID_COHESION_RADIUS)
|
||||
, separation_weight_(BOID_SEPARATION_WEIGHT)
|
||||
, alignment_weight_(BOID_ALIGNMENT_WEIGHT)
|
||||
, cohesion_weight_(BOID_COHESION_WEIGHT)
|
||||
, max_speed_(BOID_MAX_SPEED)
|
||||
, min_speed_(BOID_MIN_SPEED)
|
||||
, max_force_(BOID_MAX_FORCE)
|
||||
, boundary_margin_(BOID_BOUNDARY_MARGIN)
|
||||
, boundary_weight_(BOID_BOUNDARY_WEIGHT) {
|
||||
}
|
||||
|
||||
BoidManager::~BoidManager() {
|
||||
@@ -57,9 +68,9 @@ void BoidManager::activateBoids() {
|
||||
float vx, vy;
|
||||
ball->getVelocity(vx, vy);
|
||||
if (vx == 0.0f && vy == 0.0f) {
|
||||
// Velocidad aleatoria entre -1 y 1
|
||||
vx = (rand() % 200 - 100) / 100.0f;
|
||||
vy = (rand() % 200 - 100) / 100.0f;
|
||||
// Velocidad aleatoria entre -60 y +60 px/s (time-based)
|
||||
vx = ((rand() % 200 - 100) / 100.0f) * 60.0f;
|
||||
vy = ((rand() % 200 - 100) / 100.0f) * 60.0f;
|
||||
ball->setVelocity(vx, vy);
|
||||
}
|
||||
}
|
||||
@@ -118,14 +129,14 @@ void BoidManager::update(float delta_time) {
|
||||
limitSpeed(ball.get());
|
||||
}
|
||||
|
||||
// Actualizar posiciones con velocidades resultantes
|
||||
// Actualizar posiciones con velocidades resultantes (time-based)
|
||||
for (auto& ball : balls) {
|
||||
float vx, vy;
|
||||
ball->getVelocity(vx, vy);
|
||||
|
||||
SDL_FRect pos = ball->getPosition();
|
||||
pos.x += vx;
|
||||
pos.y += vy;
|
||||
pos.x += vx * delta_time; // time-based
|
||||
pos.y += vy * delta_time;
|
||||
|
||||
ball->setPosition(pos.x, pos.y);
|
||||
}
|
||||
@@ -146,7 +157,7 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
|
||||
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
|
||||
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, BOID_SEPARATION_RADIUS);
|
||||
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, separation_radius_);
|
||||
|
||||
for (Ball* other : neighbors) {
|
||||
if (other == boid) continue; // Ignorar a sí mismo
|
||||
@@ -159,10 +170,10 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
||||
float dy = center_y - other_y;
|
||||
float distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance > 0.0f && distance < BOID_SEPARATION_RADIUS) {
|
||||
if (distance > 0.0f && distance < separation_radius_) {
|
||||
// FASE 1.3: Separación más fuerte cuando más cerca (inversamente proporcional a distancia)
|
||||
// Fuerza proporcional a cercanía: 0% en radio máximo, 100% en colisión
|
||||
float separation_strength = (BOID_SEPARATION_RADIUS - distance) / BOID_SEPARATION_RADIUS;
|
||||
float separation_strength = (separation_radius_ - distance) / separation_radius_;
|
||||
steer_x += (dx / distance) * separation_strength;
|
||||
steer_y += (dy / distance) * separation_strength;
|
||||
count++;
|
||||
@@ -177,8 +188,8 @@ void BoidManager::applySeparation(Ball* boid, float delta_time) {
|
||||
// Aplicar fuerza de separación
|
||||
float vx, vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
vx += steer_x * BOID_SEPARATION_WEIGHT * delta_time;
|
||||
vy += steer_y * BOID_SEPARATION_WEIGHT * delta_time;
|
||||
vx += steer_x * separation_weight_ * delta_time;
|
||||
vy += steer_y * separation_weight_ * delta_time;
|
||||
boid->setVelocity(vx, vy);
|
||||
}
|
||||
}
|
||||
@@ -194,7 +205,7 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
|
||||
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
|
||||
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, BOID_ALIGNMENT_RADIUS);
|
||||
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, alignment_radius_);
|
||||
|
||||
for (Ball* other : neighbors) {
|
||||
if (other == boid) continue;
|
||||
@@ -207,7 +218,7 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
||||
float dy = center_y - other_y;
|
||||
float distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < BOID_ALIGNMENT_RADIUS) {
|
||||
if (distance < alignment_radius_) {
|
||||
float other_vx, other_vy;
|
||||
other->getVelocity(other_vx, other_vy);
|
||||
avg_vx += other_vx;
|
||||
@@ -224,14 +235,14 @@ void BoidManager::applyAlignment(Ball* boid, float delta_time) {
|
||||
// Steering hacia la velocidad promedio
|
||||
float vx, vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
float steer_x = (avg_vx - vx) * BOID_ALIGNMENT_WEIGHT * delta_time;
|
||||
float steer_y = (avg_vy - vy) * BOID_ALIGNMENT_WEIGHT * delta_time;
|
||||
float steer_x = (avg_vx - vx) * alignment_weight_ * delta_time;
|
||||
float steer_y = (avg_vy - vy) * alignment_weight_ * delta_time;
|
||||
|
||||
// Limitar fuerza máxima de steering
|
||||
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
|
||||
if (steer_mag > BOID_MAX_FORCE) {
|
||||
steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
|
||||
steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
|
||||
if (steer_mag > max_force_) {
|
||||
steer_x = (steer_x / steer_mag) * max_force_;
|
||||
steer_y = (steer_y / steer_mag) * max_force_;
|
||||
}
|
||||
|
||||
vx += steer_x;
|
||||
@@ -251,7 +262,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
|
||||
// FASE 2: Usar spatial grid para buscar solo vecinos cercanos (O(1) en lugar de O(n))
|
||||
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, BOID_COHESION_RADIUS);
|
||||
auto neighbors = spatial_grid_.queryRadius(center_x, center_y, cohesion_radius_);
|
||||
|
||||
for (Ball* other : neighbors) {
|
||||
if (other == boid) continue;
|
||||
@@ -264,7 +275,7 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
||||
float dy = center_y - other_y;
|
||||
float distance = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < BOID_COHESION_RADIUS) {
|
||||
if (distance < cohesion_radius_) {
|
||||
center_of_mass_x += other_x;
|
||||
center_of_mass_y += other_y;
|
||||
count++;
|
||||
@@ -284,14 +295,14 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
||||
// Solo aplicar si hay distancia al centro (evitar división por cero)
|
||||
if (distance_to_center > 0.1f) {
|
||||
// Normalizar vector dirección (fuerza independiente de distancia)
|
||||
float steer_x = (dx_to_center / distance_to_center) * BOID_COHESION_WEIGHT * delta_time;
|
||||
float steer_y = (dy_to_center / distance_to_center) * BOID_COHESION_WEIGHT * delta_time;
|
||||
float steer_x = (dx_to_center / distance_to_center) * cohesion_weight_ * delta_time;
|
||||
float steer_y = (dy_to_center / distance_to_center) * cohesion_weight_ * delta_time;
|
||||
|
||||
// Limitar fuerza máxima de steering
|
||||
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
|
||||
if (steer_mag > BOID_MAX_FORCE) {
|
||||
steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
|
||||
steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
|
||||
if (steer_mag > max_force_) {
|
||||
steer_x = (steer_x / steer_mag) * max_force_;
|
||||
steer_y = (steer_y / steer_mag) * max_force_;
|
||||
}
|
||||
|
||||
float vx, vy;
|
||||
@@ -304,32 +315,69 @@ void BoidManager::applyCohesion(Ball* boid, float delta_time) {
|
||||
}
|
||||
|
||||
void BoidManager::applyBoundaries(Ball* boid) {
|
||||
// Mantener boids dentro de los límites de la pantalla
|
||||
// Comportamiento "wrapping" (teletransporte al otro lado)
|
||||
// NUEVA IMPLEMENTACIÓN: Bordes como obstáculos (repulsión en lugar de wrapping)
|
||||
// Cuando un boid se acerca a un borde, se aplica una fuerza alejándolo
|
||||
SDL_FRect pos = boid->getPosition();
|
||||
float center_x = pos.x + pos.w / 2.0f;
|
||||
float center_y = pos.y + pos.h / 2.0f;
|
||||
|
||||
bool wrapped = false;
|
||||
float steer_x = 0.0f;
|
||||
float steer_y = 0.0f;
|
||||
|
||||
if (center_x < 0) {
|
||||
pos.x = screen_width_ - pos.w / 2.0f;
|
||||
wrapped = true;
|
||||
} else if (center_x > screen_width_) {
|
||||
pos.x = -pos.w / 2.0f;
|
||||
wrapped = true;
|
||||
// Borde izquierdo (x < boundary_margin_)
|
||||
if (center_x < boundary_margin_) {
|
||||
float distance = center_x; // Distancia al borde (0 = colisión)
|
||||
if (distance < boundary_margin_) {
|
||||
// Fuerza proporcional a cercanía: 0% en margen, 100% en colisión
|
||||
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
|
||||
steer_x += repulsion_strength; // Empujar hacia la derecha
|
||||
}
|
||||
}
|
||||
|
||||
if (center_y < 0) {
|
||||
pos.y = screen_height_ - pos.h / 2.0f;
|
||||
wrapped = true;
|
||||
} else if (center_y > screen_height_) {
|
||||
pos.y = -pos.h / 2.0f;
|
||||
wrapped = true;
|
||||
// Borde derecho (x > screen_width_ - boundary_margin_)
|
||||
if (center_x > screen_width_ - boundary_margin_) {
|
||||
float distance = screen_width_ - center_x;
|
||||
if (distance < boundary_margin_) {
|
||||
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
|
||||
steer_x -= repulsion_strength; // Empujar hacia la izquierda
|
||||
}
|
||||
}
|
||||
|
||||
if (wrapped) {
|
||||
boid->setPosition(pos.x, pos.y);
|
||||
// Borde superior (y < boundary_margin_)
|
||||
if (center_y < boundary_margin_) {
|
||||
float distance = center_y;
|
||||
if (distance < boundary_margin_) {
|
||||
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
|
||||
steer_y += repulsion_strength; // Empujar hacia abajo
|
||||
}
|
||||
}
|
||||
|
||||
// Borde inferior (y > screen_height_ - boundary_margin_)
|
||||
if (center_y > screen_height_ - boundary_margin_) {
|
||||
float distance = screen_height_ - center_y;
|
||||
if (distance < boundary_margin_) {
|
||||
float repulsion_strength = (boundary_margin_ - distance) / boundary_margin_;
|
||||
steer_y -= repulsion_strength; // Empujar hacia arriba
|
||||
}
|
||||
}
|
||||
|
||||
// Aplicar fuerza de repulsión si hay alguna
|
||||
if (steer_x != 0.0f || steer_y != 0.0f) {
|
||||
float vx, vy;
|
||||
boid->getVelocity(vx, vy);
|
||||
|
||||
// Normalizar fuerza de repulsión (para que todas las direcciones tengan la misma intensidad)
|
||||
float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
|
||||
if (steer_mag > 0.0f) {
|
||||
steer_x /= steer_mag;
|
||||
steer_y /= steer_mag;
|
||||
}
|
||||
|
||||
// Aplicar aceleración de repulsión (time-based)
|
||||
// boundary_weight_ es más fuerte que separation para garantizar que no escapen
|
||||
vx += steer_x * boundary_weight_ * (1.0f / 60.0f); // Simular delta_time fijo para independencia
|
||||
vy += steer_y * boundary_weight_ * (1.0f / 60.0f);
|
||||
boid->setVelocity(vx, vy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,16 +389,16 @@ void BoidManager::limitSpeed(Ball* boid) {
|
||||
float speed = std::sqrt(vx * vx + vy * vy);
|
||||
|
||||
// Limitar velocidad máxima
|
||||
if (speed > BOID_MAX_SPEED) {
|
||||
vx = (vx / speed) * BOID_MAX_SPEED;
|
||||
vy = (vy / speed) * BOID_MAX_SPEED;
|
||||
if (speed > max_speed_) {
|
||||
vx = (vx / speed) * max_speed_;
|
||||
vy = (vy / speed) * max_speed_;
|
||||
boid->setVelocity(vx, vy);
|
||||
}
|
||||
|
||||
// FASE 1.2: Aplicar velocidad mínima (evitar boids estáticos)
|
||||
if (speed > 0.0f && speed < BOID_MIN_SPEED) {
|
||||
vx = (vx / speed) * BOID_MIN_SPEED;
|
||||
vy = (vy / speed) * BOID_MIN_SPEED;
|
||||
if (speed > 0.0f && speed < min_speed_) {
|
||||
vx = (vx / speed) * min_speed_;
|
||||
vy = (vy / speed) * min_speed_;
|
||||
boid->setVelocity(vx, vy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +103,24 @@ class BoidManager {
|
||||
// FASE 2: Grid reutilizable para búsquedas de vecinos
|
||||
SpatialGrid spatial_grid_;
|
||||
|
||||
// === Parámetros ajustables en runtime (inicializados con valores de defines.h) ===
|
||||
// Permite modificar comportamiento sin recompilar (para tweaking/debug visual)
|
||||
float separation_radius_; // Radio de separación (evitar colisiones)
|
||||
float alignment_radius_; // Radio de alineación (matching de velocidad)
|
||||
float cohesion_radius_; // Radio de cohesión (centro de masa)
|
||||
float separation_weight_; // Peso fuerza de separación (aceleración px/s²)
|
||||
float alignment_weight_; // Peso fuerza de alineación (steering proporcional)
|
||||
float cohesion_weight_; // Peso fuerza de cohesión (aceleración px/s²)
|
||||
float max_speed_; // Velocidad máxima (px/s)
|
||||
float min_speed_; // Velocidad mínima (px/s)
|
||||
float max_force_; // Fuerza máxima de steering (px/s)
|
||||
float boundary_margin_; // Margen para repulsión de bordes (px)
|
||||
float boundary_weight_; // Peso fuerza de repulsión de bordes (aceleración px/s²)
|
||||
|
||||
// Métodos privados para las reglas de Reynolds
|
||||
void applySeparation(Ball* boid, float delta_time);
|
||||
void applyAlignment(Ball* boid, float delta_time);
|
||||
void applyCohesion(Ball* boid, float delta_time);
|
||||
void applyBoundaries(Ball* boid); // Mantener boids dentro de pantalla
|
||||
void applyBoundaries(Ball* boid); // Repulsión de bordes (ya no wrapping)
|
||||
void limitSpeed(Ball* boid); // Limitar velocidad máxima
|
||||
};
|
||||
|
||||
@@ -288,17 +288,31 @@ constexpr float LOGO_FLIP_TRIGGER_MIN = 0.20f; // 20% mínimo de progres
|
||||
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"
|
||||
|
||||
// Configuración de AppLogo (logo periódico en pantalla)
|
||||
constexpr float APPLOGO_DISPLAY_INTERVAL = 20.0f; // Intervalo entre apariciones del logo (segundos)
|
||||
constexpr float APPLOGO_DISPLAY_DURATION = 5.0f; // Duración de visibilidad del logo (segundos)
|
||||
constexpr float APPLOGO_FADE_DURATION = 0.5f; // Duración del fade in/out (segundos)
|
||||
constexpr float APPLOGO_HEIGHT_PERCENT = 0.4f; // Altura del logo = 40% de la altura de pantalla
|
||||
constexpr float APPLOGO_PADDING_PERCENT = 0.1f; // Padding desde esquina inferior-derecha = 10%
|
||||
|
||||
// Configuración de Modo BOIDS (comportamiento de enjambre)
|
||||
// FASE 1.1 REVISADA: Parámetros ajustados tras detectar cohesión mal normalizada
|
||||
// TIME-BASED CONVERSION (frame-based → time-based):
|
||||
// - Radios: sin cambios (píxeles)
|
||||
// - Velocidades (MAX_SPEED, MIN_SPEED): ×60 (px/frame → px/s)
|
||||
// - Aceleraciones puras (SEPARATION, COHESION): ×60² = ×3600 (px/frame² → px/s²)
|
||||
// - Steering proporcional (ALIGNMENT): ×60 (proporcional a velocidad)
|
||||
// - Límite velocidad (MAX_FORCE): ×60 (px/frame → px/s)
|
||||
constexpr float BOID_SEPARATION_RADIUS = 30.0f; // Radio para evitar colisiones (píxeles)
|
||||
constexpr float BOID_ALIGNMENT_RADIUS = 50.0f; // Radio para alinear velocidad con vecinos
|
||||
constexpr float BOID_COHESION_RADIUS = 80.0f; // Radio para moverse hacia centro del grupo
|
||||
constexpr float BOID_SEPARATION_WEIGHT = 1.5f; // Peso de separación
|
||||
constexpr float BOID_ALIGNMENT_WEIGHT = 1.0f; // Peso de alineación
|
||||
constexpr float BOID_COHESION_WEIGHT = 0.001f; // Peso de cohesión (MICRO - 1000x menor por falta de normalización)
|
||||
constexpr float BOID_MAX_SPEED = 2.5f; // Velocidad máxima (píxeles/frame - REDUCIDA)
|
||||
constexpr float BOID_MAX_FORCE = 0.05f; // Fuerza máxima de steering (REDUCIDA para evitar aceleración excesiva)
|
||||
constexpr float BOID_MIN_SPEED = 0.3f; // Velocidad mínima (evita boids estáticos)
|
||||
constexpr float BOID_SEPARATION_WEIGHT = 5400.0f; // Aceleración de separación (px/s²) [era 1.5 × 3600]
|
||||
constexpr float BOID_ALIGNMENT_WEIGHT = 60.0f; // Steering de alineación (proporcional) [era 1.0 × 60]
|
||||
constexpr float BOID_COHESION_WEIGHT = 3.6f; // Aceleración de cohesión (px/s²) [era 0.001 × 3600]
|
||||
constexpr float BOID_MAX_SPEED = 150.0f; // Velocidad máxima (px/s) [era 2.5 × 60]
|
||||
constexpr float BOID_MAX_FORCE = 3.0f; // Fuerza máxima de steering (px/s) [era 0.05 × 60]
|
||||
constexpr float BOID_MIN_SPEED = 18.0f; // Velocidad mínima (px/s) [era 0.3 × 60]
|
||||
constexpr float BOID_BOUNDARY_MARGIN = 50.0f; // Distancia a borde para activar repulsión (píxeles)
|
||||
constexpr float BOID_BOUNDARY_WEIGHT = 7200.0f; // Aceleración de repulsión de bordes (px/s²) [más fuerte que separation]
|
||||
|
||||
// FASE 2: Spatial Hash Grid para optimización O(n²) → O(n)
|
||||
constexpr float BOID_GRID_CELL_SIZE = 100.0f; // Tamaño de celda del grid (píxeles)
|
||||
|
||||
@@ -254,6 +254,14 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
|
||||
boid_manager_ = std::make_unique<BoidManager>();
|
||||
boid_manager_->initialize(this, scene_manager_.get(), ui_manager_.get(), state_manager_.get(),
|
||||
current_screen_width_, current_screen_height_);
|
||||
|
||||
// Inicializar AppLogo (logo periódico en pantalla)
|
||||
app_logo_ = std::make_unique<AppLogo>();
|
||||
if (!app_logo_->initialize(renderer_, current_screen_width_, current_screen_height_)) {
|
||||
std::cerr << "Advertencia: No se pudo inicializar AppLogo (logo periódico)" << std::endl;
|
||||
// No es crítico, continuar sin logo
|
||||
app_logo_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -334,6 +342,11 @@ void Engine::update() {
|
||||
|
||||
// Actualizar transiciones de temas (delegado a ThemeManager)
|
||||
theme_manager_->update(delta_time_);
|
||||
|
||||
// Actualizar AppLogo (logo periódico)
|
||||
if (app_logo_) {
|
||||
app_logo_->update(delta_time_, state_manager_->getCurrentMode());
|
||||
}
|
||||
}
|
||||
|
||||
// === IMPLEMENTACIÓN DE MÉTODOS PÚBLICOS PARA INPUT HANDLER ===
|
||||
@@ -343,7 +356,7 @@ void Engine::handleGravityToggle() {
|
||||
// Si estamos en modo boids, salir a modo física CON GRAVEDAD OFF
|
||||
// Según RULES.md: "BOIDS a PHYSICS: Pulsando la tecla G: Gravedad OFF"
|
||||
if (current_mode_ == SimulationMode::BOIDS) {
|
||||
toggleBoidsMode(); // Cambiar a PHYSICS (preserva inercia, gravedad ya está OFF desde activateBoids)
|
||||
toggleBoidsMode(false); // Cambiar a PHYSICS sin activar gravedad (preserva inercia)
|
||||
// NO llamar a forceBallsGravityOff() porque aplica impulsos que destruyen la inercia de BOIDS
|
||||
// La gravedad ya está desactivada por BoidManager::activateBoids() y se mantiene al salir
|
||||
showNotificationForAction("Modo Física - Gravedad Off");
|
||||
@@ -364,18 +377,19 @@ void Engine::handleGravityToggle() {
|
||||
}
|
||||
|
||||
void Engine::handleGravityDirectionChange(GravityDirection direction, const char* notification_text) {
|
||||
// Si estamos en modo boids, salir a modo física primero
|
||||
// Si estamos en modo boids, salir a modo física primero PRESERVANDO VELOCIDAD
|
||||
if (current_mode_ == SimulationMode::BOIDS) {
|
||||
toggleBoidsMode(); // Esto cambia a PHYSICS y activa gravedad
|
||||
// Continuar para aplicar la dirección de gravedad
|
||||
current_mode_ = SimulationMode::PHYSICS;
|
||||
boid_manager_->deactivateBoids(false); // NO activar gravedad aún (preservar momentum)
|
||||
scene_manager_->forceBallsGravityOn(); // Activar gravedad SIN impulsos (preserva velocidad)
|
||||
}
|
||||
|
||||
// Si estamos en modo figura, salir a modo física CON gravedad
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
else if (current_mode_ == SimulationMode::SHAPE) {
|
||||
toggleShapeModeInternal(); // Desactivar figura (activa gravedad automáticamente)
|
||||
} else {
|
||||
scene_manager_->enableBallsGravityIfDisabled(); // Reactivar gravedad si estaba OFF
|
||||
}
|
||||
|
||||
scene_manager_->changeGravityDirection(direction);
|
||||
showNotificationForAction(notification_text);
|
||||
}
|
||||
@@ -435,11 +449,11 @@ void Engine::toggleDepthZoom() {
|
||||
}
|
||||
|
||||
// Boids (comportamiento de enjambre)
|
||||
void Engine::toggleBoidsMode() {
|
||||
void Engine::toggleBoidsMode(bool force_gravity_on) {
|
||||
if (current_mode_ == SimulationMode::BOIDS) {
|
||||
// Salir del modo boids
|
||||
// Salir del modo boids (velocidades ya son time-based, no requiere conversión)
|
||||
current_mode_ = SimulationMode::PHYSICS;
|
||||
boid_manager_->deactivateBoids();
|
||||
boid_manager_->deactivateBoids(force_gravity_on); // Pasar parámetro para control preciso
|
||||
} else {
|
||||
// Entrar al modo boids (desde PHYSICS o SHAPE)
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
@@ -522,6 +536,11 @@ void Engine::changeScenario(int scenario_id, const char* notification_text) {
|
||||
}
|
||||
}
|
||||
|
||||
// Si estamos en modo BOIDS, desactivar gravedad (modo BOIDS = gravedad OFF siempre)
|
||||
if (current_mode_ == SimulationMode::BOIDS) {
|
||||
scene_manager_->forceBallsGravityOff();
|
||||
}
|
||||
|
||||
showNotificationForAction(notification_text);
|
||||
}
|
||||
|
||||
@@ -710,6 +729,11 @@ void Engine::render() {
|
||||
active_shape_.get(), shape_convergence_,
|
||||
physical_window_width_, physical_window_height_, current_screen_width_);
|
||||
|
||||
// Renderizar AppLogo (logo periódico) - después de UI, antes de present
|
||||
if (app_logo_) {
|
||||
app_logo_->render();
|
||||
}
|
||||
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
@@ -791,6 +815,11 @@ void Engine::toggleRealFullscreen() {
|
||||
// Actualizar tamaño de pantalla para boids (wrapping boundaries)
|
||||
boid_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||
|
||||
// Actualizar AppLogo con nueva resolución
|
||||
if (app_logo_) {
|
||||
app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||
}
|
||||
|
||||
// Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla
|
||||
@@ -824,6 +853,11 @@ void Engine::toggleRealFullscreen() {
|
||||
scene_manager_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||
scene_manager_->changeScenario(scene_manager_->getCurrentScenario(), current_mode_);
|
||||
|
||||
// Actualizar AppLogo con resolución restaurada
|
||||
if (app_logo_) {
|
||||
app_logo_->updateScreenSize(current_screen_width_, current_screen_height_);
|
||||
}
|
||||
|
||||
// Si estamos en modo SHAPE, regenerar la figura con nuevas dimensiones
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
generateShape(); // Regenerar figura con nuevas dimensiones de pantalla
|
||||
@@ -1359,6 +1393,18 @@ void Engine::executeDemoAction(bool is_lite) {
|
||||
int valid_scenarios[] = {1, 2, 3, 4, 5};
|
||||
int new_scenario = valid_scenarios[rand() % 5];
|
||||
scene_manager_->changeScenario(new_scenario, current_mode_);
|
||||
|
||||
// Si estamos en modo SHAPE, regenerar la figura con nuevo número de pelotas
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
generateShape();
|
||||
|
||||
// Activar atracción física en las bolas nuevas (crítico tras changeScenario)
|
||||
auto& balls = scene_manager_->getBallsMutable();
|
||||
for (auto& ball : balls) {
|
||||
ball->enableShapeAttraction(true);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1573,6 +1619,15 @@ void Engine::executeExitLogoMode() {
|
||||
clampShapeScale();
|
||||
generateShape();
|
||||
|
||||
// Activar atracción física si estamos en modo SHAPE
|
||||
// (crítico para que las bolas se muevan hacia la figura restaurada)
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
auto& balls = scene_manager_->getBallsMutable();
|
||||
for (auto& ball : balls) {
|
||||
ball->enableShapeAttraction(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Desactivar modo LOGO en PNG_SHAPE (volver a flip intervals normales)
|
||||
if (active_shape_) {
|
||||
PNGShape* png_shape = dynamic_cast<PNGShape*>(active_shape_.get());
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "app_logo.h" // for AppLogo
|
||||
#include "ball.h" // for Ball
|
||||
#include "boids_mgr/boid_manager.h" // for BoidManager
|
||||
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
|
||||
@@ -49,7 +50,7 @@ class Engine {
|
||||
void toggleDepthZoom();
|
||||
|
||||
// Boids (comportamiento de enjambre)
|
||||
void toggleBoidsMode();
|
||||
void toggleBoidsMode(bool force_gravity_on = true);
|
||||
|
||||
// Temas de colores
|
||||
void cycleTheme(bool forward);
|
||||
@@ -105,6 +106,7 @@ class Engine {
|
||||
std::unique_ptr<BoidManager> boid_manager_; // Gestión de comportamiento boids
|
||||
std::unique_ptr<StateManager> state_manager_; // Gestión de estados (DEMO/LOGO)
|
||||
std::unique_ptr<UIManager> ui_manager_; // Gestión de UI (HUD, FPS, notificaciones)
|
||||
std::unique_ptr<AppLogo> app_logo_; // Gestión de logo periódico en pantalla
|
||||
|
||||
// Recursos SDL
|
||||
SDL_Window* window_ = nullptr;
|
||||
@@ -160,7 +162,6 @@ class Engine {
|
||||
// Sistema de Modo DEMO (auto-play) y LOGO
|
||||
// NOTA: Engine mantiene estado de implementación para callbacks performLogoAction()
|
||||
// StateManager coordina los triggers y timers, Engine ejecuta las acciones
|
||||
AppMode previous_app_mode_ = AppMode::SANDBOX; // Modo previo antes de entrar a LOGO
|
||||
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)
|
||||
|
||||
|
||||
78
source/external/dbgtxt.h
vendored
78
source/external/dbgtxt.h
vendored
@@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace {
|
||||
SDL_Texture* dbg_tex = nullptr;
|
||||
SDL_Renderer* dbg_ren = nullptr;
|
||||
} // namespace
|
||||
|
||||
inline void dbg_init(SDL_Renderer* renderer) {
|
||||
dbg_ren = renderer;
|
||||
Uint8 font[448] = {0x42, 0x4D, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x01, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x18, 0xF3, 0x83, 0x83, 0xCF, 0x83, 0x87, 0x00, 0x00, 0xF3, 0x39, 0x39, 0xCF, 0x79, 0xF3, 0x00, 0x00, 0x01, 0xF9, 0x39, 0xCF, 0x61, 0xF9, 0x00, 0x00, 0x33, 0xF9, 0x03, 0xE7, 0x87, 0x81, 0x00, 0x00, 0x93, 0x03, 0x3F, 0xF3, 0x1B, 0x39, 0x00, 0x00, 0xC3, 0x3F, 0x9F, 0x39, 0x3B, 0x39, 0x00, 0x41, 0xE3, 0x03, 0xC3, 0x01, 0x87, 0x83, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE7, 0x01, 0xC7, 0x81, 0x01, 0x83, 0x00, 0x00, 0xE7, 0x1F, 0x9B, 0xE7, 0x1F, 0x39, 0x00, 0x00, 0xE7, 0x8F, 0x39, 0xE7, 0x87, 0xF9, 0x00, 0x00, 0xC3, 0xC7, 0x39, 0xE7, 0xC3, 0xC3, 0x00, 0x00, 0x99, 0xE3, 0x39, 0xE7, 0xF1, 0xE7, 0x00, 0x00, 0x99, 0xF1, 0xB3, 0xC7, 0x39, 0xF3, 0x00, 0x00, 0x99, 0x01, 0xC7, 0xE7, 0x83, 0x81, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x83, 0xE7, 0x83, 0xEF, 0x39, 0x39, 0x00, 0x00, 0x39, 0xE7, 0x39, 0xC7, 0x11, 0x11, 0x00, 0x00, 0xF9, 0xE7, 0x39, 0x83, 0x01, 0x83, 0x00, 0x00, 0x83, 0xE7, 0x39, 0x11, 0x01, 0xC7, 0x00, 0x00, 0x3F, 0xE7, 0x39, 0x39, 0x29, 0x83, 0x00, 0x00, 0x33, 0xE7, 0x39, 0x39, 0x39, 0x11, 0x00, 0x00, 0x87, 0x81, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x39, 0x39, 0x83, 0x3F, 0x85, 0x31, 0x00, 0x00, 0x39, 0x31, 0x39, 0x3F, 0x33, 0x23, 0x00, 0x00, 0x29, 0x21, 0x39, 0x03, 0x21, 0x07, 0x00, 0x00, 0x01, 0x01, 0x39, 0x39, 0x39, 0x31, 0x00, 0x00, 0x01, 0x09, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0x11, 0x19, 0x39, 0x39, 0x39, 0x39, 0x00, 0x00, 0x39, 0x39, 0x83, 0x03, 0x83, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xC1, 0x39, 0x81, 0x83, 0x31, 0x01, 0x00, 0x00, 0x99, 0x39, 0xE7, 0x39, 0x23, 0x3F, 0x00, 0x00, 0x39, 0x39, 0xE7, 0xF9, 0x07, 0x3F, 0x00, 0x00, 0x31, 0x01, 0xE7, 0xF9, 0x0F, 0x3F, 0x00, 0x00, 0x3F, 0x39, 0xE7, 0xF9, 0x27, 0x3F, 0x00, 0x00, 0x9F, 0x39, 0xE7, 0xF9, 0x33, 0x3F, 0x00, 0x00, 0xC1, 0x39, 0x81, 0xF9, 0x39, 0x3F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x39, 0x03, 0xC3, 0x07, 0x01, 0x3F, 0x00, 0x00, 0x39, 0x39, 0x99, 0x33, 0x3F, 0x3F, 0x00, 0x00, 0x01, 0x39, 0x3F, 0x39, 0x3F, 0x3F, 0x00, 0x00, 0x39, 0x03, 0x3F, 0x39, 0x03, 0x03, 0x00, 0x00, 0x39, 0x39, 0x3F, 0x39, 0x3F, 0x3F, 0x00, 0x00, 0x93, 0x39, 0x99, 0x33, 0x3F, 0x3F, 0x00, 0x00, 0xC7, 0x03, 0xC3, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
// Cargar surface del bitmap font
|
||||
SDL_Surface* font_surface = SDL_LoadBMP_IO(SDL_IOFromMem(font, 448), 1);
|
||||
if (font_surface != nullptr) {
|
||||
// Crear una nueva surface de 32 bits con canal alpha
|
||||
SDL_Surface* rgba_surface = SDL_CreateSurface(font_surface->w, font_surface->h, SDL_PIXELFORMAT_RGBA8888);
|
||||
if (rgba_surface != nullptr) {
|
||||
// Obtener píxeles de ambas surfaces
|
||||
Uint8* src_pixels = (Uint8*)font_surface->pixels;
|
||||
Uint32* dst_pixels = (Uint32*)rgba_surface->pixels;
|
||||
|
||||
int width = font_surface->w;
|
||||
int height = font_surface->h;
|
||||
|
||||
// Procesar cada píxel
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int byte_index = y * font_surface->pitch + (x / 8);
|
||||
int bit_index = 7 - (x % 8);
|
||||
|
||||
// Extraer bit del bitmap monocromo
|
||||
bool is_white = (src_pixels[byte_index] >> bit_index) & 1;
|
||||
|
||||
if (is_white) // Fondo blanco original -> transparente
|
||||
{
|
||||
dst_pixels[y * width + x] = 0x00000000; // Transparente
|
||||
} else // Texto negro original -> blanco opaco
|
||||
{
|
||||
dst_pixels[y * width + x] = 0xFFFFFFFF; // Blanco opaco
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbg_tex = SDL_CreateTextureFromSurface(dbg_ren, rgba_surface);
|
||||
SDL_DestroySurface(rgba_surface);
|
||||
}
|
||||
SDL_DestroySurface(font_surface);
|
||||
}
|
||||
|
||||
// Configurar filtro nearest neighbor para píxel perfect del texto
|
||||
if (dbg_tex != nullptr) {
|
||||
SDL_SetTextureScaleMode(dbg_tex, SDL_SCALEMODE_NEAREST);
|
||||
// Configurar blend mode para transparencia normal
|
||||
SDL_SetTextureBlendMode(dbg_tex, SDL_BLENDMODE_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
inline void dbg_print(int x, int y, const char* text, Uint8 r, Uint8 g, Uint8 b) {
|
||||
int cc = 0;
|
||||
SDL_SetTextureColorMod(dbg_tex, r, g, b);
|
||||
SDL_FRect src = {0, 0, 8, 8};
|
||||
SDL_FRect dst = {static_cast<float>(x), static_cast<float>(y), 8, 8};
|
||||
while (text[cc] != 0) {
|
||||
if (text[cc] != 32) {
|
||||
if (text[cc] >= 65) {
|
||||
src.x = ((text[cc] - 65) % 6) * 8;
|
||||
src.y = ((text[cc] - 65) / 6) * 8;
|
||||
} else {
|
||||
src.x = ((text[cc] - 22) % 6) * 8;
|
||||
src.y = ((text[cc] - 22) / 6) * 8;
|
||||
}
|
||||
|
||||
SDL_RenderTexture(dbg_ren, dbg_tex, &src, &dst);
|
||||
}
|
||||
cc++;
|
||||
dst.x += 8;
|
||||
}
|
||||
}
|
||||
17
source/external/texture.cpp
vendored
17
source/external/texture.cpp
vendored
@@ -128,6 +128,9 @@ bool Texture::loadFromFile(const std::string &file_path) {
|
||||
|
||||
// Configurar filtro nearest neighbor para píxel perfect
|
||||
SDL_SetTextureScaleMode(new_texture, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
// Habilitar alpha blending para transparencias
|
||||
SDL_SetTextureBlendMode(new_texture, SDL_BLENDMODE_BLEND);
|
||||
}
|
||||
|
||||
// Destruye la superficie cargada
|
||||
@@ -169,3 +172,17 @@ int Texture::getHeight() {
|
||||
void Texture::setColor(int r, int g, int b) {
|
||||
SDL_SetTextureColorMod(texture_, r, g, b);
|
||||
}
|
||||
|
||||
// Modula el alpha de la textura
|
||||
void Texture::setAlpha(int alpha) {
|
||||
if (texture_ != nullptr) {
|
||||
SDL_SetTextureAlphaMod(texture_, static_cast<Uint8>(alpha));
|
||||
}
|
||||
}
|
||||
|
||||
// Configurar modo de escalado
|
||||
void Texture::setScaleMode(SDL_ScaleMode mode) {
|
||||
if (texture_ != nullptr) {
|
||||
SDL_SetTextureScaleMode(texture_, mode);
|
||||
}
|
||||
}
|
||||
|
||||
6
source/external/texture.h
vendored
6
source/external/texture.h
vendored
@@ -44,6 +44,12 @@ class Texture {
|
||||
// Modula el color de la textura
|
||||
void setColor(int r, int g, int b);
|
||||
|
||||
// Modula el alpha (transparencia) de la textura
|
||||
void setAlpha(int alpha);
|
||||
|
||||
// Configurar modo de escalado (NEAREST para pixel art, LINEAR para suavizado)
|
||||
void setScaleMode(SDL_ScaleMode mode);
|
||||
|
||||
// Getter para batch rendering
|
||||
SDL_Texture *getSDLTexture() const { return texture_; }
|
||||
};
|
||||
|
||||
@@ -76,8 +76,10 @@ void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) {
|
||||
|
||||
// Si estamos en LOGO MODE, generar threshold aleatorio de convergencia (75-100%)
|
||||
if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO) {
|
||||
/*
|
||||
float 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 {
|
||||
|
||||
@@ -88,9 +88,6 @@ HelpOverlay::~HelpOverlay() {
|
||||
|
||||
void HelpOverlay::toggle() {
|
||||
visible_ = !visible_;
|
||||
SDL_Log("HelpOverlay::toggle() - visible=%s, box_pos=(%d,%d), box_size=%dx%d, physical=%dx%d",
|
||||
visible_ ? "TRUE" : "FALSE", box_x_, box_y_, box_width_, box_height_,
|
||||
physical_width_, physical_height_);
|
||||
}
|
||||
|
||||
void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, int physical_width, int physical_height, int font_size) {
|
||||
@@ -103,12 +100,7 @@ void HelpOverlay::initialize(SDL_Renderer* renderer, ThemeManager* theme_mgr, in
|
||||
text_renderer_ = new TextRenderer();
|
||||
text_renderer_->init(renderer, "data/fonts/FunnelSans-Regular.ttf", font_size, true);
|
||||
|
||||
SDL_Log("HelpOverlay::initialize() - physical=%dx%d, font_size=%d", physical_width, physical_height, font_size);
|
||||
|
||||
calculateBoxDimensions();
|
||||
|
||||
SDL_Log("HelpOverlay::initialize() - AFTER calculateBoxDimensions: box_pos=(%d,%d), box_size=%dx%d",
|
||||
box_x_, box_y_, box_width_, box_height_);
|
||||
}
|
||||
|
||||
void HelpOverlay::updatePhysicalWindowSize(int physical_width, int physical_height) {
|
||||
@@ -135,11 +127,6 @@ void HelpOverlay::reinitializeFontSize(int new_font_size) {
|
||||
}
|
||||
|
||||
void HelpOverlay::updateAll(int font_size, int physical_width, int physical_height) {
|
||||
SDL_Log("HelpOverlay::updateAll() - INPUT: font_size=%d, physical=%dx%d",
|
||||
font_size, physical_width, physical_height);
|
||||
SDL_Log("HelpOverlay::updateAll() - BEFORE: box_pos=(%d,%d), box_size=%dx%d",
|
||||
box_x_, box_y_, box_width_, box_height_);
|
||||
|
||||
// Actualizar dimensiones físicas PRIMERO
|
||||
physical_width_ = physical_width;
|
||||
physical_height_ = physical_height;
|
||||
@@ -154,9 +141,6 @@ void HelpOverlay::updateAll(int font_size, int physical_width, int physical_heig
|
||||
|
||||
// Marcar textura para regeneración completa
|
||||
texture_needs_rebuild_ = true;
|
||||
|
||||
SDL_Log("HelpOverlay::updateAll() - AFTER: box_pos=(%d,%d), box_size=%dx%d",
|
||||
box_x_, box_y_, box_width_, box_height_);
|
||||
}
|
||||
|
||||
void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
|
||||
@@ -246,15 +230,10 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
|
||||
}
|
||||
|
||||
void HelpOverlay::calculateBoxDimensions() {
|
||||
SDL_Log("HelpOverlay::calculateBoxDimensions() START - physical=%dx%d", physical_width_, physical_height_);
|
||||
|
||||
// Calcular dimensiones necesarias según el texto
|
||||
int text_width, text_height;
|
||||
calculateTextDimensions(text_width, text_height);
|
||||
|
||||
SDL_Log("HelpOverlay::calculateBoxDimensions() - text_width=%d, text_height=%d, col1_width=%d, col2_width=%d",
|
||||
text_width, text_height, column1_width_, column2_width_);
|
||||
|
||||
// Usar directamente el ancho y altura calculados según el contenido
|
||||
box_width_ = text_width;
|
||||
|
||||
@@ -265,17 +244,11 @@ void HelpOverlay::calculateBoxDimensions() {
|
||||
// Centrar en pantalla
|
||||
box_x_ = (physical_width_ - box_width_) / 2;
|
||||
box_y_ = (physical_height_ - box_height_) / 2;
|
||||
|
||||
SDL_Log("HelpOverlay::calculateBoxDimensions() END - box_pos=(%d,%d), box_size=%dx%d, max_height=%d",
|
||||
box_x_, box_y_, box_width_, box_height_, max_height);
|
||||
}
|
||||
|
||||
void HelpOverlay::rebuildCachedTexture() {
|
||||
if (!renderer_ || !theme_mgr_ || !text_renderer_) return;
|
||||
|
||||
SDL_Log("HelpOverlay::rebuildCachedTexture() - Regenerando textura: box_size=%dx%d, box_pos=(%d,%d)",
|
||||
box_width_, box_height_, box_x_, box_y_);
|
||||
|
||||
// Destruir textura anterior si existe
|
||||
if (cached_texture_) {
|
||||
SDL_DestroyTexture(cached_texture_);
|
||||
@@ -471,9 +444,6 @@ void HelpOverlay::render(SDL_Renderer* renderer) {
|
||||
int centered_x = viewport.x + (viewport.w - box_width_) / 2;
|
||||
int centered_y = viewport.y + (viewport.h - box_height_) / 2;
|
||||
|
||||
SDL_Log("HelpOverlay::render() - viewport=(%d,%d,%dx%d), centered_pos=(%d,%d), box_size=%dx%d",
|
||||
viewport.x, viewport.y, viewport.w, viewport.h, centered_x, centered_y, box_width_, box_height_);
|
||||
|
||||
// Renderizar la textura cacheada centrada en el viewport
|
||||
SDL_FRect dest_rect;
|
||||
dest_rect.x = static_cast<float>(centered_x);
|
||||
|
||||
@@ -277,6 +277,27 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
||||
text_renderer_debug_->printAbsolute(margin, left_y, simmode_text.c_str(), {0, 255, 255, 255}); // Cian
|
||||
left_y += line_height;
|
||||
|
||||
// Número de pelotas (escenario actual)
|
||||
size_t ball_count = scene_manager->getBallCount();
|
||||
std::string balls_text;
|
||||
if (ball_count >= 1000) {
|
||||
// Formatear con separador de miles (ejemplo: 5,000 o 50,000)
|
||||
std::string count_str = std::to_string(ball_count);
|
||||
std::string formatted;
|
||||
int digits = count_str.length();
|
||||
for (int i = 0; i < digits; i++) {
|
||||
if (i > 0 && (digits - i) % 3 == 0) {
|
||||
formatted += ',';
|
||||
}
|
||||
formatted += count_str[i];
|
||||
}
|
||||
balls_text = "Balls: " + formatted;
|
||||
} else {
|
||||
balls_text = "Balls: " + std::to_string(ball_count);
|
||||
}
|
||||
text_renderer_debug_->printAbsolute(margin, left_y, balls_text.c_str(), {128, 255, 128, 255}); // Verde claro
|
||||
left_y += line_height;
|
||||
|
||||
// V-Sync
|
||||
text_renderer_debug_->printAbsolute(margin, left_y, vsync_text_.c_str(), {0, 255, 255, 255}); // Cian
|
||||
left_y += line_height;
|
||||
|
||||
Reference in New Issue
Block a user