Files
vibe3_physics/source/shapes_mgr/shape_manager.cpp
Sergio Valor a9d7b66e83 Refactorizar estilo del proyecto: .h → .hpp, #pragma once, includes desde raíz
Modernizar convenciones de código C++ aplicando las siguientes directivas:

## Cambios principales

**1. Renombrar headers (.h → .hpp)**
- 36 archivos renombrados a extensión .hpp (estándar C++)
- Mantenidos como .h: stb_image.h, stb_image_resize2.h (librerías C externas)

**2. Modernizar include guards (#ifndef → #pragma once)**
- resource_manager.hpp: #ifndef RESOURCE_MANAGER_H → #pragma once
- resource_pack.hpp: #ifndef RESOURCE_PACK_H → #pragma once
- spatial_grid.hpp: #ifndef SPATIAL_GRID_H → #pragma once

**3. Sistema de includes desde raíz del proyecto**
- CMakeLists.txt: añadido include_directories(${CMAKE_SOURCE_DIR}/source)
- Eliminadas rutas relativas (../) en todos los includes
- Includes ahora usan rutas absolutas desde source/

**Antes:**
```cpp
#include "../defines.h"
#include "../text/textrenderer.h"
```

**Ahora:**
```cpp
#include "defines.hpp"
#include "text/textrenderer.hpp"
```

## Archivos afectados

- 1 archivo CMakeLists.txt modificado
- 36 archivos renombrados (.h → .hpp)
- 32 archivos .cpp actualizados (includes)
- 36 archivos .hpp actualizados (includes + guards)
- 1 archivo tools/ actualizado

**Total: 70 archivos modificados**

## Verificación

 Proyecto compila sin errores
 Todas las rutas de includes correctas
 Include guards modernizados
 Librerías externas C mantienen extensión .h

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 13:49:58 +02:00

307 lines
12 KiB
C++

#include "shape_manager.hpp"
#include <algorithm> // for std::min, std::max
#include <cstdlib> // for rand
#include <string> // for std::string
#include "ball.hpp" // for Ball
#include "defines.hpp" // for constantes
#include "scene/scene_manager.hpp" // for SceneManager
#include "state/state_manager.hpp" // for StateManager
#include "ui/ui_manager.hpp" // for UIManager
// Includes de todas las shapes (necesario para creación polimórfica)
#include "shapes/atom_shape.hpp"
#include "shapes/cube_shape.hpp"
#include "shapes/cylinder_shape.hpp"
#include "shapes/helix_shape.hpp"
#include "shapes/icosahedron_shape.hpp"
#include "shapes/lissajous_shape.hpp"
#include "shapes/png_shape.hpp"
#include "shapes/sphere_shape.hpp"
#include "shapes/torus_shape.hpp"
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<PNGShape*>(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<int>(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<float>(screen_width_), static_cast<float>(screen_height_));
// Obtener factor de escala para física (base de figura + escala manual)
float scale_factor = active_shape_->getScaleFactor(static_cast<float>(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<int>(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<float>(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<int>(scene_mgr_->getBallCount());
active_shape_->generatePoints(num_points, static_cast<float>(screen_width_), static_cast<float>(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<SphereShape>();
break;
case ShapeType::CUBE:
active_shape_ = std::make_unique<CubeShape>();
break;
case ShapeType::HELIX:
active_shape_ = std::make_unique<HelixShape>();
break;
case ShapeType::TORUS:
active_shape_ = std::make_unique<TorusShape>();
break;
case ShapeType::LISSAJOUS:
active_shape_ = std::make_unique<LissajousShape>();
break;
case ShapeType::CYLINDER:
active_shape_ = std::make_unique<CylinderShape>();
break;
case ShapeType::ICOSAHEDRON:
active_shape_ = std::make_unique<IcosahedronShape>();
break;
case ShapeType::ATOM:
active_shape_ = std::make_unique<AtomShape>();
break;
case ShapeType::PNG_SHAPE:
active_shape_ = std::make_unique<PNGShape>("data/shapes/jailgames.png");
break;
default:
active_shape_ = std::make_unique<SphereShape>(); // 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_));
}