#pragma once #include // for Uint64 #include // for std::vector in DynamicThemeKeyframe/DynamicTheme // Configuración de ventana y pantalla constexpr char WINDOW_CAPTION[] = "ViBe3 Physics (JailDesigner 2025)"; // Resolución por defecto (usada si no se especifica en CLI) constexpr int DEFAULT_SCREEN_WIDTH = 1280; // Ancho lógico por defecto (si no hay -w) constexpr int DEFAULT_SCREEN_HEIGHT = 720; // Alto lógico por defecto (si no hay -h) constexpr int DEFAULT_WINDOW_ZOOM = 1; // Zoom inicial de ventana (1x = sin zoom) // Configuración de zoom dinámico de ventana constexpr int WINDOW_ZOOM_MIN = 1; // Zoom mínimo (320x240) constexpr int WINDOW_ZOOM_MAX = 10; // Zoom máximo teórico (3200x2400) constexpr int WINDOW_DESKTOP_MARGIN = 10; // Margen mínimo con bordes del escritorio constexpr int WINDOW_DECORATION_HEIGHT = 30; // Altura estimada de decoraciones del SO // Configuración de física constexpr float GRAVITY_FORCE = 0.2f; // Fuerza de gravedad (píxeles/frame²) // Configuración de interfaz constexpr float THEME_TRANSITION_DURATION = 0.5f; // Duración de transiciones LERP entre temas (segundos) // Configuración de notificaciones (sistema Notifier) constexpr int TEXT_ABSOLUTE_SIZE = 12; // Tamaño fuente base en píxeles físicos (múltiplo de 12px, tamaño nativo de la fuente) constexpr Uint64 NOTIFICATION_DURATION = 2000; // Duración default de notificaciones (ms) constexpr Uint64 NOTIFICATION_SLIDE_TIME = 300; // Duración animación entrada (ms) constexpr Uint64 NOTIFICATION_FADE_TIME = 200; // Duración animación salida (ms) constexpr float NOTIFICATION_BG_ALPHA = 0.7f; // Opacidad fondo semitransparente (0.0-1.0) constexpr int NOTIFICATION_PADDING = 10; // Padding interno del fondo (píxeles físicos) constexpr int NOTIFICATION_TOP_MARGIN = 20; // Margen superior desde borde pantalla (píxeles físicos) // Configuración de pérdida aleatoria en rebotes constexpr float BASE_BOUNCE_COEFFICIENT = 0.75f; // Coeficiente base IGUAL para todas las pelotas constexpr float BOUNCE_RANDOM_LOSS_PERCENT = 0.1f; // 0-10% pérdida adicional aleatoria en cada rebote constexpr float LATERAL_LOSS_PERCENT = 0.02f; // ±2% pérdida lateral en rebotes // Configuración de masa/peso individual por pelota constexpr float GRAVITY_MASS_MIN = 0.7f; // Factor mínimo de masa (pelota ligera - 70% gravedad) constexpr float GRAVITY_MASS_MAX = 1.3f; // Factor máximo de masa (pelota pesada - 130% gravedad) // Configuración de velocidad lateral al cambiar gravedad (muy sutil) constexpr float GRAVITY_CHANGE_LATERAL_MIN = 0.04f; // Velocidad lateral mínima (2.4 px/s) constexpr float GRAVITY_CHANGE_LATERAL_MAX = 0.08f; // Velocidad lateral máxima (4.8 px/s) // Configuración de spawn inicial de pelotas constexpr float BALL_SPAWN_MARGIN = 0.15f; // Margen lateral para spawn (0.25 = 25% a cada lado) // Escenarios de número de pelotas (teclas 1-8) constexpr int BALL_COUNT_SCENARIOS[8] = {10, 50, 100, 500, 1000, 5000, 10000, 50000}; // Estructura para representar colores RGB struct Color { int r, g, b; // Componentes rojo, verde, azul (0-255) }; // Estructura de tema de colores estático struct ThemeColors { const char* name_en; // Nombre en inglés (para debug) const char* name_es; // Nombre en español (para display) int text_color_r, text_color_g, text_color_b; // Color del texto del tema float bg_top_r, bg_top_g, bg_top_b; float bg_bottom_r, bg_bottom_g, bg_bottom_b; std::vector ball_colors; }; // Estructura para keyframe de tema dinámico struct DynamicThemeKeyframe { // Fondo degradado float bg_top_r, bg_top_g, bg_top_b; float bg_bottom_r, bg_bottom_g, bg_bottom_b; // Color de fondo de notificaciones int notif_bg_r, notif_bg_g, notif_bg_b; // Colores de pelotas en este keyframe std::vector ball_colors; // Duración de transición HACIA este keyframe (segundos) // 0.0 = estado inicial (sin transición) float duration; }; // NOTA: La clase DynamicTheme (tema dinámico animado) está definida en themes/dynamic_theme.h // Esta estructura de datos es solo para definir keyframes que se pasan al constructor // Enum para dirección de gravedad enum class GravityDirection { DOWN, // ↓ Gravedad hacia abajo (por defecto) UP, // ↑ Gravedad hacia arriba LEFT, // ← Gravedad hacia la izquierda RIGHT // → Gravedad hacia la derecha }; // Enum para temas de colores (seleccionables con teclado numérico y Shift+Numpad) // Todos los temas usan ahora sistema dinámico de keyframes enum class ColorTheme { SUNSET = 0, // Naranjas, rojos, amarillos, rosas (estático: 1 keyframe) OCEAN = 1, // Azules, turquesas, blancos (estático: 1 keyframe) NEON = 2, // Cian, magenta, verde lima, amarillo vibrante (estático: 1 keyframe) FOREST = 3, // Verdes, marrones, amarillos otoño (estático: 1 keyframe) RGB = 4, // RGB puros y subdivisiones matemáticas - fondo blanco (estático: 1 keyframe) MONOCHROME = 5, // Fondo negro degradado, sprites blancos monocromáticos (estático: 1 keyframe) LAVENDER = 6, // Degradado violeta-azul, pelotas amarillo dorado (estático: 1 keyframe) CRIMSON = 7, // Fondo negro-rojo, pelotas rojas uniformes (estático: 1 keyframe) EMERALD = 8, // Fondo negro-verde, pelotas verdes uniformes (estático: 1 keyframe) SUNRISE = 9, // Amanecer: Noche → Alba → Día (animado: 4 keyframes, 12s ciclo) OCEAN_WAVES = 10, // Olas oceánicas: Azul oscuro ↔ Turquesa (animado: 3 keyframes, 8s ciclo) NEON_PULSE = 11, // Pulso neón: Negro ↔ Neón vibrante (animado: 3 keyframes, 3s ping-pong) FIRE = 12, // Fuego vivo: Brasas → Llamas → Inferno (animado: 4 keyframes, 10s ciclo) AURORA = 13, // Aurora boreal: Verde → Violeta → Cian (animado: 4 keyframes, 14s ciclo) VOLCANIC = 14 // Erupción volcánica: Ceniza → Erupción → Lava (animado: 4 keyframes, 12s ciclo) }; // Enum para tipo de figura 3D enum class ShapeType { NONE, // Sin figura (modo física pura) SPHERE, // Esfera Fibonacci (antiguo RotoBall) CUBE, // Cubo rotante HELIX, // Espiral 3D TORUS, // Toroide/donut LISSAJOUS, // Malla ondeante CYLINDER, // Cilindro rotante ICOSAHEDRON, // Icosaedro D20 ATOM, // Átomo con órbitas PNG_SHAPE // Forma cargada desde PNG 1-bit }; // Enum para modo de simulación enum class SimulationMode { PHYSICS, // Modo física normal con gravedad SHAPE, // Modo figura 3D (Shape polimórfico) BOIDS // Modo enjambre (Boids - comportamiento emergente) }; // Enum para modo de aplicación (mutuamente excluyentes) enum class AppMode { SANDBOX, // Control manual del usuario (modo sandbox) DEMO, // Modo demo completo (auto-play) DEMO_LITE, // Modo demo lite (solo física/figuras) LOGO // Modo logo (easter egg) }; // Enum para modo de escalado en fullscreen (F5) enum class ScalingMode { INTEGER, // Escalado entero con barras negras (mantiene aspecto + píxel perfecto) LETTERBOX, // Zoom hasta llenar una dimensión (una barra desaparece) STRETCH // Estirar para llenar pantalla completa (puede distorsionar aspecto) }; // Configuración de RotoBall (esfera 3D rotante) constexpr float ROTOBALL_RADIUS_FACTOR = 0.333f; // Radio como proporción de altura de pantalla (80/240 ≈ 0.333) constexpr float ROTOBALL_ROTATION_SPEED_Y = 1.5f; // Velocidad rotación eje Y (rad/s) constexpr float ROTOBALL_ROTATION_SPEED_X = 0.8f; // Velocidad rotación eje X (rad/s) constexpr float ROTOBALL_TRANSITION_TIME = 1.5f; // Tiempo de transición (segundos) constexpr int ROTOBALL_MIN_BRIGHTNESS = 50; // Brillo mínimo (fondo, 0-255) constexpr int ROTOBALL_MAX_BRIGHTNESS = 255; // Brillo máximo (frente, 0-255) // Física de atracción para figuras 3D (sistema de resorte) // SHAPE: Figuras 3D normales (Q/W/E/R/T/Y/U/I/O) - Mayor pegajosidad constexpr float SHAPE_SPRING_K = 800.0f; // Rigidez alta (pelotas más "pegadas") constexpr float SHAPE_DAMPING_BASE = 60.0f; // Amortiguación alta (menos rebote) constexpr float SHAPE_DAMPING_NEAR = 150.0f; // Absorción muy rápida al llegar constexpr float SHAPE_NEAR_THRESHOLD = 8.0f; // Umbral "cerca" más amplio constexpr float SHAPE_MAX_FORCE = 2000.0f; // Permite fuerzas más fuertes // Configuración del Cubo (cubo 3D rotante) constexpr float CUBE_SIZE_FACTOR = 0.25f; // Tamaño como proporción de altura (60/240 = 0.25) constexpr float CUBE_ROTATION_SPEED_X = 0.5f; // Velocidad rotación eje X (rad/s) constexpr float CUBE_ROTATION_SPEED_Y = 0.7f; // Velocidad rotación eje Y (rad/s) constexpr float CUBE_ROTATION_SPEED_Z = 0.3f; // Velocidad rotación eje Z (rad/s) // Configuración de Helix (espiral helicoidal 3D) constexpr float HELIX_RADIUS_FACTOR = 0.25f; // Radio de la espiral (proporción de altura) constexpr float HELIX_PITCH_FACTOR = 0.25f; // Separación vertical entre vueltas (proporción de altura) constexpr float HELIX_NUM_TURNS = 3.0f; // Número de vueltas completas (1-5) constexpr float HELIX_ROTATION_SPEED_Y = 1.2f; // Velocidad rotación eje Y (rad/s) constexpr float HELIX_PHASE_SPEED = 0.5f; // Velocidad de animación vertical (rad/s) // Configuración de Lissajous Curve 3D (curva paramétrica) constexpr float LISSAJOUS_SIZE_FACTOR = 0.35f; // Amplitud de la curva (proporción de altura) constexpr float LISSAJOUS_FREQ_X = 3.0f; // Frecuencia en eje X (ratio 3:2:1) constexpr float LISSAJOUS_FREQ_Y = 2.0f; // Frecuencia en eje Y constexpr float LISSAJOUS_FREQ_Z = 1.0f; // Frecuencia en eje Z constexpr float LISSAJOUS_PHASE_SPEED = 1.0f; // Velocidad de animación de fase (rad/s) constexpr float LISSAJOUS_ROTATION_SPEED_X = 0.4f; // Velocidad rotación global X (rad/s) constexpr float LISSAJOUS_ROTATION_SPEED_Y = 0.6f; // Velocidad rotación global Y (rad/s) // Configuración de Torus (toroide/donut 3D) constexpr float TORUS_MAJOR_RADIUS_FACTOR = 0.25f; // Radio mayor R (centro torus a centro tubo) constexpr float TORUS_MINOR_RADIUS_FACTOR = 0.12f; // Radio menor r (grosor del tubo) constexpr float TORUS_ROTATION_SPEED_X = 0.6f; // Velocidad rotación eje X (rad/s) constexpr float TORUS_ROTATION_SPEED_Y = 0.9f; // Velocidad rotación eje Y (rad/s) constexpr float TORUS_ROTATION_SPEED_Z = 0.3f; // Velocidad rotación eje Z (rad/s) // Configuración de Cylinder (cilindro 3D) constexpr float CYLINDER_RADIUS_FACTOR = 0.25f; // Radio del cilindro (proporción de altura) constexpr float CYLINDER_HEIGHT_FACTOR = 0.5f; // Altura del cilindro (proporción de altura) constexpr float CYLINDER_ROTATION_SPEED_Y = 1.0f; // Velocidad rotación eje Y (rad/s) // Configuración de Icosahedron (icosaedro D20) constexpr float ICOSAHEDRON_RADIUS_FACTOR = 0.30f; // Radio de la esfera circunscrita constexpr float ICOSAHEDRON_ROTATION_SPEED_X = 0.4f; // Velocidad rotación eje X (rad/s) constexpr float ICOSAHEDRON_ROTATION_SPEED_Y = 0.7f; // Velocidad rotación eje Y (rad/s) constexpr float ICOSAHEDRON_ROTATION_SPEED_Z = 0.2f; // Velocidad rotación eje Z (rad/s) // Configuración de Atom (núcleo con órbitas electrónicas) constexpr float ATOM_NUCLEUS_RADIUS_FACTOR = 0.08f; // Radio del núcleo central constexpr float ATOM_ORBIT_RADIUS_FACTOR = 0.30f; // Radio de las órbitas constexpr float ATOM_NUM_ORBITS = 3; // Número de órbitas constexpr float ATOM_ORBIT_ROTATION_SPEED = 2.0f; // Velocidad de electrones (rad/s) constexpr float ATOM_ROTATION_SPEED_Y = 0.5f; // Velocidad rotación global (rad/s) // Configuración de PNG Shape (forma desde imagen PNG 1-bit) constexpr float PNG_SIZE_FACTOR = 0.8f; // Tamaño como proporción de altura (80% pantalla) constexpr float PNG_EXTRUSION_DEPTH_FACTOR = 0.12f; // Profundidad de extrusión (compacta) constexpr int PNG_NUM_EXTRUSION_LAYERS = 15; // Capas de extrusión (más capas = más pegajosidad) constexpr bool PNG_USE_EDGES_ONLY = false; // true = solo bordes, false = relleno completo // Rotación "legible" (texto de frente con volteretas ocasionales) constexpr float PNG_IDLE_TIME_MIN = 0.5f; // Tiempo mínimo de frente (segundos) - modo MANUAL constexpr float PNG_IDLE_TIME_MAX = 2.0f; // Tiempo máximo de frente (segundos) - modo MANUAL constexpr float PNG_IDLE_TIME_MIN_LOGO = 2.0f; // Tiempo mínimo de frente en LOGO MODE constexpr float PNG_IDLE_TIME_MAX_LOGO = 4.0f; // Tiempo máximo de frente en LOGO MODE constexpr float PNG_FLIP_SPEED = 3.0f; // Velocidad voltereta (rad/s) constexpr float PNG_FLIP_DURATION = 1.5f; // Duración voltereta (segundos) // Control manual de escala de figuras 3D (Numpad +/-) constexpr float SHAPE_SCALE_MIN = 0.3f; // Escala mínima (30%) constexpr float SHAPE_SCALE_MAX = 3.0f; // Escala máxima (300%) constexpr float SHAPE_SCALE_STEP = 0.1f; // Incremento por pulsación constexpr float SHAPE_SCALE_DEFAULT = 1.0f; // Escala por defecto (100%) // Configuración de Modo DEMO (auto-play completo) constexpr float DEMO_ACTION_INTERVAL_MIN = 2.0f; // Tiempo mínimo entre acciones (segundos) constexpr float DEMO_ACTION_INTERVAL_MAX = 6.0f; // Tiempo máximo entre acciones (segundos) // Pesos de probabilidad DEMO MODE (valores relativos, se normalizan) constexpr int DEMO_WEIGHT_GRAVITY_DIR = 12; // Cambiar dirección gravedad (12%) constexpr int DEMO_WEIGHT_GRAVITY_TOGGLE = 15; // Toggle gravedad ON/OFF (15%) - ¡Ver caer pelotas! constexpr int DEMO_WEIGHT_SHAPE = 22; // Activar figura 3D (22%) - Construir figuras constexpr int DEMO_WEIGHT_TOGGLE_PHYSICS = 18; // Toggle física ↔ figura (18%) - ¡Destruir figuras! constexpr int DEMO_WEIGHT_REGENERATE_SHAPE = 10; // Re-generar misma figura (10%) - Reconstruir constexpr int DEMO_WEIGHT_THEME = 12; // Cambiar tema de colores (12%) constexpr int DEMO_WEIGHT_SCENARIO = 2; // Cambiar número de pelotas (2%) - MUY OCASIONAL constexpr int DEMO_WEIGHT_IMPULSE = 6; // Aplicar impulso (SPACE) (6%) constexpr int DEMO_WEIGHT_DEPTH_ZOOM = 1; // Toggle profundidad (1%) constexpr int DEMO_WEIGHT_SHAPE_SCALE = 1; // Cambiar escala figura (1%) constexpr int DEMO_WEIGHT_SPRITE = 1; // Cambiar sprite (1%) // TOTAL: 100 // Configuración de Modo DEMO LITE (solo física/figuras) constexpr float DEMO_LITE_ACTION_INTERVAL_MIN = 1.5f; // Más rápido que demo completo constexpr float DEMO_LITE_ACTION_INTERVAL_MAX = 4.0f; // Pesos de probabilidad DEMO LITE (solo física/figuras, sin cambios de escenario/tema) constexpr int DEMO_LITE_WEIGHT_GRAVITY_DIR = 25; // Cambiar dirección gravedad (25%) constexpr int DEMO_LITE_WEIGHT_GRAVITY_TOGGLE = 20; // Toggle gravedad ON/OFF (20%) constexpr int DEMO_LITE_WEIGHT_SHAPE = 25; // Activar figura 3D (25%) constexpr int DEMO_LITE_WEIGHT_TOGGLE_PHYSICS = 20; // Toggle física ↔ figura (20%) constexpr int DEMO_LITE_WEIGHT_IMPULSE = 10; // Aplicar impulso (10%) // TOTAL: 100 // Configuración de Modo LOGO (easter egg - "marca de agua") constexpr int LOGO_MODE_MIN_BALLS = 500; // Mínimo de pelotas para activar modo logo constexpr float LOGO_MODE_SHAPE_SCALE = 1.2f; // Escala de figura en modo logo (120%) constexpr float LOGO_ACTION_INTERVAL_MIN = 3.0f; // Tiempo mínimo entre alternancia SHAPE/PHYSICS (escalado con resolución) constexpr float LOGO_ACTION_INTERVAL_MAX = 5.0f; // Tiempo máximo entre alternancia SHAPE/PHYSICS (escalado con resolución) constexpr int LOGO_WEIGHT_TOGGLE_PHYSICS = 100; // Único peso: alternar SHAPE ↔ PHYSICS (100%) // Sistema de convergencia para LOGO MODE (evita interrupciones prematuras en resoluciones altas) constexpr float LOGO_CONVERGENCE_MIN = 0.75f; // 75% mínimo (permite algo de movimiento al disparar) constexpr float LOGO_CONVERGENCE_MAX = 1.00f; // 100% máximo (completamente formado) constexpr float LOGO_CONVERGENCE_DISTANCE = 20.0f; // Distancia (px) para considerar pelota "convergida" (más permisivo que SHAPE_NEAR) // Probabilidad de salto a Logo Mode desde DEMO/DEMO_LITE (%) // Relación DEMO:LOGO = 6:1 (pasa 6x más tiempo en DEMO que en LOGO) constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO = 5; // 5% probabilidad en DEMO normal (más raro) constexpr int LOGO_JUMP_PROBABILITY_FROM_DEMO_LITE = 3; // 3% probabilidad en DEMO LITE (aún más raro) // Sistema de espera de flips en LOGO MODE (camino alternativo de decisión) constexpr int LOGO_FLIP_WAIT_MIN = 1; // Mínimo de flips a esperar antes de cambiar a PHYSICS constexpr int LOGO_FLIP_WAIT_MAX = 3; // Máximo de flips a esperar constexpr float LOGO_FLIP_TRIGGER_MIN = 0.20f; // 20% mínimo de progreso de flip para trigger constexpr float LOGO_FLIP_TRIGGER_MAX = 0.80f; // 80% máximo de progreso de flip para trigger constexpr int LOGO_FLIP_WAIT_PROBABILITY = 50; // 50% probabilidad de elegir el camino "esperar flip" // Configuración de AppLogo (logo periódico en pantalla) constexpr float APPLOGO_DISPLAY_INTERVAL = 90.0f; // Intervalo entre apariciones del logo (segundos) constexpr float APPLOGO_DISPLAY_DURATION = 30.0f; // Duración de visibilidad del logo (segundos) constexpr float APPLOGO_ANIMATION_DURATION = 0.5f; // Duración de animación entrada/salida (segundos) constexpr float APPLOGO_HEIGHT_PERCENT = 0.4f; // Altura del logo = 40% de la altura de pantalla constexpr float APPLOGO_PADDING_PERCENT = 0.05f; // Padding desde esquina inferior-derecha = 10% constexpr float APPLOGO_LOGO2_DELAY = 0.25f; // Retraso de Logo 2 respecto a Logo 1 (segundos) // Configuración de Modo BOIDS (comportamiento de enjambre) // 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 = 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) // Debe ser ≥ BOID_COHESION_RADIUS para funcionar correctamente constexpr float PI = 3.14159265358979323846f; // Constante PI // Función auxiliar para obtener la ruta del directorio del ejecutable #include #ifdef _WIN32 #include #elif defined(__APPLE__) #include #include #else #include #include #endif inline std::string getExecutableDirectory() { #ifdef _WIN32 char buffer[MAX_PATH]; GetModuleFileNameA(NULL, buffer, MAX_PATH); std::filesystem::path exe_path(buffer); return exe_path.parent_path().string(); #elif defined(__APPLE__) char buffer[PATH_MAX]; uint32_t size = sizeof(buffer); if (_NSGetExecutablePath(buffer, &size) == 0) { std::filesystem::path exe_path(buffer); return exe_path.parent_path().string(); } return "."; #else // Linux y otros Unix char buffer[PATH_MAX]; ssize_t len = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1); if (len != -1) { buffer[len] = '\0'; std::filesystem::path exe_path(buffer); return exe_path.parent_path().string(); } return "."; #endif } // Función auxiliar para obtener la ruta del directorio de recursos inline std::string getResourcesDirectory() { std::string exe_dir = getExecutableDirectory(); #ifdef MACOS_BUNDLE // En macOS Bundle: ejecutable está en Contents/MacOS/, recursos en Contents/Resources/ std::filesystem::path resources_path = std::filesystem::path(exe_dir) / ".." / "Resources"; return resources_path.string(); #else // En desarrollo o releases normales: recursos están junto al ejecutable return exe_dir; #endif }