From 707fd29b97a14cfc441a8533251780b09b6ae4bc Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 20 May 2026 16:50:03 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20eliminar=20Rotation3D=20i=20el=20se?= =?UTF-8?q?u=20cam=C3=AD=20de=20codi=20(codi=20mort)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'struct Rotation3D, la funció apply3dRotation i el paràmetre opcional rotation_3d de renderShape mai s'activaven en cap caller: - Ship, Enemy i Bullet passaven explícitament nullptr. - Title scene, logo scene, starfield, vector_text i ship_animator usaven el default nullptr (set els 7 callers). CLAUDE.md descriu un sistema 3D del title screen que ja no està viu — el comentari en ship_animator.cpp aclareix que la perspectiva s'ha bakeat dins de la shape, així que la rotació dinàmica era residu històric. Esborrats: struct Rotation3D + ctors + hasRotation(), apply3dRotation(), la branca rotation_3d a transformPoint() i el seu paràmetre, el paràmetre rotation_3d de renderShape, i els arguments nullptr als 3 callers d'entitats. Hallazgo #16 de CODE_REVIEW.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- source/core/rendering/shape_renderer.cpp | 128 ++++++++--------------- source/core/rendering/shape_renderer.hpp | 56 +++------- source/game/entities/bullet.cpp | 12 +-- source/game/entities/enemy.cpp | 88 ++++++++-------- source/game/entities/ship.cpp | 16 ++- 5 files changed, 117 insertions(+), 183 deletions(-) diff --git a/source/core/rendering/shape_renderer.cpp b/source/core/rendering/shape_renderer.cpp index 7ec2d7e..e18ff64 100644 --- a/source/core/rendering/shape_renderer.cpp +++ b/source/core/rendering/shape_renderer.cpp @@ -9,102 +9,58 @@ namespace Rendering { -// Helper: aplicar rotación 3D a un point 2D (assumeix Z=0) -static auto apply3dRotation(float x, float y, const Rotation3D& rot) -> Vec2 { - float z = 0.0F; // Todos los points 2D comencen a Z=0 + // Helper: transformar un point con rotación, scale i traslación + static auto transformPoint(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale) -> Vec2 { + // 1. Centrar el point respecte al centro de la shape + float centered_x = point.x - shape_centre.x; + float centered_y = point.y - shape_centre.y; - // Pitch (rotación eix X): cabeceo arriba/baix - float cos_pitch = std::cos(rot.pitch); - float sin_pitch = std::sin(rot.pitch); - float y1 = (y * cos_pitch) - (z * sin_pitch); - float z1 = (y * sin_pitch) + (z * cos_pitch); + // 2. Aplicar scale al point + float scaled_x = centered_x * scale; + float scaled_y = centered_y * scale; - // Yaw (rotación eix Y): guiñada izquierda/derecha - float cos_yaw = std::cos(rot.yaw); - float sin_yaw = std::sin(rot.yaw); - float x2 = (x * cos_yaw) + (z1 * sin_yaw); - float z2 = (-x * sin_yaw) + (z1 * cos_yaw); + // 3. Aplicar rotación 2D (Z-axis) + float cos_a = std::cos(angle); + float sin_a = std::sin(angle); - // Roll (rotación eix Z): alabeo lateral - float cos_roll = std::cos(rot.roll); - float sin_roll = std::sin(rot.roll); - float x3 = (x2 * cos_roll) - (y1 * sin_roll); - float y3 = (x2 * sin_roll) + (y1 * cos_roll); + float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a); + float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a); - // Proyecció perspectiva (Z-divide simple) - // Naves quieren hacia el point de fuga (320, 240) a "infinit" (Z → +∞) - // Z més grande = més lluny = més pequeño a pantalla - constexpr float PERSPECTIVE_FACTOR = 500.0F; - float scale_factor = PERSPECTIVE_FACTOR / (PERSPECTIVE_FACTOR + z2); - - return {.x = x3 * scale_factor, .y = y3 * scale_factor}; -} - -// Helper: transformar un point con rotación, scale i traslación -static auto transformPoint(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale, const Rotation3D* rotation_3d) -> Vec2 { - // 1. Centrar el point respecte al centro de la shape - float centered_x = point.x - shape_centre.x; - float centered_y = point.y - shape_centre.y; - - // 2. Aplicar rotación 3D (si es proporciona) - if ((rotation_3d != nullptr) && rotation_3d->hasRotation()) { - Vec2 rotated_3d = apply3dRotation(centered_x, centered_y, *rotation_3d); - centered_x = rotated_3d.x; - centered_y = rotated_3d.y; + // 4. Aplicar traslación a posición mundial + return {.x = rotated_x + position.x, .y = rotated_y + position.y}; } - // 3. Aplicar scale al point (después de rotación 3D) - float scaled_x = centered_x * scale; - float scaled_y = centered_y * scale; + void renderShape(Rendering::Renderer* renderer, + const std::shared_ptr& shape, + const Vec2& position, + float angle, + float scale, + float progress, + float brightness, + SDL_Color color) { + if (!shape || !shape->isValid()) { + return; + } + if (progress < 1.0F) { + return; + } - // 4. Aplicar rotación 2D (Z-axis, tradicional) - // IMPORTANT: En el sistema original, angle=0 apunta AMUNT (no derecha) - // Per això usem (angle - PI/2) per compensar - // Pero aquí angle ya ve en el sistema correcte del juego - float cos_a = std::cos(angle); - float sin_a = std::sin(angle); + const Vec2& shape_centre = shape->getCenter(); - float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a); - float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a); - - // 5. Aplicar traslación a posición mundial - return {.x = rotated_x + position.x, .y = rotated_y + position.y}; -} - -void renderShape(Rendering::Renderer* renderer, - const std::shared_ptr& shape, - const Vec2& position, - float angle, - float scale, - float progress, - float brightness, - const Rotation3D* rotation_3d, - SDL_Color color) { - if (!shape || !shape->isValid()) { - return; - } - if (progress < 1.0F) { - return; - } - - const Vec2& shape_centre = shape->getCenter(); - - for (const auto& primitive : shape->getPrimitives()) { - if (primitive.type == Graphics::PrimitiveType::POLYLINE) { - // POLYLINE: conectar puntos consecutivos. - for (size_t i = 0; i < primitive.points.size() - 1; i++) { - const Vec2 P1 = transformPoint(primitive.points[i], shape_centre, position, angle, scale, rotation_3d); - const Vec2 P2 = transformPoint(primitive.points[i + 1], shape_centre, position, angle, scale, rotation_3d); - linea(renderer, static_cast(P1.x), static_cast(P1.y), - static_cast(P2.x), static_cast(P2.y), brightness, 0.0F, color); + for (const auto& primitive : shape->getPrimitives()) { + if (primitive.type == Graphics::PrimitiveType::POLYLINE) { + // POLYLINE: conectar puntos consecutivos. + for (size_t i = 0; i < primitive.points.size() - 1; i++) { + const Vec2 P1 = transformPoint(primitive.points[i], shape_centre, position, angle, scale); + const Vec2 P2 = transformPoint(primitive.points[i + 1], shape_centre, position, angle, scale); + linea(renderer, static_cast(P1.x), static_cast(P1.y), static_cast(P2.x), static_cast(P2.y), brightness, 0.0F, color); + } + } else if (primitive.points.size() >= 2) { // LINE + const Vec2 P1 = transformPoint(primitive.points[0], shape_centre, position, angle, scale); + const Vec2 P2 = transformPoint(primitive.points[1], shape_centre, position, angle, scale); + linea(renderer, static_cast(P1.x), static_cast(P1.y), static_cast(P2.x), static_cast(P2.y), brightness, 0.0F, color); } - } else if (primitive.points.size() >= 2) { // LINE - const Vec2 P1 = transformPoint(primitive.points[0], shape_centre, position, angle, scale, rotation_3d); - const Vec2 P2 = transformPoint(primitive.points[1], shape_centre, position, angle, scale, rotation_3d); - linea(renderer, static_cast(P1.x), static_cast(P1.y), - static_cast(P2.x), static_cast(P2.y), brightness, 0.0F, color); } } -} } // namespace Rendering diff --git a/source/core/rendering/shape_renderer.hpp b/source/core/rendering/shape_renderer.hpp index 88c156b..43eed6b 100644 --- a/source/core/rendering/shape_renderer.hpp +++ b/source/core/rendering/shape_renderer.hpp @@ -3,53 +3,31 @@ #pragma once -#include "core/rendering/render_context.hpp" - #include #include #include "core/graphics/shape.hpp" +#include "core/rendering/render_context.hpp" #include "core/types.hpp" namespace Rendering { -// Estructura per rotacions 3D (pitch, yaw, roll) -struct Rotation3D { - float pitch; // Rotación eix X (cabeceo arriba/baix) - float yaw; // Rotación eix Y (guiñada izquierda/derecha) - float roll; // Rotación eix Z (alabeo lateral) - - Rotation3D() - : pitch(0.0F), - yaw(0.0F), - roll(0.0F) {} - Rotation3D(float p, float y, float r) - : pitch(p), - yaw(y), - roll(r) {} - - [[nodiscard]] auto hasRotation() const -> bool { - return pitch != 0.0F || yaw != 0.0F || roll != 0.0F; - } -}; - -// Renderizar shape con transformacions -// - renderer: SDL renderer -// - shape: shape vectorial a draw -// - position: posición del centro en coordenades mundials -// - angle: rotación en radians (0 = amunt, sentit horari) -// - scale: factor de scale (1.0 = mida original) -// - progress: progrés de l'animación (0.0-1.0, default 1.0 = tot visible) -// - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness) -void renderShape(Rendering::Renderer* renderer, - const std::shared_ptr& shape, - const Vec2& position, - float angle, - float scale = 1.0F, - float progress = 1.0F, - float brightness = 1.0F, - const Rotation3D* rotation_3d = nullptr, - SDL_Color color = {0, 0, 0, 0}); // alpha==0 → usa global oscilador + // Renderizar shape con transformacions + // - renderer: SDL renderer + // - shape: shape vectorial a draw + // - position: posición del centro en coordenades mundials + // - angle: rotación en radians (0 = amunt, sentit horari) + // - scale: factor de scale (1.0 = mida original) + // - progress: progrés de l'animación (0.0-1.0, default 1.0 = tot visible) + // - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness) + void renderShape(Rendering::Renderer* renderer, + const std::shared_ptr& shape, + const Vec2& position, + float angle, + float scale = 1.0F, + float progress = 1.0F, + float brightness = 1.0F, + SDL_Color color = {0, 0, 0, 0}); // alpha==0 → usa global oscilador } // namespace Rendering diff --git a/source/game/entities/bullet.cpp b/source/game/entities/bullet.cpp index 6a838cb..6434263 100644 --- a/source/game/entities/bullet.cpp +++ b/source/game/entities/bullet.cpp @@ -17,14 +17,13 @@ #include "game/constants.hpp" namespace { -// Velocidad escalar de las balas (px/s). Conserva el feel del Pascal original -// (7 px/frame × 20 FPS = 140 px/s). -constexpr float BULLET_SPEED = 140.0F; + // Velocidad escalar de las balas (px/s). Conserva el feel del Pascal original + // (7 px/frame × 20 FPS = 140 px/s). + constexpr float BULLET_SPEED = 140.0F; } // namespace Bullet::Bullet(Rendering::Renderer* renderer) - : Entity(renderer) - { + : Entity(renderer) { // Brightness específico para balas brightness_ = Defaults::Brightness::BALA; @@ -134,7 +133,6 @@ void Bullet::desactivar() { void Bullet::draw() const { if (is_active_ && shape_) { // Les bales roten segons l'angle de trayectòria (estático tras disparo) - Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, - /*rotation_3d=*/nullptr, Defaults::Palette::BULLET); + Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, Defaults::Palette::BULLET); } } diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index 388afe2..c23a846 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -17,41 +17,40 @@ namespace { -// Velocidad inicial vectorial a partir de un ángulo (rad). -// angle=0 apunta hacia arriba (eje Y negativo SDL), como el resto del juego. -auto angleToDirection(float angle) -> Vec2 { - return Vec2{ - .x = std::cos(angle - (Constants::PI / 2.0F)), - .y = std::sin(angle - (Constants::PI / 2.0F)), - }; -} - -// Recupera el "ángulo equivalente" de un body en movimiento (para zigzag). -// Si está parado, devuelve 0. -auto velocityToAngle(const Vec2& velocity) -> float { - if (velocity.lengthSquared() < 0.0001F) { - return 0.0F; + // Velocidad inicial vectorial a partir de un ángulo (rad). + // angle=0 apunta hacia arriba (eje Y negativo SDL), como el resto del juego. + auto angleToDirection(float angle) -> Vec2 { + return Vec2{ + .x = std::cos(angle - (Constants::PI / 2.0F)), + .y = std::sin(angle - (Constants::PI / 2.0F)), + }; + } + + // Recupera el "ángulo equivalente" de un body en movimiento (para zigzag). + // Si está parado, devuelve 0. + auto velocityToAngle(const Vec2& velocity) -> float { + if (velocity.lengthSquared() < 0.0001F) { + return 0.0F; + } + // El movimiento (vx, vy) corresponde a angle - PI/2; invertimos. + return std::atan2(velocity.y, velocity.x) + (Constants::PI / 2.0F); } - // El movimiento (vx, vy) corresponde a angle - PI/2; invertimos. - return std::atan2(velocity.y, velocity.x) + (Constants::PI / 2.0F); -} } // namespace Enemy::Enemy(Rendering::Renderer* renderer) : Entity(renderer), - - tracking_strength_(0.5F) - { + + tracking_strength_(0.5F) { brightness_ = Defaults::Brightness::ENEMIC; // Configuración del cuerpo físico — defaults para enemy genérico. // init() ajusta velocidad y masa según el tipo (Pentagon/Quadrat/Molinillo). - body_.setMass(5.0F); // Más liviano que la nave (10.0) - body_.radius = 0.0F; // 0 hasta spawn (no colisiona inactivo) - body_.restitution = 1.0F; // Rebote elástico perfecto contra paredes - body_.linear_damping = 0.0F; // Sin fricción: mantienen velocidad - body_.angular_damping = 0.0F; // Idem + body_.setMass(5.0F); // Más liviano que la nave (10.0) + body_.radius = 0.0F; // 0 hasta spawn (no colisiona inactivo) + body_.restitution = 1.0F; // Rebote elástico perfecto contra paredes + body_.linear_damping = 0.0F; // Sin fricción: mantienen velocidad + body_.angular_damping = 0.0F; // Idem } void Enemy::init(EnemyType type, const Vec2* ship_pos) { @@ -225,12 +224,17 @@ void Enemy::draw() const { const float SCALE = computeCurrentScale(); SDL_Color color{}; switch (type_) { - case EnemyType::PENTAGON: color = Defaults::Palette::PENTAGON; break; - case EnemyType::QUADRAT: color = Defaults::Palette::QUADRAT; break; - case EnemyType::MOLINILLO: color = Defaults::Palette::MOLINILLO; break; + case EnemyType::PENTAGON: + color = Defaults::Palette::PENTAGON; + break; + case EnemyType::QUADRAT: + color = Defaults::Palette::QUADRAT; + break; + case EnemyType::MOLINILLO: + color = Defaults::Palette::MOLINILLO; + break; } - Rendering::renderShape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_, - /*rotation_3d=*/nullptr, color); + Rendering::renderShape(renderer_, shape_, center_, rotacio_, SCALE, 1.0F, brightness_, color); } void Enemy::destruir() { @@ -269,7 +273,7 @@ void Enemy::behaviorPentagon(float delta_time) { if (RAND_VAL < ZIGZAG_PROB_PER_SECOND * delta_time) { const float CURRENT_ANGLE = velocityToAngle(body_.velocity); const float DELTA = (static_cast(std::rand()) / RAND_MAX) * - Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX; + Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX; const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA); const float SPEED = body_.velocity.length(); setVelocityFromAngle(NEW_ANGLE, SPEED); @@ -294,7 +298,7 @@ void Enemy::behaviorQuadrat(float delta_time) { // Mezcla LERP: velocidad actual con la deseada según tracking_strength_. body_.velocity = (body_.velocity * (1.0F - tracking_strength_)) + - (DESIRED_VEL * tracking_strength_); + (DESIRED_VEL * tracking_strength_); // Renormalizar a la velocidad escalar original const float NEW_SPEED = body_.velocity.length(); @@ -342,19 +346,19 @@ void Enemy::updatePalpitation(float delta_time) { animacio_.palpitacio_fase = 0.0F; const float FREQ_RANGE = Defaults::Enemies::Animation::PALPITACIO_FREQ_MAX - - Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN; + Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN; animacio_.palpitacio_frequencia = Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN + - ((static_cast(std::rand()) / RAND_MAX) * FREQ_RANGE); + ((static_cast(std::rand()) / RAND_MAX) * FREQ_RANGE); const float AMP_RANGE = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MAX - - Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN; + Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN; animacio_.palpitacio_amplitud = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN + - ((static_cast(std::rand()) / RAND_MAX) * AMP_RANGE); + ((static_cast(std::rand()) / RAND_MAX) * AMP_RANGE); const float DUR_RANGE = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MAX - - Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN; + Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN; animacio_.palpitacio_temps_restant = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN + - ((static_cast(std::rand()) / RAND_MAX) * DUR_RANGE); + ((static_cast(std::rand()) / RAND_MAX) * DUR_RANGE); } } } @@ -380,15 +384,15 @@ void Enemy::updateRotationAcceleration(float delta_time) { animacio_.drotacio_t = 0.0F; const float MULT_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MAX - - Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN; + Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN; const float MULTIPLIER = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN + - ((static_cast(std::rand()) / RAND_MAX) * MULT_RANGE); + ((static_cast(std::rand()) / RAND_MAX) * MULT_RANGE); animacio_.drotacio_objetivo = animacio_.drotacio_base * MULTIPLIER; const float DUR_RANGE = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MAX - - Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN; + Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN; animacio_.drotacio_duracio = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN + - ((static_cast(std::rand()) / RAND_MAX) * DUR_RANGE); + ((static_cast(std::rand()) / RAND_MAX) * DUR_RANGE); } } } diff --git a/source/game/entities/ship.cpp b/source/game/entities/ship.cpp index e954759..f4b129e 100644 --- a/source/game/entities/ship.cpp +++ b/source/game/entities/ship.cpp @@ -20,17 +20,16 @@ #include "game/constants.hpp" Ship::Ship(Rendering::Renderer* renderer, const char* shape_file) - : Entity(renderer) - { + : Entity(renderer) { // Brightness específico para naves brightness_ = Defaults::Brightness::NAU; // Configuración del cuerpo físico - body_.setMass(10.0F); // Masa de referencia para choques - body_.radius = Defaults::Entities::SHIP_RADIUS; // Radio de colisión - body_.restitution = 0.6F; // Rebote moderado contra paredes - body_.linear_damping = 1.5F; // Fricción exponencial (s⁻¹) - body_.angular_damping = 0.0F; // La rotación es 100% por input, no inercial + body_.setMass(10.0F); // Masa de referencia para choques + body_.radius = Defaults::Entities::SHIP_RADIUS; // Radio de colisión + body_.restitution = 0.6F; // Rebote moderado contra paredes + body_.linear_damping = 1.5F; // Fricción exponencial (s⁻¹) + body_.angular_damping = 0.0F; // La rotación es 100% por input, no inercial // Cargar shape compartida desde archivo shape_ = Graphics::ShapeLoader::load(shape_file); @@ -158,6 +157,5 @@ void Ship::draw() const { const float VISUAL_PUSH = SPEED / 33.33F; const float SCALE = 1.0F + (VISUAL_PUSH / 12.0F); - Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, - /*rotation_3d=*/nullptr, Defaults::Palette::SHIP); + Rendering::renderShape(renderer_, shape_, center_, angle_, SCALE, 1.0F, brightness_, Defaults::Palette::SHIP); }