diff --git a/CMakeLists.txt b/CMakeLists.txt
index d37fd20..446a1c4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
-project(orni VERSION 0.3.1)
+project(orni VERSION 0.4.0)
# Info del proyecto
set(PROJECT_LONG_NAME "Orni Attack")
diff --git a/data/shapes/title/letra_a.shp b/data/shapes/title/letra_a.shp
new file mode 100644
index 0000000..46f740a
--- /dev/null
+++ b/data/shapes/title/letra_a.shp
@@ -0,0 +1,10 @@
+# letra_a.shp
+# Generado automáticamente desde jailgames.svg
+# Dimensiones: 137.50 x 100.00 px
+
+name: letra_a
+scale: 1.0
+center: 68.75, 50.00
+
+polyline: 0.00,100.00 0.00,75.00 37.50,0.00 100.00,0.00 137.50,75.00 137.50,100.00 100.00,100.00 100.00,87.50 37.50,87.50 37.50,100.00 0.00,100.00
+polyline: 62.50,25.00 50.00,50.00 50.00,62.50 87.50,62.50 87.50,50.00 75.00,25.00 62.50,25.00
diff --git a/data/shapes/title/letra_c.shp b/data/shapes/title/letra_c.shp
new file mode 100644
index 0000000..54389f6
--- /dev/null
+++ b/data/shapes/title/letra_c.shp
@@ -0,0 +1,9 @@
+# letra_c.shp
+# Generado automáticamente desde jailgames.svg
+# Dimensiones: 137.50 x 100.00 px
+
+name: letra_c
+scale: 1.0
+center: 68.75, 50.00
+
+polyline: 12.50,100.00 0.00,87.50 0.00,12.50 12.50,0.00 125.00,0.00 137.50,12.50 137.50,37.50 100.00,37.50 100.00,25.00 37.50,25.00 37.50,75.00 100.00,75.00 100.00,62.50 137.50,62.50 137.50,87.50 125.00,100.00 12.50,100.00
diff --git a/data/shapes/title/letra_exclamacion.shp b/data/shapes/title/letra_exclamacion.shp
new file mode 100644
index 0000000..5f27e11
--- /dev/null
+++ b/data/shapes/title/letra_exclamacion.shp
@@ -0,0 +1,10 @@
+# letra_exclamacion.shp
+# Generado automáticamente desde jailgames.svg
+# Dimensiones: 37.51 x 100.00 px
+
+name: letra_exclamacion
+scale: 1.0
+center: 18.75, 50.00
+
+polyline: 0.00,62.50 0.00,0.00 37.51,0.00 37.51,62.50 0.00,62.50
+polyline: 0.00,100.00 0.00,75.00 37.51,75.00 37.51,100.00 0.00,100.00
diff --git a/data/shapes/title/letra_i.shp b/data/shapes/title/letra_i.shp
new file mode 100644
index 0000000..f3677d4
--- /dev/null
+++ b/data/shapes/title/letra_i.shp
@@ -0,0 +1,9 @@
+# letra_i.shp
+# Generado automáticamente desde jailgames.svg
+# Dimensiones: 37.50 x 100.00 px
+
+name: letra_i
+scale: 1.0
+center: 18.75, 50.00
+
+polyline: 0.00,0.00 37.50,0.00 37.50,100.00 0.00,100.00 0.00,0.00
diff --git a/data/shapes/title/letra_k.shp b/data/shapes/title/letra_k.shp
new file mode 100644
index 0000000..9266eaa
--- /dev/null
+++ b/data/shapes/title/letra_k.shp
@@ -0,0 +1,9 @@
+# letra_k.shp
+# Generado automáticamente desde jailgames.svg
+# Dimensiones: 137.50 x 100.00 px
+
+name: letra_k
+scale: 1.0
+center: 68.75, 50.00
+
+polyline: 0.00,100.00 0.00,0.00 37.50,0.00 37.50,37.50 50.00,37.50 100.00,0.00 137.50,0.00 137.50,25.00 87.06,50.00 137.50,75.00 137.50,100.00 100.00,100.00 50.00,62.50 37.50,62.50 37.50,100.00 0.00,100.00
diff --git a/data/shapes/title/letra_n.shp b/data/shapes/title/letra_n.shp
new file mode 100644
index 0000000..2ff6535
--- /dev/null
+++ b/data/shapes/title/letra_n.shp
@@ -0,0 +1,9 @@
+# letra_n.shp
+# Generado automáticamente desde jailgames.svg
+# Dimensiones: 137.50 x 100.00 px
+
+name: letra_n
+scale: 1.0
+center: 68.75, 50.00
+
+polyline: 0.00,100.00 0.00,0.00 50.00,0.00 100.00,50.00 100.00,0.00 137.50,0.00 137.50,100.00 87.50,100.00 37.50,50.00 37.50,100.00 0.00,100.00
diff --git a/data/shapes/title/letra_o.shp b/data/shapes/title/letra_o.shp
new file mode 100644
index 0000000..36226da
--- /dev/null
+++ b/data/shapes/title/letra_o.shp
@@ -0,0 +1,10 @@
+# letra_o.shp
+# Generado automáticamente desde jailgames.svg
+# Dimensiones: 137.50 x 100.00 px
+
+name: letra_o
+scale: 1.0
+center: 68.75, 50.00
+
+polyline: 12.50,100.00 0.00,87.50 0.00,12.50 12.50,0.00 125.00,0.00 137.50,12.50 137.50,87.50 125.00,100.00 12.50,100.00
+polyline: 100.00,25.00 37.50,25.00 37.50,75.00 100.00,75.00 100.00,25.00
diff --git a/data/shapes/title/letra_r.shp b/data/shapes/title/letra_r.shp
new file mode 100644
index 0000000..96a86b3
--- /dev/null
+++ b/data/shapes/title/letra_r.shp
@@ -0,0 +1,10 @@
+# letra_r.shp
+# Generado automáticamente desde jailgames.svg
+# Dimensiones: 137.50 x 100.00 px
+
+name: letra_r
+scale: 1.0
+center: 68.75, 50.00
+
+polyline: 0.00,100.00 0.00,0.00 125.00,0.00 137.50,12.50 137.50,62.50 125.00,62.50 137.50,75.00 137.50,100.00 100.00,100.00 100.00,75.00 37.50,75.00 37.50,100.00 0.00,100.00
+polyline: 37.50,50.00 100.00,50.00 100.00,25.00 37.50,25.00 37.50,50.00
diff --git a/data/shapes/title/letra_t.shp b/data/shapes/title/letra_t.shp
new file mode 100644
index 0000000..616b976
--- /dev/null
+++ b/data/shapes/title/letra_t.shp
@@ -0,0 +1,9 @@
+# letra_t.shp
+# Generado automáticamente desde jailgames.svg
+# Dimensiones: 137.50 x 100.00 px
+
+name: letra_t
+scale: 1.0
+center: 68.75, 50.00
+
+polyline: 0.00,25.00 0.00,0.00 137.50,0.00 137.50,25.00 87.50,25.00 87.50,100.00 50.00,100.00 50.00,25.00 0.00,25.00
diff --git a/jailgames.svg b/jailgames.svg
deleted file mode 100644
index 8b69eb2..0000000
--- a/jailgames.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
diff --git a/sample.png b/sample.png
deleted file mode 100644
index 8c394a2..0000000
Binary files a/sample.png and /dev/null differ
diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp
index e19a5df..0394862 100644
--- a/source/core/defaults.hpp
+++ b/source/core/defaults.hpp
@@ -178,53 +178,53 @@ constexpr const char* LASER = "laser_shoot.wav"; // Disparo
namespace Enemies {
// Pentagon (esquivador - zigzag evasion)
namespace Pentagon {
-constexpr float VELOCITAT = 35.0f; // px/s (slightly slower)
-constexpr float CANVI_ANGLE_PROB = 0.20f; // 20% per wall hit (frequent zigzag)
-constexpr float CANVI_ANGLE_MAX = 1.0f; // Max random angle change (rad)
-constexpr float DROTACIO_MIN = 0.5f; // Min visual rotation (rad/s)
-constexpr float DROTACIO_MAX = 2.5f; // Max visual rotation (rad/s)
+constexpr float VELOCITAT = 35.0f; // px/s (slightly slower)
+constexpr float CANVI_ANGLE_PROB = 0.20f; // 20% per wall hit (frequent zigzag)
+constexpr float CANVI_ANGLE_MAX = 1.0f; // Max random angle change (rad)
+constexpr float DROTACIO_MIN = 0.5f; // Min visual rotation (rad/s)
+constexpr float DROTACIO_MAX = 2.5f; // Max visual rotation (rad/s)
constexpr const char* SHAPE_FILE = "enemy_pentagon.shp";
} // namespace Pentagon
// Quadrat (perseguidor - tracks player)
namespace Quadrat {
-constexpr float VELOCITAT = 40.0f; // px/s (medium speed)
-constexpr float TRACKING_STRENGTH = 0.5f; // Interpolation toward player (0.0-1.0)
-constexpr float TRACKING_INTERVAL = 1.0f; // Seconds between angle updates
-constexpr float DROTACIO_MIN = 0.2f; // Slow rotation
+constexpr float VELOCITAT = 40.0f; // px/s (medium speed)
+constexpr float TRACKING_STRENGTH = 0.5f; // Interpolation toward player (0.0-1.0)
+constexpr float TRACKING_INTERVAL = 1.0f; // Seconds between angle updates
+constexpr float DROTACIO_MIN = 0.2f; // Slow rotation
constexpr float DROTACIO_MAX = 1.0f;
constexpr const char* SHAPE_FILE = "enemy_square.shp";
} // namespace Quadrat
// Molinillo (agressiu - fast straight lines, proximity spin-up)
namespace Molinillo {
-constexpr float VELOCITAT = 50.0f; // px/s (fastest)
-constexpr float CANVI_ANGLE_PROB = 0.05f; // 5% per wall hit (rare direction change)
-constexpr float CANVI_ANGLE_MAX = 0.3f; // Small angle adjustments
-constexpr float DROTACIO_MIN = 2.0f; // Base rotation (rad/s)
+constexpr float VELOCITAT = 50.0f; // px/s (fastest)
+constexpr float CANVI_ANGLE_PROB = 0.05f; // 5% per wall hit (rare direction change)
+constexpr float CANVI_ANGLE_MAX = 0.3f; // Small angle adjustments
+constexpr float DROTACIO_MIN = 2.0f; // Base rotation (rad/s)
constexpr float DROTACIO_MAX = 4.0f;
constexpr float DROTACIO_PROXIMITY_MULTIPLIER = 3.0f; // Spin-up multiplier when near ship
-constexpr float PROXIMITY_DISTANCE = 100.0f; // Distance threshold (px)
+constexpr float PROXIMITY_DISTANCE = 100.0f; // Distance threshold (px)
constexpr const char* SHAPE_FILE = "enemy_pinwheel.shp";
} // namespace Molinillo
// Animation parameters (shared)
namespace Animation {
// Palpitation
-constexpr float PALPITACIO_TRIGGER_PROB = 0.01f; // 1% chance per second
-constexpr float PALPITACIO_DURACIO_MIN = 1.0f; // Min duration (seconds)
-constexpr float PALPITACIO_DURACIO_MAX = 3.0f; // Max duration (seconds)
-constexpr float PALPITACIO_AMPLITUD_MIN = 0.08f; // Min scale variation
-constexpr float PALPITACIO_AMPLITUD_MAX = 0.20f; // Max scale variation
-constexpr float PALPITACIO_FREQ_MIN = 1.5f; // Min frequency (Hz)
-constexpr float PALPITACIO_FREQ_MAX = 3.0f; // Max frequency (Hz)
+constexpr float PALPITACIO_TRIGGER_PROB = 0.01f; // 1% chance per second
+constexpr float PALPITACIO_DURACIO_MIN = 1.0f; // Min duration (seconds)
+constexpr float PALPITACIO_DURACIO_MAX = 3.0f; // Max duration (seconds)
+constexpr float PALPITACIO_AMPLITUD_MIN = 0.08f; // Min scale variation
+constexpr float PALPITACIO_AMPLITUD_MAX = 0.20f; // Max scale variation
+constexpr float PALPITACIO_FREQ_MIN = 1.5f; // Min frequency (Hz)
+constexpr float PALPITACIO_FREQ_MAX = 3.0f; // Max frequency (Hz)
// Rotation acceleration
-constexpr float ROTACIO_ACCEL_TRIGGER_PROB = 0.005f; // 0.5% chance per second
-constexpr float ROTACIO_ACCEL_DURACIO_MIN = 3.0f; // Min transition time
-constexpr float ROTACIO_ACCEL_DURACIO_MAX = 8.0f; // Max transition time
-constexpr float ROTACIO_ACCEL_MULTIPLIER_MIN = 0.5f; // Min speed multiplier
-constexpr float ROTACIO_ACCEL_MULTIPLIER_MAX = 2.5f; // Max speed multiplier
+constexpr float ROTACIO_ACCEL_TRIGGER_PROB = 0.005f; // 0.5% chance per second
+constexpr float ROTACIO_ACCEL_DURACIO_MIN = 3.0f; // Min transition time
+constexpr float ROTACIO_ACCEL_DURACIO_MAX = 8.0f; // Max transition time
+constexpr float ROTACIO_ACCEL_MULTIPLIER_MIN = 0.5f; // Min speed multiplier
+constexpr float ROTACIO_ACCEL_MULTIPLIER_MAX = 2.5f; // Max speed multiplier
} // namespace Animation
} // namespace Enemies
} // namespace Defaults
diff --git a/source/core/graphics/starfield.cpp b/source/core/graphics/starfield.cpp
index ad97e0e..9c0e729 100644
--- a/source/core/graphics/starfield.cpp
+++ b/source/core/graphics/starfield.cpp
@@ -17,10 +17,10 @@ Starfield::Starfield(SDL_Renderer* renderer,
const Punt& punt_fuga,
const SDL_FRect& area,
int densitat)
- : renderer_(renderer)
- , punt_fuga_(punt_fuga)
- , area_(area)
- , densitat_(densitat) {
+ : renderer_(renderer),
+ punt_fuga_(punt_fuga),
+ area_(area),
+ densitat_(densitat) {
// Carregar forma d'estrella
shape_estrella_ = std::make_shared("data/shapes/star.shp");
@@ -84,9 +84,9 @@ void Starfield::inicialitzar_estrella(Estrella& estrella) {
// Verificar si una estrella està fora de l'àrea
bool Starfield::fora_area(const Estrella& estrella) const {
return (estrella.posicio.x < area_.x ||
- estrella.posicio.x > area_.x + area_.w ||
- estrella.posicio.y < area_.y ||
- estrella.posicio.y > area_.y + area_.h);
+ estrella.posicio.x > area_.x + area_.w ||
+ estrella.posicio.y < area_.y ||
+ estrella.posicio.y > area_.y + area_.h);
}
// Calcular escala dinàmica segons distància del centre
@@ -96,7 +96,7 @@ float Starfield::calcular_escala(const Estrella& estrella) const {
// Interpolació lineal basada en distància del centre
// distancia_centre: 0.0 (centre) → 1.0 (vora)
return capa.escala_min +
- (capa.escala_max - capa.escala_min) * estrella.distancia_centre;
+ (capa.escala_max - capa.escala_min) * estrella.distancia_centre;
}
// Calcular brightness dinàmica segons distància del centre
@@ -104,8 +104,8 @@ float Starfield::calcular_brightness(const Estrella& estrella) const {
// Interpolació lineal: estrelles properes (vora) més brillants
// distancia_centre: 0.0 (centre, llunyanes) → 1.0 (vora, properes)
return Defaults::Brightness::STARFIELD_MIN +
- (Defaults::Brightness::STARFIELD_MAX - Defaults::Brightness::STARFIELD_MIN) *
- estrella.distancia_centre;
+ (Defaults::Brightness::STARFIELD_MAX - Defaults::Brightness::STARFIELD_MIN) *
+ estrella.distancia_centre;
}
// Actualitzar posicions de les estrelles
diff --git a/source/core/graphics/starfield.hpp b/source/core/graphics/starfield.hpp
index 40b5df4..4109503 100644
--- a/source/core/graphics/starfield.hpp
+++ b/source/core/graphics/starfield.hpp
@@ -15,66 +15,66 @@ namespace Graphics {
// Configuració per cada capa de profunditat
struct CapaConfig {
- float velocitat_base; // Velocitat base d'aquesta capa (px/s)
- float escala_min; // Escala mínima prop del centre
- float escala_max; // Escala màxima al límit de pantalla
- int num_estrelles; // Nombre d'estrelles en aquesta capa
+ float velocitat_base; // Velocitat base d'aquesta capa (px/s)
+ float escala_min; // Escala mínima prop del centre
+ float escala_max; // Escala màxima al límit de pantalla
+ int num_estrelles; // Nombre d'estrelles en aquesta capa
};
// Classe Starfield - camp d'estrelles animat amb efecte de profunditat
class Starfield {
- public:
- // Constructor
- // - renderer: SDL renderer
- // - punt_fuga: punt d'origen/fuga des d'on surten les estrelles
- // - area: rectangle on actuen les estrelles (SDL_FRect)
- // - densitat: nombre total d'estrelles (es divideix entre capes)
- Starfield(SDL_Renderer* renderer,
- const Punt& punt_fuga,
- const SDL_FRect& area,
- int densitat = 150);
+ public:
+ // Constructor
+ // - renderer: SDL renderer
+ // - punt_fuga: punt d'origen/fuga des d'on surten les estrelles
+ // - area: rectangle on actuen les estrelles (SDL_FRect)
+ // - densitat: nombre total d'estrelles (es divideix entre capes)
+ Starfield(SDL_Renderer* renderer,
+ const Punt& punt_fuga,
+ const SDL_FRect& area,
+ int densitat = 150);
- // Actualitzar posicions de les estrelles
- void actualitzar(float delta_time);
+ // Actualitzar posicions de les estrelles
+ void actualitzar(float delta_time);
- // Dibuixar totes les estrelles
- void dibuixar();
+ // Dibuixar totes les estrelles
+ void dibuixar();
- // Setters per ajustar paràmetres en temps real
- void set_punt_fuga(const Punt& punt) { punt_fuga_ = punt; }
+ // Setters per ajustar paràmetres en temps real
+ void set_punt_fuga(const Punt& punt) { punt_fuga_ = punt; }
- private:
- // Estructura interna per cada estrella
- struct Estrella {
- Punt posicio; // Posició actual
- float angle; // Angle de moviment (radians)
- float distancia_centre; // Distància normalitzada del centre (0.0-1.0)
- int capa; // Índex de capa (0=lluny, 1=mitjà, 2=prop)
- };
+ private:
+ // Estructura interna per cada estrella
+ struct Estrella {
+ Punt posicio; // Posició actual
+ float angle; // Angle de moviment (radians)
+ float distancia_centre; // Distància normalitzada del centre (0.0-1.0)
+ int capa; // Índex de capa (0=lluny, 1=mitjà, 2=prop)
+ };
- // Inicialitzar una estrella (nova o regenerada)
- void inicialitzar_estrella(Estrella& estrella);
+ // Inicialitzar una estrella (nova o regenerada)
+ void inicialitzar_estrella(Estrella& estrella);
- // Verificar si una estrella està fora de l'àrea
- bool fora_area(const Estrella& estrella) const;
+ // Verificar si una estrella està fora de l'àrea
+ bool fora_area(const Estrella& estrella) const;
- // Calcular escala dinàmica segons distància del centre
- float calcular_escala(const Estrella& estrella) const;
+ // Calcular escala dinàmica segons distància del centre
+ float calcular_escala(const Estrella& estrella) const;
- // Calcular brightness dinàmica segons distància del centre
- float calcular_brightness(const Estrella& estrella) const;
+ // Calcular brightness dinàmica segons distància del centre
+ float calcular_brightness(const Estrella& estrella) const;
- // Dades
- std::vector estrelles_;
- std::vector capes_; // Configuració de les 3 capes
- std::shared_ptr shape_estrella_;
- SDL_Renderer* renderer_;
+ // Dades
+ std::vector estrelles_;
+ std::vector capes_; // Configuració de les 3 capes
+ std::shared_ptr shape_estrella_;
+ SDL_Renderer* renderer_;
- // Configuració
- Punt punt_fuga_; // Punt d'origen de les estrelles
- SDL_FRect area_; // Àrea activa
- float radi_max_; // Distància màxima del centre al límit de pantalla
- int densitat_; // Nombre total d'estrelles
+ // Configuració
+ Punt punt_fuga_; // Punt d'origen de les estrelles
+ SDL_FRect area_; // Àrea activa
+ float radi_max_; // Distància màxima del centre al límit de pantalla
+ int densitat_; // Nombre total d'estrelles
};
} // namespace Graphics
diff --git a/source/core/rendering/line_renderer.cpp b/source/core/rendering/line_renderer.cpp
index 12e584c..d802d31 100644
--- a/source/core/rendering/line_renderer.cpp
+++ b/source/core/rendering/line_renderer.cpp
@@ -58,8 +58,7 @@ bool linea(SDL_Renderer* renderer, int x1, int y1, int x2, int y2, bool dibuixar
SDL_SetRenderDrawColor(renderer, color_final.r, color_final.g, color_final.b, 255);
// Renderitzar amb coordenades físiques
- SDL_RenderLine(renderer, static_cast(px1), static_cast(py1),
- static_cast(px2), static_cast(py2));
+ SDL_RenderLine(renderer, static_cast(px1), static_cast(py1), static_cast(px2), static_cast(py2));
}
// Algorisme de Bresenham original (conservat per a futura detecció de
diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp
index 858ab8a..6afbd53 100644
--- a/source/core/system/director.cpp
+++ b/source/core/system/director.cpp
@@ -7,13 +7,13 @@
#include
#include
+#include "core/audio/audio.hpp"
+#include "core/defaults.hpp"
+#include "core/rendering/sdl_manager.hpp"
#include "game/escenes/escena_joc.hpp"
#include "game/escenes/escena_logo.hpp"
#include "game/escenes/escena_titol.hpp"
#include "game/options.hpp"
-#include "core/audio/audio.hpp"
-#include "core/defaults.hpp"
-#include "core/rendering/sdl_manager.hpp"
#include "gestor_escenes.hpp"
#include "project.h"
diff --git a/source/core/system/global_events.cpp b/source/core/system/global_events.cpp
index 2ca8b85..26a801c 100644
--- a/source/core/system/global_events.cpp
+++ b/source/core/system/global_events.cpp
@@ -3,9 +3,9 @@
#include "global_events.hpp"
+#include "core/input/mouse.hpp"
#include "core/rendering/sdl_manager.hpp"
#include "gestor_escenes.hpp"
-#include "core/input/mouse.hpp"
namespace GlobalEvents {
diff --git a/source/game/entities/enemic.cpp b/source/game/entities/enemic.cpp
index 6db99c1..fe62ed8 100644
--- a/source/game/entities/enemic.cpp
+++ b/source/game/entities/enemic.cpp
@@ -150,7 +150,10 @@ void Enemic::comportament_pentagon(float delta_time) {
// Obtenir límits segurs
float min_x, max_x, min_y, max_y;
Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS,
- min_x, max_x, min_y, max_y);
+ min_x,
+ max_x,
+ min_y,
+ max_y);
// Zigzag: canvi d'angle més freqüent en tocar límits
if (new_y >= min_y && new_y <= max_y) {
@@ -159,7 +162,7 @@ void Enemic::comportament_pentagon(float delta_time) {
// Probabilitat més alta de canvi d'angle
if (static_cast(std::rand()) / RAND_MAX < Defaults::Enemies::Pentagon::CANVI_ANGLE_PROB) {
float rand_angle = (static_cast(std::rand()) / RAND_MAX) *
- Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
+ Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle;
}
}
@@ -169,7 +172,7 @@ void Enemic::comportament_pentagon(float delta_time) {
} else {
if (static_cast(std::rand()) / RAND_MAX < Defaults::Enemies::Pentagon::CANVI_ANGLE_PROB) {
float rand_angle = (static_cast(std::rand()) / RAND_MAX) *
- Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
+ Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle;
}
}
@@ -214,7 +217,10 @@ void Enemic::comportament_quadrat(float delta_time) {
// Obtenir límits segurs
float min_x, max_x, min_y, max_y;
Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS,
- min_x, max_x, min_y, max_y);
+ min_x,
+ max_x,
+ min_y,
+ max_y);
// Bounce on walls (simple reflection)
if (new_y >= min_y && new_y <= max_y) {
@@ -260,7 +266,10 @@ void Enemic::comportament_molinillo(float delta_time) {
// Obtenir límits segurs
float min_x, max_x, min_y, max_y;
Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS,
- min_x, max_x, min_y, max_y);
+ min_x,
+ max_x,
+ min_y,
+ max_y);
// Rare angle changes on wall hits
if (new_y >= min_y && new_y <= max_y) {
@@ -268,7 +277,7 @@ void Enemic::comportament_molinillo(float delta_time) {
} else {
if (static_cast(std::rand()) / RAND_MAX < Defaults::Enemies::Molinillo::CANVI_ANGLE_PROB) {
float rand_angle = (static_cast(std::rand()) / RAND_MAX) *
- Defaults::Enemies::Molinillo::CANVI_ANGLE_MAX;
+ Defaults::Enemies::Molinillo::CANVI_ANGLE_MAX;
angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle;
}
}
@@ -278,7 +287,7 @@ void Enemic::comportament_molinillo(float delta_time) {
} else {
if (static_cast(std::rand()) / RAND_MAX < Defaults::Enemies::Molinillo::CANVI_ANGLE_PROB) {
float rand_angle = (static_cast(std::rand()) / RAND_MAX) *
- Defaults::Enemies::Molinillo::CANVI_ANGLE_MAX;
+ Defaults::Enemies::Molinillo::CANVI_ANGLE_MAX;
angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle;
}
}
@@ -313,17 +322,17 @@ void Enemic::actualitzar_palpitacio(float delta_time) {
// Randomize parameters
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;
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;
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;
}
@@ -360,7 +369,7 @@ void Enemic::actualitzar_rotacio_accelerada(float delta_time) {
// Randomize target speed (multiplier * base)
float mult_range = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MAX -
- Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN;
+ Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN;
float multiplier = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN +
(static_cast(std::rand()) / RAND_MAX) * mult_range;
@@ -368,7 +377,7 @@ void Enemic::actualitzar_rotacio_accelerada(float delta_time) {
// Randomize duration
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;
}
diff --git a/source/game/entities/enemic.hpp b/source/game/entities/enemic.hpp
index af64d59..b9e3dcd 100644
--- a/source/game/entities/enemic.hpp
+++ b/source/game/entities/enemic.hpp
@@ -12,25 +12,25 @@
// Tipus d'enemic
enum class TipusEnemic : uint8_t {
- PENTAGON = 0, // Pentàgon esquivador (zigzag)
- QUADRAT = 1, // Quadrat perseguidor (tracks ship)
- MOLINILLO = 2 // Molinillo agressiu (fast, spinning)
+ PENTAGON = 0, // Pentàgon esquivador (zigzag)
+ QUADRAT = 1, // Quadrat perseguidor (tracks ship)
+ MOLINILLO = 2 // Molinillo agressiu (fast, spinning)
};
// Estat d'animació (palpitació i rotació accelerada)
struct AnimacioEnemic {
- // Palpitation (breathing effect)
- bool palpitacio_activa = false;
- float palpitacio_fase = 0.0f; // Phase in cycle (0.0-2π)
- float palpitacio_frequencia = 2.0f; // Hz (cycles per second)
- float palpitacio_amplitud = 0.15f; // Scale variation (±15%)
- float palpitacio_temps_restant = 0.0f; // Time remaining (seconds)
+ // Palpitation (breathing effect)
+ bool palpitacio_activa = false;
+ float palpitacio_fase = 0.0f; // Phase in cycle (0.0-2π)
+ float palpitacio_frequencia = 2.0f; // Hz (cycles per second)
+ float palpitacio_amplitud = 0.15f; // Scale variation (±15%)
+ float palpitacio_temps_restant = 0.0f; // Time remaining (seconds)
- // Rotation acceleration (long-term spin modulation)
- float drotacio_base = 0.0f; // Base rotation speed (rad/s)
- float drotacio_objetivo = 0.0f; // Target rotation speed (rad/s)
- float drotacio_t = 0.0f; // Interpolation progress (0.0-1.0)
- float drotacio_duracio = 0.0f; // Duration of transition (seconds)
+ // Rotation acceleration (long-term spin modulation)
+ float drotacio_base = 0.0f; // Base rotation speed (rad/s)
+ float drotacio_objetivo = 0.0f; // Target rotation speed (rad/s)
+ float drotacio_t = 0.0f; // Interpolation progress (0.0-1.0)
+ float drotacio_duracio = 0.0f; // Duration of transition (seconds)
};
class Enemic {
@@ -60,10 +60,10 @@ class Enemic {
// [NUEVO] Estat de la instància (separat de la geometria)
Punt centre_;
- float angle_; // Angle de moviment
+ float angle_; // Angle de moviment
float velocitat_;
- float drotacio_; // Delta rotació visual (rad/s)
- float rotacio_; // Rotació visual acumulada
+ float drotacio_; // Delta rotació visual (rad/s)
+ float rotacio_; // Rotació visual acumulada
bool esta_;
float brightness_; // Factor de brillantor (0.0-1.0)
@@ -74,8 +74,8 @@ class Enemic {
AnimacioEnemic animacio_;
// [NEW] Behavior state (type-specific)
- float tracking_timer_; // For Quadrat: time since last angle update
- const Punt* ship_position_; // Pointer to ship position (for tracking)
+ float tracking_timer_; // For Quadrat: time since last angle update
+ const Punt* ship_position_; // Pointer to ship position (for tracking)
// [EXISTING] Private methods
void mou(float delta_time);
diff --git a/source/game/entities/nau.hpp b/source/game/entities/nau.hpp
index 1bb2b9e..3231147 100644
--- a/source/game/entities/nau.hpp
+++ b/source/game/entities/nau.hpp
@@ -39,8 +39,8 @@ class Nau {
// [NUEVO] Estat de la instància (separat de la geometria)
Punt centre_;
- float angle_; // Angle d'orientació
- float velocitat_; // Velocitat (px/s)
+ float angle_; // Angle d'orientació
+ float velocitat_; // Velocitat (px/s)
bool esta_tocada_;
float brightness_; // Factor de brillantor (0.0-1.0)
diff --git a/source/game/escenes/escena_joc.cpp b/source/game/escenes/escena_joc.cpp
index 0fbcb85..cea0ca9 100644
--- a/source/game/escenes/escena_joc.cpp
+++ b/source/game/escenes/escena_joc.cpp
@@ -362,10 +362,10 @@ void EscenaJoc::tocado() {
float ship_angle = nau_.get_angle();
debris_manager_.explotar(
- nau_.get_forma(), // Ship shape (3 lines)
- ship_pos, // Center position
- ship_angle, // Ship orientation
- 1.0f, // Normal scale
+ nau_.get_forma(), // Ship shape (3 lines)
+ ship_pos, // Center position
+ ship_angle, // Ship orientation
+ 1.0f, // Normal scale
Defaults::Physics::Debris::VELOCITAT_BASE // 80 px/s
);
diff --git a/source/game/escenes/escena_joc.hpp b/source/game/escenes/escena_joc.hpp
index 3791d91..a87e0df 100644
--- a/source/game/escenes/escena_joc.hpp
+++ b/source/game/escenes/escena_joc.hpp
@@ -10,14 +10,14 @@
#include
#include
-#include "core/graphics/vector_text.hpp"
-#include "core/rendering/sdl_manager.hpp"
-#include "core/types.hpp"
#include "../constants.hpp"
#include "../effects/debris_manager.hpp"
#include "../entities/bala.hpp"
#include "../entities/enemic.hpp"
#include "../entities/nau.hpp"
+#include "core/graphics/vector_text.hpp"
+#include "core/rendering/sdl_manager.hpp"
+#include "core/types.hpp"
// Classe principal del joc (escena)
class EscenaJoc {
@@ -45,10 +45,10 @@ class EscenaJoc {
float itocado_; // Death timer (seconds)
// Lives and game over system
- int num_vides_; // Current lives count
- bool game_over_; // Game over state flag
- float game_over_timer_; // Countdown timer for auto-return (seconds)
- Punt punt_spawn_; // Configurable spawn point
+ int num_vides_; // Current lives count
+ bool game_over_; // Game over state flag
+ float game_over_timer_; // Countdown timer for auto-return (seconds)
+ Punt punt_spawn_; // Configurable spawn point
// Text vectorial
Graphics::VectorText text_;
diff --git a/source/game/escenes/escena_logo.hpp b/source/game/escenes/escena_logo.hpp
index 24c0386..345c311 100644
--- a/source/game/escenes/escena_logo.hpp
+++ b/source/game/escenes/escena_logo.hpp
@@ -9,11 +9,11 @@
#include
#include
+#include "../effects/debris_manager.hpp"
+#include "core/defaults.hpp"
#include "core/graphics/shape.hpp"
#include "core/rendering/sdl_manager.hpp"
#include "core/types.hpp"
-#include "../effects/debris_manager.hpp"
-#include "core/defaults.hpp"
class EscenaLogo {
public:
diff --git a/source/game/escenes/escena_titol.cpp b/source/game/escenes/escena_titol.cpp
index 4b22c79..f6c884f 100644
--- a/source/game/escenes/escena_titol.cpp
+++ b/source/game/escenes/escena_titol.cpp
@@ -3,11 +3,14 @@
#include "escena_titol.hpp"
+#include
#include
#include
#include "core/audio/audio.hpp"
+#include "core/graphics/shape_loader.hpp"
#include "core/input/mouse.hpp"
+#include "core/rendering/shape_renderer.hpp"
#include "core/system/gestor_escenes.hpp"
#include "core/system/global_events.hpp"
#include "project.h"
@@ -25,7 +28,8 @@ EscenaTitol::EscenaTitol(SDLManager& sdl)
Defaults::Game::HEIGHT / 2.0f};
SDL_FRect area_completa{
- 0, 0,
+ 0,
+ 0,
static_cast(Defaults::Game::WIDTH),
static_cast(Defaults::Game::HEIGHT)};
@@ -35,6 +39,146 @@ EscenaTitol::EscenaTitol(SDLManager& sdl)
area_completa,
150 // densitat: 150 estrelles (50 per capa)
);
+
+ // Inicialitzar lletres del títol "ORNI ATTACK!"
+ inicialitzar_titol();
+}
+
+void EscenaTitol::inicialitzar_titol() {
+ using namespace Graphics;
+
+ // === LÍNIA 1: "ORNI" ===
+ std::vector fitxers_orni = {
+ "title/letra_o.shp",
+ "title/letra_r.shp",
+ "title/letra_n.shp",
+ "title/letra_i.shp"};
+
+ // Pas 1: Carregar formes i calcular amplades per "ORNI"
+ float ancho_total_orni = 0.0f;
+
+ for (const auto& fitxer : fitxers_orni) {
+ auto forma = ShapeLoader::load(fitxer);
+ if (!forma || !forma->es_valida()) {
+ std::cerr << "[EscenaTitol] Error carregant " << fitxer << std::endl;
+ continue;
+ }
+
+ // Calcular bounding box de la forma (trobar ancho i altura)
+ float min_x = FLT_MAX;
+ float max_x = -FLT_MAX;
+ float min_y = FLT_MAX;
+ float max_y = -FLT_MAX;
+
+ for (const auto& prim : forma->get_primitives()) {
+ for (const auto& punt : prim.points) {
+ min_x = std::min(min_x, punt.x);
+ max_x = std::max(max_x, punt.x);
+ min_y = std::min(min_y, punt.y);
+ max_y = std::max(max_y, punt.y);
+ }
+ }
+
+ float ancho_sin_escalar = max_x - min_x;
+ float altura_sin_escalar = max_y - min_y;
+
+ // Escalar ancho, altura i offset amb ESCALA_TITULO
+ float ancho = ancho_sin_escalar * ESCALA_TITULO;
+ float altura = altura_sin_escalar * ESCALA_TITULO;
+ float offset_centre = (forma->get_centre().x - min_x) * ESCALA_TITULO;
+
+ lletres_orni_.push_back({forma, {0.0f, 0.0f}, ancho, altura, offset_centre});
+
+ ancho_total_orni += ancho;
+ }
+
+ // Afegir espaiat entre lletres
+ ancho_total_orni += ESPAI_ENTRE_LLETRES * (lletres_orni_.size() - 1);
+
+ // Calcular posició inicial (centrat horitzontal) per "ORNI"
+ float x_inicial_orni = (Defaults::Game::WIDTH - ancho_total_orni) / 2.0f;
+ float x_actual = x_inicial_orni;
+
+ for (auto& lletra : lletres_orni_) {
+ lletra.posicio.x = x_actual + lletra.offset_centre;
+ lletra.posicio.y = Y_ORNI;
+ x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
+ }
+
+ std::cout << "[EscenaTitol] Línia 1 (ORNI): " << lletres_orni_.size()
+ << " lletres, ancho total: " << ancho_total_orni << " px\n";
+
+ // === Calcular posició Y dinàmica per "ATTACK!" ===
+ // Totes les lletres ORNI tenen la mateixa altura, utilitzem la primera
+ float altura_orni = lletres_orni_.empty() ? 50.0f : lletres_orni_[0].altura;
+ y_attack_dinamica_ = Y_ORNI + altura_orni + SEPARACION_LINEAS;
+
+ std::cout << "[EscenaTitol] Altura ORNI: " << altura_orni
+ << " px, Y_ATTACK dinàmica: " << y_attack_dinamica_ << " px\n";
+
+ // === LÍNIA 2: "ATTACK!" ===
+ std::vector fitxers_attack = {
+ "title/letra_a.shp",
+ "title/letra_t.shp",
+ "title/letra_t.shp", // T repetida
+ "title/letra_a.shp", // A repetida
+ "title/letra_c.shp",
+ "title/letra_k.shp",
+ "title/letra_exclamacion.shp"};
+
+ // Pas 1: Carregar formes i calcular amplades per "ATTACK!"
+ float ancho_total_attack = 0.0f;
+
+ for (const auto& fitxer : fitxers_attack) {
+ auto forma = ShapeLoader::load(fitxer);
+ if (!forma || !forma->es_valida()) {
+ std::cerr << "[EscenaTitol] Error carregant " << fitxer << std::endl;
+ continue;
+ }
+
+ // Calcular bounding box de la forma (trobar ancho i altura)
+ float min_x = FLT_MAX;
+ float max_x = -FLT_MAX;
+ float min_y = FLT_MAX;
+ float max_y = -FLT_MAX;
+
+ for (const auto& prim : forma->get_primitives()) {
+ for (const auto& punt : prim.points) {
+ min_x = std::min(min_x, punt.x);
+ max_x = std::max(max_x, punt.x);
+ min_y = std::min(min_y, punt.y);
+ max_y = std::max(max_y, punt.y);
+ }
+ }
+
+ float ancho_sin_escalar = max_x - min_x;
+ float altura_sin_escalar = max_y - min_y;
+
+ // Escalar ancho, altura i offset amb ESCALA_TITULO
+ float ancho = ancho_sin_escalar * ESCALA_TITULO;
+ float altura = altura_sin_escalar * ESCALA_TITULO;
+ float offset_centre = (forma->get_centre().x - min_x) * ESCALA_TITULO;
+
+ lletres_attack_.push_back({forma, {0.0f, 0.0f}, ancho, altura, offset_centre});
+
+ ancho_total_attack += ancho;
+ }
+
+ // Afegir espaiat entre lletres
+ ancho_total_attack += ESPAI_ENTRE_LLETRES * (lletres_attack_.size() - 1);
+
+ // Calcular posició inicial (centrat horitzontal) per "ATTACK!"
+ float x_inicial_attack = (Defaults::Game::WIDTH - ancho_total_attack) / 2.0f;
+ x_actual = x_inicial_attack;
+
+ for (auto& lletra : lletres_attack_) {
+ lletra.posicio.x = x_actual + lletra.offset_centre;
+ lletra.posicio.y = y_attack_dinamica_; // Usar posició dinàmica
+ x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
+ }
+
+ std::cout << "[EscenaTitol] Línia 2 (ATTACK!): " << lletres_attack_.size()
+ << " lletres, ancho total: " << ancho_total_attack << " px\n";
}
void EscenaTitol::executar() {
@@ -129,22 +273,51 @@ void EscenaTitol::dibuixar() {
return;
}
- // Estat MAIN: Dibuixar text de títol i copyright (sobre el starfield)
+ // Estat MAIN: Dibuixar títol i text (sobre el starfield)
if (estat_actual_ == EstatTitol::MAIN) {
- // Text principal centrat (vertical i horitzontalment)
+ // === Dibuixar lletres del títol "ORNI ATTACK!" ===
+
+ // Dibuixar "ORNI" (línia 1)
+ for (const auto& lletra : lletres_orni_) {
+ Rendering::render_shape(
+ sdl_.obte_renderer(),
+ lletra.forma,
+ lletra.posicio,
+ 0.0f, // sense rotació
+ ESCALA_TITULO, // escala 80%
+ true, // dibuixar
+ 1.0f // progrés complet (totalment visible)
+ );
+ }
+
+ // Dibuixar "ATTACK!" (línia 2)
+ for (const auto& lletra : lletres_attack_) {
+ Rendering::render_shape(
+ sdl_.obte_renderer(),
+ lletra.forma,
+ lletra.posicio,
+ 0.0f, // sense rotació
+ ESCALA_TITULO, // escala 80%
+ true, // dibuixar
+ 1.0f // progrés complet (totalment visible)
+ );
+ }
+
+ // === Text "PRESS BUTTON TO PLAY" (a sota del títol) ===
const std::string main_text = "PRESS BUTTON TO PLAY";
const float escala_main = 1.0f;
const float spacing = 2.0f;
float text_width = text_.get_text_width(main_text, escala_main, spacing);
- float text_height = text_.get_text_height(escala_main);
float x_center = (Defaults::Game::WIDTH - text_width) / 2.0f;
- float y_center = (Defaults::Game::HEIGHT - text_height) / 2.0f;
+ // Usar posició dinàmica: ATTACK + altura lletres + separació
+ float altura_attack = lletres_attack_.empty() ? 50.0f : lletres_attack_[0].altura;
+ float y_center = y_attack_dinamica_ + altura_attack + 70.0f; // 70px sota "ATTACK!"
text_.render(main_text, Punt{x_center, y_center}, escala_main, spacing);
- // Copyright a la part inferior (centrat horitzontalment)
+ // === Copyright a la part inferior (centrat horitzontalment) ===
// Convert to uppercase since VectorText only supports A-Z
std::string copyright = Project::COPYRIGHT;
for (char& c : copyright) {
@@ -168,7 +341,6 @@ void EscenaTitol::processar_events(const SDL_Event& event) {
// Qualsevol tecla o clic de ratolí
if (event.type == SDL_EVENT_KEY_DOWN ||
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
-
switch (estat_actual_) {
case EstatTitol::INIT:
// Saltar a MAIN
diff --git a/source/game/escenes/escena_titol.hpp b/source/game/escenes/escena_titol.hpp
index 8fd38ac..a1ad2fe 100644
--- a/source/game/escenes/escena_titol.hpp
+++ b/source/game/escenes/escena_titol.hpp
@@ -7,11 +7,14 @@
#include
#include
+#include
+#include "core/defaults.hpp"
+#include "core/graphics/shape.hpp"
#include "core/graphics/starfield.hpp"
#include "core/graphics/vector_text.hpp"
#include "core/rendering/sdl_manager.hpp"
-#include "core/defaults.hpp"
+#include "core/types.hpp"
class EscenaTitol {
public:
@@ -25,17 +28,36 @@ class EscenaTitol {
MAIN // Pantalla de títol amb text
};
+ // Estructura per emmagatzemar informació de cada lletra del títol
+ struct LetraLogo {
+ std::shared_ptr forma; // Forma vectorial de la lletra
+ Punt posicio; // Posició en pantalla
+ float ancho; // Amplada escalada
+ float altura; // Altura escalada
+ float offset_centre; // Offset del centre per posicionament
+ };
+
SDLManager& sdl_;
- Graphics::VectorText text_; // Sistema de text vectorial
+ Graphics::VectorText text_; // Sistema de text vectorial
std::unique_ptr starfield_; // Camp d'estrelles de fons
- EstatTitol estat_actual_; // Estat actual de la màquina
- float temps_acumulat_; // Temps acumulat per l'estat INIT
+ EstatTitol estat_actual_; // Estat actual de la màquina
+ float temps_acumulat_; // Temps acumulat per l'estat INIT
+
+ // Lletres del títol "ORNI ATTACK!"
+ std::vector lletres_orni_; // Lletres de "ORNI" (línia 1)
+ std::vector lletres_attack_; // Lletres de "ATTACK!" (línia 2)
+ float y_attack_dinamica_; // Posició Y calculada dinàmicament per "ATTACK!"
// Constants
- static constexpr float DURACIO_INIT = 2.0f; // Duració de l'estat INIT (2 segons)
+ static constexpr float DURACIO_INIT = 2.0f; // Duració de l'estat INIT (2 segons)
+ static constexpr float ESCALA_TITULO = 0.6f; // Escala per les lletres del títol (50%)
+ static constexpr float ESPAI_ENTRE_LLETRES = 10.0f; // Espai entre lletres
+ static constexpr float Y_ORNI = 150.0f; // Posició Y de "ORNI"
+ static constexpr float SEPARACION_LINEAS = 10.0f; // Separació entre "ORNI" i "ATTACK!" (0.0f = pegades)
// Mètodes privats
void actualitzar(float delta_time);
void dibuixar();
void processar_events(const SDL_Event& event);
+ void inicialitzar_titol(); // Carrega i posiciona les lletres del títol
};
diff --git a/tools/svg_to_shp.py b/tools/svg_to_shp.py
index 7404e7c..e02c108 100755
--- a/tools/svg_to_shp.py
+++ b/tools/svg_to_shp.py
@@ -44,8 +44,9 @@ def apply_transform(punto, matrix):
def parse_svg_path(d_attr):
"""
- Convierte comandos SVG path (M y L) a lista de puntos [(x, y), ...]
- Ejemplo: "M896,1693L896,1531.23L1219.53,1531.23..." → [(896, 1693), (896, 1531.23), ...]
+ Convierte comandos SVG path (M y L) a lista de polylines separadas.
+ Cada comando M inicia una nueva polyline.
+ Retorna: lista de listas de puntos [[(x,y), ...], [(x,y), ...], ...]
"""
# Reemplazar comas por espacios para facilitar parsing
d_attr = d_attr.replace(',', ' ')
@@ -55,7 +56,9 @@ def parse_svg_path(d_attr):
d_attr = re.sub(r'([ML])', r'|\1', d_attr)
commands = [c.strip() for c in d_attr.split('|') if c.strip()]
- points = []
+ polylines = [] # Lista de polylines
+ current_polyline = [] # Polyline actual
+
for cmd in commands:
if not cmd:
continue
@@ -70,18 +73,29 @@ def parse_svg_path(d_attr):
# Parsear pares de coordenadas
coords = coords_str.split()
+ # Si es comando M (MoveTo), empezar nueva polyline
+ if cmd_letter == 'M':
+ # Guardar polyline anterior si tiene puntos
+ if current_polyline:
+ polylines.append(current_polyline)
+ current_polyline = []
+
# Procesar en pares (x, y)
i = 0
while i < len(coords) - 1:
try:
x = float(coords[i])
y = float(coords[i + 1])
- points.append((x, y))
+ current_polyline.append((x, y))
i += 2
except (ValueError, IndexError):
i += 1
- return points
+ # No olvidar la última polyline
+ if current_polyline:
+ polylines.append(current_polyline)
+
+ return polylines
def rect_to_points(rect_elem):
@@ -103,11 +117,22 @@ def rect_to_points(rect_elem):
]
-def calc_bounding_box(puntos):
+def calc_bounding_box(polylines):
"""
- Calcula bounding box de una lista de puntos
+ Calcula bounding box de una o varias polylines
+ polylines puede ser:
+ - lista de puntos [(x,y), ...]
+ - lista de polylines [[(x,y), ...], [(x,y), ...]]
Retorna: (min_x, max_x, min_y, max_y, ancho, alto)
"""
+ # Aplanar si es lista de polylines
+ puntos = []
+ if polylines and isinstance(polylines[0], list):
+ for polyline in polylines:
+ puntos.extend(polyline)
+ else:
+ puntos = polylines
+
if not puntos:
return (0, 0, 0, 0, 0, 0)
@@ -125,17 +150,18 @@ def calc_bounding_box(puntos):
return (min_x, max_x, min_y, max_y, ancho, alto)
-def normalizar_letra(nombre, puntos, altura_objetivo=100.0):
+def normalizar_letra(nombre, polylines, altura_objetivo=100.0):
"""
Escala y traslada letra para que tenga altura_objetivo pixels
y esté centrada en origen (0, 0) en esquina superior izquierda
- Retorna: dict con puntos normalizados, centro, ancho, alto
+ polylines: lista de polylines [[(x,y), ...], [(x,y), ...]]
+ Retorna: dict con polylines normalizadas, centro, ancho, alto
"""
- if not puntos:
+ if not polylines:
return None
- min_x, max_x, min_y, max_y, ancho, alto = calc_bounding_box(puntos)
+ min_x, max_x, min_y, max_y, ancho, alto = calc_bounding_box(polylines)
if alto == 0:
print(f" [WARN] Letra {nombre}: altura cero")
@@ -144,14 +170,26 @@ def normalizar_letra(nombre, puntos, altura_objetivo=100.0):
# Factor de escala basado en altura
escala = altura_objetivo / alto
- # Normalizar puntos:
+ # Normalizar cada polyline:
# 1. Trasladar a origen (restar min_x, min_y)
# 2. Aplicar escala
- puntos_norm = []
- for x, y in puntos:
- x_norm = (x - min_x) * escala
- y_norm = (y - min_y) * escala
- puntos_norm.append((x_norm, y_norm))
+ # 3. Cerrar polyline (último punto = primer punto)
+ polylines_norm = []
+ total_puntos = 0
+
+ for polyline in polylines:
+ polyline_norm = []
+ for x, y in polyline:
+ x_norm = (x - min_x) * escala
+ y_norm = (y - min_y) * escala
+ polyline_norm.append((x_norm, y_norm))
+
+ # Cerrar polyline si no está cerrada
+ if polyline_norm and polyline_norm[0] != polyline_norm[-1]:
+ polyline_norm.append(polyline_norm[0])
+
+ polylines_norm.append(polyline_norm)
+ total_puntos += len(polyline_norm)
# Calcular dimensiones finales
ancho_norm = ancho * escala
@@ -162,10 +200,11 @@ def normalizar_letra(nombre, puntos, altura_objetivo=100.0):
return {
'nombre': nombre,
- 'puntos': puntos_norm,
+ 'polylines': polylines_norm,
'centro': centro,
'ancho': ancho_norm,
- 'alto': alto_norm
+ 'alto': alto_norm,
+ 'total_puntos': total_puntos
}
@@ -178,6 +217,7 @@ def generar_shp(letra_norm, output_dir):
scale: 1.0
center: cx, cy
polyline: x1,y1 x2,y2 x3,y3 ...
+ polyline: x1,y1 x2,y2 x3,y3 ... (si hay múltiples formas)
"""
if not letra_norm:
return
@@ -198,13 +238,13 @@ def generar_shp(letra_norm, output_dir):
f.write(f"center: {letra_norm['centro'][0]:.2f}, {letra_norm['centro'][1]:.2f}\n")
f.write(f"\n")
- # Polyline con todos los puntos
- f.write("polyline: ")
- puntos_str = " ".join([f"{x:.2f},{y:.2f}" for x, y in letra_norm['puntos']])
- f.write(puntos_str)
- f.write("\n")
+ # Generar una línea polyline por cada forma
+ for polyline in letra_norm['polylines']:
+ puntos_str = " ".join([f"{x:.2f},{y:.2f}" for x, y in polyline])
+ f.write(f"polyline: {puntos_str}\n")
- print(f" ✓ {nombre_archivo:20} ({len(letra_norm['puntos']):3} puntos, "
+ print(f" ✓ {nombre_archivo:20} ({len(letra_norm['polylines'])} formas, "
+ f"{letra_norm['total_puntos']:3} puntos, "
f"{letra_norm['ancho']:6.2f} x {letra_norm['alto']:6.2f} px)")
@@ -232,14 +272,18 @@ def parse_svg(filepath):
print(f"[INFO] Transform matrix: {transform_matrix}")
- # Extraer paths y rects
- paths = group.findall('svg:path', ns)
- rects = group.findall('svg:rect', ns)
+ # Extraer paths y rects (buscar recursivamente en todos los descendientes)
+ paths = group.findall('.//svg:path', ns)
+ rects = group.findall('.//svg:rect', ns)
print(f"[INFO] Encontrados {len(paths)} paths y {len(rects)} rects")
- # Nombres de las letras para paths (sin I que es un rect)
- nombres_paths = ['J', 'A', 'L', 'G', 'A', 'M', 'E', 'S']
+ # Nombres de las letras para "ORNI ATTACK!"
+ # Grupo 1 (top): O, R, N, I (3 paths + 1 rect)
+ # Grupo 2 (bottom): A, T, T, A, C, K, ! (6 paths + 1 path para !)
+ # Total: 9 paths + 1 rect
+ # Asumiendo orden de aparición en SVG:
+ nombres_paths = ['O', 'R', 'N', 'A', 'T', 'T', 'A', 'C', 'K', 'EXCLAMACION']
letras = []
@@ -252,15 +296,18 @@ def parse_svg(filepath):
if not d_attr:
continue
- # Parsear puntos del path
- puntos = parse_svg_path(d_attr)
+ # Parsear polylines del path (ahora retorna lista de polylines)
+ polylines = parse_svg_path(d_attr)
- # Aplicar transformación
- puntos = [apply_transform(p, transform_matrix) for p in puntos]
+ # Aplicar transformación a cada polyline
+ polylines_transformed = []
+ for polyline in polylines:
+ polyline_transformed = [apply_transform(p, transform_matrix) for p in polyline]
+ polylines_transformed.append(polyline_transformed)
letras.append({
'nombre': nombres_paths[i],
- 'puntos': puntos
+ 'polylines': polylines_transformed
})
# Procesar rects (la letra I es un rect)
@@ -268,11 +315,12 @@ def parse_svg(filepath):
puntos = rect_to_points(rect)
# Aplicar transformación
- puntos = [apply_transform(p, transform_matrix) for p in puntos]
+ puntos_transformed = [apply_transform(p, transform_matrix) for p in puntos]
+ # Rect es una sola polyline
letras.append({
'nombre': 'I',
- 'puntos': puntos
+ 'polylines': [puntos_transformed]
})
return letras
@@ -326,7 +374,7 @@ def main():
for nombre in sorted(letras_unicas.keys()):
letra = letras_unicas[nombre]
- letra_norm = normalizar_letra(nombre, letra['puntos'], altura_objetivo=100.0)
+ letra_norm = normalizar_letra(nombre, letra['polylines'], altura_objetivo=100.0)
if letra_norm:
generar_shp(letra_norm, output_dir)