Añadir parámetro -z/--zoom con validación inteligente

Defaults correctos (sin CLI):
- Resolución: 320x240
- Zoom: 3
- Ventana resultante: 960x720

Nuevas funcionalidades:
- Parámetro -z/--zoom para especificar zoom de ventana
- Si se pasan -w/-h sin -z: zoom automático = 1
- Validación de resolución vs pantalla
- Validación de zoom vs max_zoom calculado

Lógica de validación:
1. Si resolución > pantalla → reset a 320x240 zoom 3
2. Calcular max_zoom = min(screen_w/width, screen_h/height)
3. Si zoom > max_zoom → ajustar a max_zoom
4. Si CLI con -w/-h pero sin -z → zoom = 1 (auto)

Ejemplos:
  ./vibe3_physics                   # 320x240 zoom 3 
  ./vibe3_physics -w 1920 -h 1080   # 1920x1080 zoom 1 
  ./vibe3_physics -w 640 -h 480 -z 2 # 640x480 zoom 2 (1280x960) 
  ./vibe3_physics -w 9999 -h 9999   # Reset a default (warning) 

Archivos:
- defines.h: Renombrar WINDOW_ZOOM → DEFAULT_WINDOW_ZOOM
- main.cpp: Añadir parsing -z/--zoom
- engine.h: initialize() acepta zoom
- engine.cpp: Validación + advertencias informativas

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-04 14:02:02 +02:00
parent 1e5c9f8f9d
commit 723bb6d198
4 changed files with 381 additions and 304 deletions

View File

@@ -5,9 +5,10 @@
// Configuración de ventana y pantalla
constexpr char WINDOW_CAPTION[] = "vibe3_physics";
constexpr int SCREEN_WIDTH = 320; // Ancho de la pantalla lógica (píxeles)
constexpr int SCREEN_HEIGHT = 240; // Alto de la pantalla lógica (píxeles)
constexpr int WINDOW_ZOOM = 3; // Zoom inicial de la ventana
// Resolución por defecto (usada si no se especifica en CLI)
constexpr int DEFAULT_SCREEN_WIDTH = 320; // Ancho lógico por defecto (si no hay -w)
constexpr int DEFAULT_SCREEN_HEIGHT = 240; // Alto lógico por defecto (si no hay -h)
constexpr int DEFAULT_WINDOW_ZOOM = 3; // Zoom inicial de ventana (1x = sin zoom)
// BALL_SIZE eliminado: ahora se obtiene dinámicamente desde texture_->getWidth()
// Configuración de zoom dinámico de ventana

View File

@@ -13,9 +13,9 @@
#include <cstdlib> // for rand, srand
#include <cstring> // for strlen
#include <ctime> // for time
#include <filesystem> // for path operations
#include <iostream> // for cout
#include <string> // for string
#include <filesystem> // for path operations
#ifdef _WIN32
#include <windows.h> // for GetModuleFileName
@@ -24,15 +24,15 @@
#include "ball.h" // for Ball
#include "external/dbgtxt.h" // for dbg_init, dbg_print
#include "external/texture.h" // for Texture
#include "shapes/sphere_shape.h" // for SphereShape
#include "shapes/cube_shape.h" // for CubeShape
#include "shapes/helix_shape.h" // for HelixShape
#include "shapes/wave_grid_shape.h" // for WaveGridShape
#include "shapes/torus_shape.h" // for TorusShape
#include "shapes/cylinder_shape.h" // for CylinderShape
#include "shapes/icosahedron_shape.h" // for IcosahedronShape
#include "shapes/atom_shape.h" // for AtomShape
#include "shapes/cube_shape.h" // for CubeShape
#include "shapes/cylinder_shape.h" // for CylinderShape
#include "shapes/helix_shape.h" // for HelixShape
#include "shapes/icosahedron_shape.h" // for IcosahedronShape
#include "shapes/png_shape.h" // for PNGShape
#include "shapes/sphere_shape.h" // for SphereShape
#include "shapes/torus_shape.h" // for TorusShape
#include "shapes/wave_grid_shape.h" // for WaveGridShape
// Función auxiliar para obtener la ruta del directorio del ejecutable
std::string getExecutableDirectory() {
@@ -48,25 +48,64 @@ std::string getExecutableDirectory() {
}
// Implementación de métodos públicos
bool Engine::initialize(int width, int height, bool fullscreen) {
bool Engine::initialize(int width, int height, int zoom, bool fullscreen) {
bool success = true;
// Usar parámetros o valores por defecto
int window_width = (width > 0) ? width : SCREEN_WIDTH * WINDOW_ZOOM;
int window_height = (height > 0) ? height : SCREEN_HEIGHT * WINDOW_ZOOM;
int logical_width = (width > 0) ? width : SCREEN_WIDTH;
int logical_height = (height > 0) ? height : SCREEN_HEIGHT;
// Obtener resolución de pantalla para validación
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::cout << "¡SDL no se pudo inicializar! Error de SDL: " << SDL_GetError() << std::endl;
return false;
}
// Guardar resolución base (configurada por CLI)
int num_displays = 0;
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
const auto *dm = (displays && num_displays > 0) ? SDL_GetCurrentDisplayMode(displays[0]) : nullptr;
int screen_w = dm ? dm->w : 1920; // Fallback si falla
int screen_h = dm ? dm->h - WINDOW_DECORATION_HEIGHT : 1080;
if (displays) SDL_free(displays);
// Usar parámetros o valores por defecto
int logical_width = (width > 0) ? width : DEFAULT_SCREEN_WIDTH;
int logical_height = (height > 0) ? height : DEFAULT_SCREEN_HEIGHT;
int window_zoom = (zoom > 0) ? zoom : DEFAULT_WINDOW_ZOOM;
// VALIDACIÓN 1: Si resolución > pantalla → reset a default
if (logical_width > screen_w || logical_height > screen_h) {
std::cout << "Advertencia: Resolución " << logical_width << "x" << logical_height
<< " excede pantalla " << screen_w << "x" << screen_h
<< ". Usando default " << DEFAULT_SCREEN_WIDTH << "x" << DEFAULT_SCREEN_HEIGHT << "\n";
logical_width = DEFAULT_SCREEN_WIDTH;
logical_height = DEFAULT_SCREEN_HEIGHT;
window_zoom = DEFAULT_WINDOW_ZOOM; // Reset zoom también
}
// VALIDACIÓN 2: Calcular max_zoom y ajustar si es necesario
int max_zoom = std::min(screen_w / logical_width, screen_h / logical_height);
if (window_zoom > max_zoom) {
std::cout << "Advertencia: Zoom " << window_zoom << " excede máximo " << max_zoom
<< " para " << logical_width << "x" << logical_height << ". Ajustando a " << max_zoom << "\n";
window_zoom = max_zoom;
}
// Si se especificaron parámetros CLI y zoom no se especificó, usar zoom=1
if ((width > 0 || height > 0) && zoom == 0) {
window_zoom = 1;
}
// Calcular tamaño de ventana
int window_width = logical_width * window_zoom;
int window_height = logical_height * window_zoom;
// Guardar resolución base (configurada por CLI o default)
base_screen_width_ = logical_width;
base_screen_height_ = logical_height;
current_screen_width_ = logical_width;
current_screen_height_ = logical_height;
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::cout << "¡SDL no se pudo inicializar! Error de SDL: " << SDL_GetError() << std::endl;
success = false;
} else {
// SDL ya inicializado arriba para validación
{
// Crear ventana principal (fullscreen si se especifica)
Uint32 window_flags = SDL_WINDOW_OPENGL;
if (fullscreen) {
@@ -217,7 +256,7 @@ void Engine::update() {
// Bifurcar actualización según modo activo
if (current_mode_ == SimulationMode::PHYSICS) {
// Modo física normal: actualizar física de cada pelota
for (auto &ball : balls_) {
for (auto& ball : balls_) {
ball->update(delta_time_); // Pasar delta time a cada pelota
}
} else if (current_mode_ == SimulationMode::SHAPE) {
@@ -613,8 +652,7 @@ void Engine::render() {
}
// Ordenar índices por profundidad Z (menor primero = fondo primero)
std::sort(render_order.begin(), render_order.end(),
[this](size_t a, size_t b) {
std::sort(render_order.begin(), render_order.end(), [this](size_t a, size_t b) {
return balls_[a]->getDepthBrightness() < balls_[b]->getDepthBrightness();
});
@@ -626,8 +664,7 @@ void Engine::render() {
float depth_scale = balls_[idx]->getDepthScale();
// Mapear brightness de 0-1 a rango MIN-MAX
float brightness_factor = (ROTOBALL_MIN_BRIGHTNESS + brightness *
(ROTOBALL_MAX_BRIGHTNESS - ROTOBALL_MIN_BRIGHTNESS)) / 255.0f;
float brightness_factor = (ROTOBALL_MIN_BRIGHTNESS + brightness * (ROTOBALL_MAX_BRIGHTNESS - ROTOBALL_MIN_BRIGHTNESS)) / 255.0f;
// Aplicar factor de brillo al color
int r_mod = static_cast<int>(color.r * brightness_factor);
@@ -639,7 +676,7 @@ void Engine::render() {
} else {
// MODO PHYSICS: Renderizar en orden normal del vector (sin escala de profundidad)
size_t idx = 0;
for (auto &ball : balls_) {
for (auto& ball : balls_) {
SDL_FRect pos = ball->getPosition();
Color color = getInterpolatedColor(idx); // Usar color interpolado (LERP)
addSpriteToBatch(pos.x, pos.y, pos.w, pos.h, color.r, color.g, color.b, 1.0f);
@@ -750,7 +787,7 @@ void Engine::initBalls(int value) {
const float VX = (((rand() % 20) + 10) * 0.1f) * SIGN; // Velocidad en X
const float VY = ((rand() % 60) - 30) * 0.1f; // Velocidad en Y
// Seleccionar color de la paleta del tema actual
ThemeColors &theme = themes_[static_cast<int>(current_theme_)];
ThemeColors& theme = themes_[static_cast<int>(current_theme_)];
int color_index = rand() % theme.ball_colors.size(); // Cantidad variable de colores por tema
const Color COLOR = theme.ball_colors[color_index];
// Generar factor de masa aleatorio (0.7 = ligera, 1.3 = pesada)
@@ -776,7 +813,7 @@ void Engine::setText() {
}
void Engine::pushBallsAwayFromGravity() {
for (auto &ball : balls_) {
for (auto& ball : balls_) {
const int SIGNO = ((rand() % 2) * 2) - 1;
const float LATERAL = (((rand() % 20) + 10) * 0.1f) * SIGNO;
const float MAIN = ((rand() % 40) * 0.1f) + 5;
@@ -805,32 +842,32 @@ void Engine::pushBallsAwayFromGravity() {
}
void Engine::switchBallsGravity() {
for (auto &ball : balls_) {
for (auto& ball : balls_) {
ball->switchGravity();
}
}
void Engine::enableBallsGravityIfDisabled() {
for (auto &ball : balls_) {
for (auto& ball : balls_) {
ball->enableGravityIfDisabled();
}
}
void Engine::forceBallsGravityOn() {
for (auto &ball : balls_) {
for (auto& ball : balls_) {
ball->forceGravityOn();
}
}
void Engine::forceBallsGravityOff() {
for (auto &ball : balls_) {
for (auto& ball : balls_) {
ball->forceGravityOff();
}
}
void Engine::changeGravityDirection(GravityDirection direction) {
current_gravity_ = direction;
for (auto &ball : balls_) {
for (auto& ball : balls_) {
ball->setGravityDirection(direction);
ball->applyRandomLateralPush(); // Aplicar empuje lateral aleatorio
}
@@ -873,9 +910,9 @@ void Engine::toggleRealFullscreen() {
if (real_fullscreen_enabled_) {
// Obtener resolución del escritorio
int num_displays = 0;
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
if (displays != nullptr && num_displays > 0) {
const auto *dm = SDL_GetCurrentDisplayMode(displays[0]);
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
if (dm != nullptr) {
// Cambiar a resolución nativa del escritorio
current_screen_width_ = dm->w;
@@ -903,7 +940,7 @@ void Engine::toggleRealFullscreen() {
// Restaurar ventana normal
SDL_SetWindowFullscreen(window_, false);
SDL_SetWindowSize(window_, base_screen_width_ * WINDOW_ZOOM, base_screen_height_ * WINDOW_ZOOM);
SDL_SetWindowSize(window_, base_screen_width_ * DEFAULT_WINDOW_ZOOM, base_screen_height_ * DEFAULT_WINDOW_ZOOM);
// Restaurar presentación lógica base
SDL_SetRenderLogicalPresentation(renderer_, base_screen_width_, base_screen_height_, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
@@ -966,11 +1003,16 @@ void Engine::toggleIntegerScaling() {
std::string Engine::gravityDirectionToString(GravityDirection direction) const {
switch (direction) {
case GravityDirection::DOWN: return "DOWN";
case GravityDirection::UP: return "UP";
case GravityDirection::LEFT: return "LEFT";
case GravityDirection::RIGHT: return "RIGHT";
default: return "UNKNOWN";
case GravityDirection::DOWN:
return "DOWN";
case GravityDirection::UP:
return "UP";
case GravityDirection::LEFT:
return "LEFT";
case GravityDirection::RIGHT:
return "RIGHT";
default:
return "UNKNOWN";
}
}
@@ -983,8 +1025,8 @@ void Engine::renderGradientBackground() {
if (transitioning_) {
// Interpolar entre tema actual y tema destino
ThemeColors &current = themes_[static_cast<int>(current_theme_)];
ThemeColors &target = themes_[static_cast<int>(target_theme_)];
ThemeColors& current = themes_[static_cast<int>(current_theme_)];
ThemeColors& target = themes_[static_cast<int>(target_theme_)];
top_r = lerp(current.bg_top_r, target.bg_top_r, transition_progress_);
top_g = lerp(current.bg_top_g, target.bg_top_g, transition_progress_);
@@ -995,7 +1037,7 @@ void Engine::renderGradientBackground() {
bottom_b = lerp(current.bg_bottom_b, target.bg_bottom_b, transition_progress_);
} else {
// Sin transición: usar tema actual directamente
ThemeColors &theme = themes_[static_cast<int>(current_theme_)];
ThemeColors& theme = themes_[static_cast<int>(current_theme_)];
top_r = theme.bg_top_r;
top_g = theme.bg_top_g;
top_b = theme.bg_top_b;
@@ -1087,20 +1129,20 @@ void Engine::addSpriteToBatch(float x, float y, float w, float h, int r, int g,
int Engine::calculateMaxWindowZoom() const {
// Obtener información del display usando el método de Coffee Crisis
int num_displays = 0;
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
if (displays == nullptr || num_displays == 0) {
return WINDOW_ZOOM_MIN; // Fallback si no se puede obtener
}
// Obtener el modo de display actual
const auto *dm = SDL_GetCurrentDisplayMode(displays[0]);
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
if (dm == nullptr) {
SDL_free(displays);
return WINDOW_ZOOM_MIN;
}
// Calcular zoom máximo usando la fórmula de Coffee Crisis
const int MAX_ZOOM = std::min(dm->w / SCREEN_WIDTH, (dm->h - WINDOW_DECORATION_HEIGHT) / SCREEN_HEIGHT);
const int MAX_ZOOM = std::min(dm->w / base_screen_width_, (dm->h - WINDOW_DECORATION_HEIGHT) / base_screen_height_);
SDL_free(displays);
@@ -1120,12 +1162,12 @@ void Engine::setWindowZoom(int new_zoom) {
// Obtener posición actual del centro de la ventana
int current_x, current_y;
SDL_GetWindowPosition(window_, &current_x, &current_y);
int current_center_x = current_x + (SCREEN_WIDTH * current_window_zoom_) / 2;
int current_center_y = current_y + (SCREEN_HEIGHT * current_window_zoom_) / 2;
int current_center_x = current_x + (base_screen_width_ * current_window_zoom_) / 2;
int current_center_y = current_y + (base_screen_height_ * current_window_zoom_) / 2;
// Calcular nuevo tamaño
int new_width = SCREEN_WIDTH * new_zoom;
int new_height = SCREEN_HEIGHT * new_zoom;
int new_width = base_screen_width_ * new_zoom;
int new_height = base_screen_height_ * new_zoom;
// Calcular nueva posición (centrada en el punto actual)
int new_x = current_center_x - new_width / 2;
@@ -1162,46 +1204,77 @@ void Engine::zoomOut() {
void Engine::initializeThemes() {
// SUNSET: Naranjas, rojos, amarillos, rosas (8 colores)
themes_[0] = {
"SUNSET", "ATARDECER", // Nombres (inglés, español)
255, 140, 60, // Color texto: naranja cálido
180.0f / 255.0f, 140.0f / 255.0f, 100.0f / 255.0f, // Fondo superior (naranja suave)
40.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, // Fondo inferior (púrpura oscuro)
{{255, 140, 0}, {255, 69, 0}, {255, 215, 0}, {255, 20, 147}, {255, 99, 71}, {255, 165, 0}, {255, 192, 203}, {220, 20, 60}}
};
"SUNSET",
"ATARDECER", // Nombres (inglés, español)
255,
140,
60, // Color texto: naranja cálido
180.0f / 255.0f,
140.0f / 255.0f,
100.0f / 255.0f, // Fondo superior (naranja suave)
40.0f / 255.0f,
20.0f / 255.0f,
60.0f / 255.0f, // Fondo inferior (púrpura oscuro)
{{255, 140, 0}, {255, 69, 0}, {255, 215, 0}, {255, 20, 147}, {255, 99, 71}, {255, 165, 0}, {255, 192, 203}, {220, 20, 60}}};
// OCEAN: Azules, turquesas, blancos (8 colores)
themes_[1] = {
"OCEAN", "OCEANO", // Nombres (inglés, español)
80, 200, 255, // Color texto: azul océano
100.0f / 255.0f, 150.0f / 255.0f, 200.0f / 255.0f, // Fondo superior (azul cielo)
20.0f / 255.0f, 40.0f / 255.0f, 80.0f / 255.0f, // Fondo inferior (azul marino)
{{0, 191, 255}, {0, 255, 255}, {32, 178, 170}, {176, 224, 230}, {70, 130, 180}, {0, 206, 209}, {240, 248, 255}, {64, 224, 208}}
};
"OCEAN",
"OCEANO", // Nombres (inglés, español)
80,
200,
255, // Color texto: azul océano
100.0f / 255.0f,
150.0f / 255.0f,
200.0f / 255.0f, // Fondo superior (azul cielo)
20.0f / 255.0f,
40.0f / 255.0f,
80.0f / 255.0f, // Fondo inferior (azul marino)
{{0, 191, 255}, {0, 255, 255}, {32, 178, 170}, {176, 224, 230}, {70, 130, 180}, {0, 206, 209}, {240, 248, 255}, {64, 224, 208}}};
// NEON: Cian, magenta, verde lima, amarillo vibrante (8 colores)
themes_[2] = {
"NEON", "NEON", // Nombres (inglés, español)
255, 60, 255, // Color texto: magenta brillante
20.0f / 255.0f, 20.0f / 255.0f, 40.0f / 255.0f, // Fondo superior (negro azulado)
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior (negro)
{{0, 255, 255}, {255, 0, 255}, {50, 205, 50}, {255, 255, 0}, {255, 20, 147}, {0, 255, 127}, {138, 43, 226}, {255, 69, 0}}
};
"NEON",
"NEON", // Nombres (inglés, español)
255,
60,
255, // Color texto: magenta brillante
20.0f / 255.0f,
20.0f / 255.0f,
40.0f / 255.0f, // Fondo superior (negro azulado)
0.0f / 255.0f,
0.0f / 255.0f,
0.0f / 255.0f, // Fondo inferior (negro)
{{0, 255, 255}, {255, 0, 255}, {50, 205, 50}, {255, 255, 0}, {255, 20, 147}, {0, 255, 127}, {138, 43, 226}, {255, 69, 0}}};
// FOREST: Verdes, marrones, amarillos otoño (8 colores)
themes_[3] = {
"FOREST", "BOSQUE", // Nombres (inglés, español)
100, 255, 100, // Color texto: verde natural
144.0f / 255.0f, 238.0f / 255.0f, 144.0f / 255.0f, // Fondo superior (verde claro)
101.0f / 255.0f, 67.0f / 255.0f, 33.0f / 255.0f, // Fondo inferior (marrón tierra)
{{34, 139, 34}, {107, 142, 35}, {154, 205, 50}, {255, 215, 0}, {210, 180, 140}, {160, 82, 45}, {218, 165, 32}, {50, 205, 50}}
};
"FOREST",
"BOSQUE", // Nombres (inglés, español)
100,
255,
100, // Color texto: verde natural
144.0f / 255.0f,
238.0f / 255.0f,
144.0f / 255.0f, // Fondo superior (verde claro)
101.0f / 255.0f,
67.0f / 255.0f,
33.0f / 255.0f, // Fondo inferior (marrón tierra)
{{34, 139, 34}, {107, 142, 35}, {154, 205, 50}, {255, 215, 0}, {210, 180, 140}, {160, 82, 45}, {218, 165, 32}, {50, 205, 50}}};
// RGB: Círculo cromático con 24 puntos (cada 15°) - Ultra precisión matemática
themes_[4] = {
"RGB", "RGB", // Nombres (inglés, español)
100, 100, 100, // Color texto: gris oscuro (contraste con fondo blanco)
1.0f, 1.0f, 1.0f, // Fondo superior (blanco puro)
1.0f, 1.0f, 1.0f, // Fondo inferior (blanco puro) - sin degradado
"RGB",
"RGB", // Nombres (inglés, español)
100,
100,
100, // Color texto: gris oscuro (contraste con fondo blanco)
1.0f,
1.0f,
1.0f, // Fondo superior (blanco puro)
1.0f,
1.0f,
1.0f, // Fondo inferior (blanco puro) - sin degradado
{
{255, 0, 0}, // 0° - Rojo puro
{255, 64, 0}, // 15° - Rojo-Naranja
@@ -1227,15 +1300,21 @@ void Engine::initializeThemes() {
{255, 0, 191}, // 315° - Magenta-Rojo claro
{255, 0, 128}, // 330° - Magenta-Rojo
{255, 0, 64} // 345° - Magenta claro-Rojo
}
};
}};
// MONOCHROME: Fondo negro degradado, sprites blancos monocromáticos (8 tonos grises)
themes_[5] = {
"MONOCHROME", "MONOCROMO", // Nombres (inglés, español)
200, 200, 200, // Color texto: gris claro
20.0f / 255.0f, 20.0f / 255.0f, 20.0f / 255.0f, // Fondo superior (gris muy oscuro)
0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, // Fondo inferior (negro)
"MONOCHROME",
"MONOCROMO", // Nombres (inglés, español)
200,
200,
200, // Color texto: gris claro
20.0f / 255.0f,
20.0f / 255.0f,
20.0f / 255.0f, // Fondo superior (gris muy oscuro)
0.0f / 255.0f,
0.0f / 255.0f,
0.0f / 255.0f, // Fondo inferior (negro)
{
{255, 255, 255}, // Blanco puro - todas las pelotas del mismo color
{255, 255, 255},
@@ -1244,9 +1323,7 @@ void Engine::initializeThemes() {
{255, 255, 255},
{255, 255, 255},
{255, 255, 255},
{255, 255, 255}
}
};
{255, 255, 255}}};
}
void Engine::startThemeTransition(ColorTheme new_theme) {
@@ -1284,8 +1361,7 @@ Color Engine::getInterpolatedColor(size_t ball_index) const {
return {
static_cast<Uint8>(lerp(static_cast<float>(current_color.r), static_cast<float>(target_color.r), transition_progress_)),
static_cast<Uint8>(lerp(static_cast<float>(current_color.g), static_cast<float>(target_color.g), transition_progress_)),
static_cast<Uint8>(lerp(static_cast<float>(current_color.b), static_cast<float>(target_color.b), transition_progress_))
};
static_cast<Uint8>(lerp(static_cast<float>(current_color.b), static_cast<float>(target_color.b), transition_progress_))};
}
// Sistema de Modo DEMO (auto-play)
@@ -1321,9 +1397,7 @@ void Engine::performDemoAction(bool is_lite) {
if (is_lite) {
// DEMO LITE: Solo física/figuras
TOTAL_WEIGHT = DEMO_LITE_WEIGHT_GRAVITY_DIR + DEMO_LITE_WEIGHT_GRAVITY_TOGGLE
+ DEMO_LITE_WEIGHT_SHAPE + DEMO_LITE_WEIGHT_TOGGLE_PHYSICS
+ DEMO_LITE_WEIGHT_IMPULSE;
TOTAL_WEIGHT = DEMO_LITE_WEIGHT_GRAVITY_DIR + DEMO_LITE_WEIGHT_GRAVITY_TOGGLE + DEMO_LITE_WEIGHT_SHAPE + DEMO_LITE_WEIGHT_TOGGLE_PHYSICS + DEMO_LITE_WEIGHT_IMPULSE;
random_value = rand() % TOTAL_WEIGHT;
// Cambiar dirección gravedad (25%)
@@ -1344,9 +1418,7 @@ void Engine::performDemoAction(bool is_lite) {
// Activar figura 3D (25%)
accumulated_weight += DEMO_LITE_WEIGHT_SHAPE;
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM, ShapeType::PNG_SHAPE};
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM, ShapeType::PNG_SHAPE};
int shape_index = rand() % 9;
activateShape(shapes[shape_index]);
return;
@@ -1368,10 +1440,7 @@ void Engine::performDemoAction(bool is_lite) {
} else {
// DEMO COMPLETO: Todas las acciones
TOTAL_WEIGHT = DEMO_WEIGHT_GRAVITY_DIR + DEMO_WEIGHT_GRAVITY_TOGGLE + DEMO_WEIGHT_SHAPE
+ DEMO_WEIGHT_TOGGLE_PHYSICS + DEMO_WEIGHT_REGENERATE_SHAPE + DEMO_WEIGHT_THEME
+ DEMO_WEIGHT_SCENARIO + DEMO_WEIGHT_IMPULSE + DEMO_WEIGHT_DEPTH_ZOOM
+ DEMO_WEIGHT_SHAPE_SCALE + DEMO_WEIGHT_SPRITE;
TOTAL_WEIGHT = DEMO_WEIGHT_GRAVITY_DIR + DEMO_WEIGHT_GRAVITY_TOGGLE + DEMO_WEIGHT_SHAPE + DEMO_WEIGHT_TOGGLE_PHYSICS + DEMO_WEIGHT_REGENERATE_SHAPE + DEMO_WEIGHT_THEME + DEMO_WEIGHT_SCENARIO + DEMO_WEIGHT_IMPULSE + DEMO_WEIGHT_DEPTH_ZOOM + DEMO_WEIGHT_SHAPE_SCALE + DEMO_WEIGHT_SPRITE;
random_value = rand() % TOTAL_WEIGHT;
// Cambiar dirección gravedad (10%)
@@ -1392,9 +1461,7 @@ void Engine::performDemoAction(bool is_lite) {
// Activar figura 3D (20%)
accumulated_weight += DEMO_WEIGHT_SHAPE;
if (random_value < accumulated_weight) {
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM, ShapeType::PNG_SHAPE};
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM, ShapeType::PNG_SHAPE};
int shape_index = rand() % 9;
activateShape(shapes[shape_index]);
return;
@@ -1489,9 +1556,7 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
}
} else {
// Modo figura: elegir figura aleatoria (excluir PNG_SHAPE - es logo especial)
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
}
@@ -1527,9 +1592,7 @@ void Engine::randomizeOnDemoStart(bool is_lite) {
}
} else {
// Modo figura: elegir figura aleatoria (excluir PNG_SHAPE - es logo especial)
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX,
ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER,
ShapeType::ICOSAHEDRON, ShapeType::ATOM};
ShapeType shapes[] = {ShapeType::SPHERE, ShapeType::WAVE_GRID, ShapeType::HELIX, ShapeType::TORUS, ShapeType::CUBE, ShapeType::CYLINDER, ShapeType::ICOSAHEDRON, ShapeType::ATOM};
activateShape(shapes[rand() % 8]);
// 5. Profundidad (solo si estamos en figura)
@@ -1780,9 +1843,7 @@ void Engine::updateShape() {
// Aplicar fuerza de atracción física hacia el punto rotado
// Usar constantes SHAPE (mayor pegajosidad que ROTOBALL)
float shape_size = scale_factor * 80.0f; // 80px = radio base
balls_[i]->applyRotoBallForce(target_x, target_y, shape_size, delta_time_,
SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR,
SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE);
balls_[i]->applyRotoBallForce(target_x, target_y, shape_size, delta_time_, SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR, SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE);
// Calcular brillo según profundidad Z para renderizado
// Normalizar Z al rango de la figura (asumiendo simetría ±shape_size)

View File

@@ -10,19 +10,19 @@
#include <string> // for string
#include <vector> // for vector
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
#include "ball.h" // for Ball
#include "defines.h" // for GravityDirection, ColorTheme, ShapeType
#include "external/texture.h" // for Texture
#include "shapes/shape.h" // for Shape (interfaz polimórfica)
class Engine {
public:
public:
// Interfaz pública
bool initialize(int width = 0, int height = 0, bool fullscreen = false);
bool initialize(int width = 0, int height = 0, int zoom = 0, bool fullscreen = false);
void run();
void shutdown();
private:
private:
// Recursos SDL
SDL_Window* window_ = nullptr;
SDL_Renderer* renderer_ = nullptr;
@@ -48,7 +48,7 @@ private:
bool show_text_ = true;
// Sistema de zoom dinámico
int current_window_zoom_ = WINDOW_ZOOM;
int current_window_zoom_ = DEFAULT_WINDOW_ZOOM;
std::string text_;
int text_pos_ = 0;
Uint64 text_init_time_ = 0;
@@ -65,12 +65,12 @@ private:
ScalingMode current_scaling_mode_ = ScalingMode::INTEGER; // Modo de escalado actual (F5)
// Resolución base (configurada por CLI o default)
int base_screen_width_ = SCREEN_WIDTH;
int base_screen_height_ = SCREEN_HEIGHT;
int base_screen_width_ = DEFAULT_SCREEN_WIDTH;
int base_screen_height_ = DEFAULT_SCREEN_HEIGHT;
// Resolución dinámica actual (cambia en fullscreen real)
int current_screen_width_ = SCREEN_WIDTH;
int current_screen_height_ = SCREEN_HEIGHT;
int current_screen_width_ = DEFAULT_SCREEN_WIDTH;
int current_screen_height_ = DEFAULT_SCREEN_HEIGHT;
// Sistema de temas
ColorTheme current_theme_ = ColorTheme::SUNSET;

View File

@@ -6,19 +6,23 @@ void printHelp() {
std::cout << "ViBe3 Physics - Simulador de físicas avanzadas\n";
std::cout << "\nUso: vibe3_physics [opciones]\n\n";
std::cout << "Opciones:\n";
std::cout << " -w, --width <px> Ancho de resolución (default: 1280)\n";
std::cout << " -h, --height <px> Alto de resolución (default: 720)\n";
std::cout << " -w, --width <px> Ancho de resolución (default: 320)\n";
std::cout << " -h, --height <px> Alto de resolución (default: 240)\n";
std::cout << " -z, --zoom <n> Zoom de ventana (default: 3)\n";
std::cout << " -f, --fullscreen Modo pantalla completa\n";
std::cout << " --help Mostrar esta ayuda\n\n";
std::cout << "Ejemplos:\n";
std::cout << " vibe3_physics # 1280x720 ventana\n";
std::cout << " vibe3_physics -w 1920 -h 1080 # 1920x1080 ventana\n";
std::cout << " vibe3_physics -w 1920 -h 1080 -f # 1920x1080 fullscreen\n";
std::cout << " vibe3_physics # 320x240 zoom 3 (ventana 960x720)\n";
std::cout << " vibe3_physics -w 1920 -h 1080 # 1920x1080 zoom 1 (auto)\n";
std::cout << " vibe3_physics -w 640 -h 480 -z 2 # 640x480 zoom 2 (ventana 1280x960)\n";
std::cout << " vibe3_physics -w 1920 -h 1080 -f # 1920x1080 fullscreen\n\n";
std::cout << "Nota: Si resolución > pantalla, se usa default. Zoom se ajusta automáticamente.\n";
}
int main(int argc, char* argv[]) {
int width = 0;
int height = 0;
int zoom = 0;
bool fullscreen = false;
// Parsear argumentos
@@ -48,6 +52,17 @@ int main(int argc, char* argv[]) {
std::cerr << "Error: -h/--height requiere un valor\n";
return -1;
}
} else if (strcmp(argv[i], "-z") == 0 || strcmp(argv[i], "--zoom") == 0) {
if (i + 1 < argc) {
zoom = atoi(argv[++i]);
if (zoom < 1) {
std::cerr << "Error: Zoom mínimo es 1\n";
return -1;
}
} else {
std::cerr << "Error: -z/--zoom requiere un valor\n";
return -1;
}
} else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--fullscreen") == 0) {
fullscreen = true;
} else {
@@ -59,7 +74,7 @@ int main(int argc, char* argv[]) {
Engine engine;
if (!engine.initialize(width, height, fullscreen)) {
if (!engine.initialize(width, height, zoom, fullscreen)) {
std::cout << "¡Error al inicializar el engine!" << std::endl;
return -1;
}