refactor: eliminar Rotation3D i el seu camí de codi (codi mort)

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 16:50:03 +02:00
parent 682c27c07c
commit 707fd29b97
5 changed files with 117 additions and 183 deletions
+42 -86
View File
@@ -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<Graphics::Shape>& 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<Graphics::Shape>& 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<int>(P1.x), static_cast<int>(P1.y),
static_cast<int>(P2.x), static_cast<int>(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<int>(P1.x), static_cast<int>(P1.y), static_cast<int>(P2.x), static_cast<int>(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<int>(P1.x), static_cast<int>(P1.y), static_cast<int>(P2.x), static_cast<int>(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<int>(P1.x), static_cast<int>(P1.y),
static_cast<int>(P2.x), static_cast<int>(P2.y), brightness, 0.0F, color);
}
}
}
} // namespace Rendering
+17 -39
View File
@@ -3,53 +3,31 @@
#pragma once
#include "core/rendering/render_context.hpp"
#include <SDL3/SDL.h>
#include <memory>
#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<Graphics::Shape>& 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<Graphics::Shape>& 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
+5 -7
View File
@@ -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);
}
}
+46 -42
View File
@@ -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<float>(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<float>(std::rand()) / RAND_MAX) * FREQ_RANGE);
((static_cast<float>(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<float>(std::rand()) / RAND_MAX) * AMP_RANGE);
((static_cast<float>(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<float>(std::rand()) / RAND_MAX) * DUR_RANGE);
((static_cast<float>(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<float>(std::rand()) / RAND_MAX) * MULT_RANGE);
((static_cast<float>(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<float>(std::rand()) / RAND_MAX) * DUR_RANGE);
((static_cast<float>(std::rand()) / RAND_MAX) * DUR_RANGE);
}
}
}
+7 -9
View File
@@ -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);
}