afegit titol al TITOL

This commit is contained in:
2025-12-03 17:40:27 +01:00
parent 622ccd22bc
commit 3b0354da54
27 changed files with 520 additions and 200 deletions

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 7875 4016" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.792849,0,0,0.792849,84.327,350.707)">
<path d="M896,1693L896,1531.23L1219.53,1531.23L1219.53,560.632L1543.07,560.632L1543.07,1531.23L1381.3,1531.23L1381.3,1693L896,1693Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M2028.37,1369.47L2028.37,1693L1704.83,1693L1704.83,722.399L1866.6,722.399L1866.6,560.632L2351.9,560.632L2351.9,722.399L2513.67,722.399L2513.67,1693L2190.14,1693L2190.14,1369.47L2028.37,1369.47ZM2028.37,722.399L2028.37,1207.7L2190.14,1207.7L2190.14,722.399L2028.37,722.399Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<rect x="2675.44" y="560.632" width="323.534" height="1132.37" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M3160.74,560.632L3484.27,560.632L3484.27,1531.23L3807.8,1531.23L3807.8,1693L3160.74,1693L3160.74,560.632Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M4131.34,560.632L4616.64,560.632L4616.64,722.399L4293.1,722.399L4293.1,1531.23L4454.87,1531.23L4454.87,1045.93L4778.4,1045.93L4778.4,1693L4131.34,1693L4131.34,1531.23L3969.57,1531.23L3969.57,722.399L4131.34,722.399L4131.34,560.632Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M5263.71,1369.47L5263.71,1693L4940.17,1693L4940.17,722.399L5101.94,722.399L5101.94,560.632L5587.24,560.632L5587.24,722.399L5749.01,722.399L5749.01,1693L5425.47,1693L5425.47,1369.47L5263.71,1369.47ZM5263.71,722.399L5263.71,1207.7L5425.47,1207.7L5425.47,722.399L5263.71,722.399Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M6719.61,1207.7L6557.84,1207.7L6557.84,1369.47L6396.07,1369.47L6396.07,1207.7L6234.31,1207.7L6234.31,1693L5910.77,1693L5910.77,560.632L6072.54,560.632L6072.54,722.399L6234.31,722.399L6234.31,884.166L6396.07,884.166L6396.07,1045.93L6557.84,1045.93L6557.84,884.166L6719.61,884.166L6719.61,722.399L6881.37,722.399L6881.37,560.632L7043.14,560.632L7043.14,1693L6719.61,1693L6719.61,1207.7Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M7851.98,884.166L7851.98,1045.93L7528.44,1045.93L7528.44,1531.23L8013.74,1531.23L8013.74,1693L7204.91,1693L7204.91,560.632L8013.74,560.632L8013.74,722.399L7528.44,722.399L7528.44,884.166L7851.98,884.166Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M8175.51,1531.23L8499.04,1531.23L8499.04,1207.7L8337.28,1207.7L8337.28,1045.93L8175.51,1045.93L8175.51,722.399L8337.28,722.399L8337.28,560.632L8822.58,560.632L8822.58,722.399L8499.04,722.399L8499.04,1045.93L8660.81,1045.93L8660.81,1207.7L8822.58,1207.7L8822.58,1531.23L8660.81,1531.23L8660.81,1693L8175.51,1693L8175.51,1531.23Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -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

View File

@@ -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<Shape>("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

View File

@@ -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<Estrella> estrelles_;
std::vector<CapaConfig> capes_; // Configuració de les 3 capes
std::shared_ptr<Shape> shape_estrella_;
SDL_Renderer* renderer_;
// Dades
std::vector<Estrella> estrelles_;
std::vector<CapaConfig> capes_; // Configuració de les 3 capes
std::shared_ptr<Shape> 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

View File

@@ -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<float>(px1), static_cast<float>(py1),
static_cast<float>(px2), static_cast<float>(py2));
SDL_RenderLine(renderer, static_cast<float>(px1), static_cast<float>(py1), static_cast<float>(px2), static_cast<float>(py2));
}
// Algorisme de Bresenham original (conservat per a futura detecció de

View File

@@ -7,13 +7,13 @@
#include <cstdlib>
#include <iostream>
#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"

View File

@@ -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 {

View File

@@ -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<float>(std::rand()) / RAND_MAX < Defaults::Enemies::Pentagon::CANVI_ANGLE_PROB) {
float rand_angle = (static_cast<float>(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<float>(std::rand()) / RAND_MAX < Defaults::Enemies::Pentagon::CANVI_ANGLE_PROB) {
float rand_angle = (static_cast<float>(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<float>(std::rand()) / RAND_MAX < Defaults::Enemies::Molinillo::CANVI_ANGLE_PROB) {
float rand_angle = (static_cast<float>(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<float>(std::rand()) / RAND_MAX < Defaults::Enemies::Molinillo::CANVI_ANGLE_PROB) {
float rand_angle = (static_cast<float>(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<float>(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<float>(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<float>(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<float>(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<float>(std::rand()) / RAND_MAX) * dur_range;
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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
);

View File

@@ -10,14 +10,14 @@
#include <array>
#include <cstdint>
#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_;

View File

@@ -9,11 +9,11 @@
#include <memory>
#include <vector>
#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:

View File

@@ -3,11 +3,14 @@
#include "escena_titol.hpp"
#include <cfloat>
#include <iostream>
#include <string>
#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<float>(Defaults::Game::WIDTH),
static_cast<float>(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<std::string> 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<std::string> 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

View File

@@ -7,11 +7,14 @@
#include <SDL3/SDL.h>
#include <memory>
#include <vector>
#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<Graphics::Shape> 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<Graphics::Starfield> 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<LetraLogo> lletres_orni_; // Lletres de "ORNI" (línia 1)
std::vector<LetraLogo> 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
};

View File

@@ -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)