#include "shape_manager.h" #include // for std::min, std::max #include // for rand #include // for std::string #include "../ball.h" // for Ball #include "../defines.h" // for constantes #include "../scene/scene_manager.h" // for SceneManager #include "../state/state_manager.h" // for StateManager #include "../ui/ui_manager.h" // for UIManager // Includes de todas las shapes (necesario para creación polimórfica) #include "../shapes/atom_shape.h" #include "../shapes/cube_shape.h" #include "../shapes/cylinder_shape.h" #include "../shapes/helix_shape.h" #include "../shapes/icosahedron_shape.h" #include "../shapes/lissajous_shape.h" #include "../shapes/png_shape.h" #include "../shapes/sphere_shape.h" #include "../shapes/torus_shape.h" ShapeManager::ShapeManager() : engine_(nullptr) , scene_mgr_(nullptr) , ui_mgr_(nullptr) , state_mgr_(nullptr) , current_mode_(SimulationMode::PHYSICS) , current_shape_type_(ShapeType::SPHERE) , last_shape_type_(ShapeType::SPHERE) , active_shape_(nullptr) , shape_scale_factor_(1.0f) , depth_zoom_enabled_(true) , screen_width_(0) , screen_height_(0) , shape_convergence_(0.0f) { } ShapeManager::~ShapeManager() { } void ShapeManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr, StateManager* state_mgr, int screen_width, int screen_height) { engine_ = engine; scene_mgr_ = scene_mgr; ui_mgr_ = ui_mgr; state_mgr_ = state_mgr; screen_width_ = screen_width; screen_height_ = screen_height; } void ShapeManager::updateScreenSize(int width, int height) { screen_width_ = width; screen_height_ = height; } // ============================================================================ // IMPLEMENTACIÓN COMPLETA - Migrado desde Engine // ============================================================================ void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) { if (current_mode_ == SimulationMode::PHYSICS) { // Cambiar a modo figura (usar última figura seleccionada) activateShapeInternal(last_shape_type_); // Si estamos en modo LOGO y la figura es PNG_SHAPE, restaurar configuración LOGO if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO && last_shape_type_ == ShapeType::PNG_SHAPE) { if (active_shape_) { PNGShape* png_shape = dynamic_cast(active_shape_.get()); if (png_shape) { png_shape->setLogoMode(true); } } } // 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 { // Volver a modo física normal current_mode_ = SimulationMode::PHYSICS; // Desactivar atracción y resetear escala de profundidad auto& balls = scene_mgr_->getBallsMutable(); for (auto& ball : balls) { ball->enableShapeAttraction(false); ball->setDepthScale(1.0f); // Reset escala a 100% (evita "pop" visual) } // Activar gravedad al salir (solo si se especifica) if (force_gravity_on_exit) { scene_mgr_->forceBallsGravityOn(); } // Mostrar notificación (solo si NO estamos en modo demo o logo) if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { ui_mgr_->showNotification("Modo Física"); } } } void ShapeManager::activateShape(ShapeType type) { activateShapeInternal(type); } void ShapeManager::handleShapeScaleChange(bool increase) { if (current_mode_ == SimulationMode::SHAPE) { if (increase) { shape_scale_factor_ += SHAPE_SCALE_STEP; } else { shape_scale_factor_ -= SHAPE_SCALE_STEP; } clampShapeScale(); // Mostrar notificación si está en modo SANDBOX if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { std::string notification = "Escala " + std::to_string(static_cast(shape_scale_factor_ * 100.0f + 0.5f)) + "%"; ui_mgr_->showNotification(notification); } } } void ShapeManager::resetShapeScale() { if (current_mode_ == SimulationMode::SHAPE) { shape_scale_factor_ = SHAPE_SCALE_DEFAULT; // Mostrar notificación si está en modo SANDBOX if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { ui_mgr_->showNotification("Escala 100%"); } } } void ShapeManager::toggleDepthZoom() { if (current_mode_ == SimulationMode::SHAPE) { depth_zoom_enabled_ = !depth_zoom_enabled_; // Mostrar notificación si está en modo SANDBOX if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { ui_mgr_->showNotification(depth_zoom_enabled_ ? "Profundidad On" : "Profundidad Off"); } } } void ShapeManager::update(float delta_time) { if (!active_shape_ || current_mode_ != SimulationMode::SHAPE) return; // Actualizar animación de la figura active_shape_->update(delta_time, static_cast(screen_width_), static_cast(screen_height_)); // Obtener factor de escala para física (base de figura + escala manual) float scale_factor = active_shape_->getScaleFactor(static_cast(screen_height_)) * shape_scale_factor_; // Centro de la pantalla float center_x = screen_width_ / 2.0f; float center_y = screen_height_ / 2.0f; // Obtener referencia mutable a las bolas desde SceneManager auto& balls = scene_mgr_->getBallsMutable(); // Actualizar cada pelota con física de atracción for (size_t i = 0; i < balls.size(); i++) { // Obtener posición 3D rotada del punto i float x_3d, y_3d, z_3d; active_shape_->getPoint3D(static_cast(i), x_3d, y_3d, z_3d); // Aplicar escala manual a las coordenadas 3D x_3d *= shape_scale_factor_; y_3d *= shape_scale_factor_; z_3d *= shape_scale_factor_; // Proyección 2D ortográfica (punto objetivo móvil) float target_x = center_x + x_3d; float target_y = center_y + y_3d; // Actualizar target de la pelota para cálculo de convergencia balls[i]->setShapeTarget2D(target_x, target_y); // Aplicar fuerza de atracción física hacia el punto rotado // Usar constantes SHAPE (mayor pegajosidad que ROTOBALL) float shape_size = scale_factor * 80.0f; // 80px = radio base balls[i]->applyShapeForce(target_x, target_y, shape_size, delta_time, SHAPE_SPRING_K, SHAPE_DAMPING_BASE, SHAPE_DAMPING_NEAR, SHAPE_NEAR_THRESHOLD, SHAPE_MAX_FORCE); // Calcular brillo según profundidad Z para renderizado // Normalizar Z al rango de la figura (asumiendo simetría ±shape_size) float z_normalized = (z_3d + shape_size) / (2.0f * shape_size); z_normalized = std::max(0.0f, std::min(1.0f, z_normalized)); balls[i]->setDepthBrightness(z_normalized); // Calcular escala según profundidad Z (perspectiva) - solo si está activado // 0.0 (fondo) → 0.5x, 0.5 (medio) → 1.0x, 1.0 (frente) → 1.5x float depth_scale = depth_zoom_enabled_ ? (0.5f + z_normalized * 1.0f) : 1.0f; balls[i]->setDepthScale(depth_scale); } // Calcular convergencia en LOGO MODE (% de pelotas cerca de su objetivo) if (state_mgr_ && state_mgr_->getCurrentMode() == AppMode::LOGO && current_mode_ == SimulationMode::SHAPE) { int balls_near = 0; float distance_threshold = LOGO_CONVERGENCE_DISTANCE; // 20px fijo (más permisivo) for (const auto& ball : balls) { if (ball->getDistanceToTarget() < distance_threshold) { balls_near++; } } shape_convergence_ = static_cast(balls_near) / scene_mgr_->getBallCount(); // Notificar a la figura sobre el porcentaje de convergencia // Esto permite que PNGShape decida cuándo empezar a contar para flips active_shape_->setConvergence(shape_convergence_); } } void ShapeManager::generateShape() { if (!active_shape_) return; int num_points = static_cast(scene_mgr_->getBallCount()); active_shape_->generatePoints(num_points, static_cast(screen_width_), static_cast(screen_height_)); } // ============================================================================ // MÉTODOS PRIVADOS // ============================================================================ void ShapeManager::activateShapeInternal(ShapeType type) { // Guardar como última figura seleccionada last_shape_type_ = type; current_shape_type_ = type; // Cambiar a modo figura current_mode_ = SimulationMode::SHAPE; // Desactivar gravedad al entrar en modo figura scene_mgr_->forceBallsGravityOff(); // Crear instancia polimórfica de la figura correspondiente switch (type) { case ShapeType::SPHERE: active_shape_ = std::make_unique(); break; case ShapeType::CUBE: active_shape_ = std::make_unique(); break; case ShapeType::HELIX: active_shape_ = std::make_unique(); break; case ShapeType::TORUS: active_shape_ = std::make_unique(); break; case ShapeType::LISSAJOUS: active_shape_ = std::make_unique(); break; case ShapeType::CYLINDER: active_shape_ = std::make_unique(); break; case ShapeType::ICOSAHEDRON: active_shape_ = std::make_unique(); break; case ShapeType::ATOM: active_shape_ = std::make_unique(); break; case ShapeType::PNG_SHAPE: active_shape_ = std::make_unique("data/shapes/jailgames.png"); break; default: active_shape_ = std::make_unique(); // Fallback break; } // Generar puntos de la figura generateShape(); // Activar atracción física en todas las pelotas auto& balls = scene_mgr_->getBallsMutable(); for (auto& ball : balls) { ball->enableShapeAttraction(true); } // Mostrar notificación con nombre de figura (solo si NO estamos en modo demo o logo) if (active_shape_ && state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) { std::string notification = std::string("Modo ") + active_shape_->getName(); ui_mgr_->showNotification(notification); } } void ShapeManager::clampShapeScale() { // Calcular tamaño máximo permitido según resolución actual // La figura más grande (esfera/cubo) usa ~33% de altura por defecto // Permitir hasta que la figura ocupe 90% de la dimensión más pequeña float max_dimension = std::min(screen_width_, screen_height_); float base_size_factor = 0.333f; // ROTOBALL_RADIUS_FACTOR o similar float max_scale_for_screen = (max_dimension * 0.9f) / (max_dimension * base_size_factor); // Limitar entre SHAPE_SCALE_MIN y el mínimo de (SHAPE_SCALE_MAX, max_scale_for_screen) float max_allowed = std::min(SHAPE_SCALE_MAX, max_scale_for_screen); shape_scale_factor_ = std::max(SHAPE_SCALE_MIN, std::min(max_allowed, shape_scale_factor_)); }