treballant en el LOGO
This commit is contained in:
BIN
Asteroids.ico
BIN
Asteroids.ico
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -35,6 +35,7 @@ configure_file(${CMAKE_SOURCE_DIR}/source/project.h.in ${CMAKE_BINARY_DIR}/proje
|
||||
set(APP_SOURCES
|
||||
source/main.cpp
|
||||
source/core/system/director.cpp
|
||||
source/core/system/global_events.cpp
|
||||
source/core/rendering/sdl_manager.cpp
|
||||
source/core/rendering/line_renderer.cpp
|
||||
source/core/rendering/color_oscillator.cpp
|
||||
|
||||
1
Makefile
1
Makefile
@@ -35,6 +35,7 @@ endif
|
||||
APP_SOURCES := \
|
||||
source/main.cpp \
|
||||
source/core/system/director.cpp \
|
||||
source/core/system/global_events.cpp \
|
||||
source/core/rendering/sdl_manager.cpp \
|
||||
source/core/rendering/line_renderer.cpp \
|
||||
source/core/rendering/color_oscillator.cpp \
|
||||
|
||||
9
data/shapes/letra_a.shp
Normal file
9
data/shapes/letra_a.shp
Normal file
@@ -0,0 +1,9 @@
|
||||
# letra_a.shp
|
||||
# Generado automáticamente desde jailgames.svg
|
||||
# Dimensiones: 71.43 x 100.00 px
|
||||
|
||||
name: letra_a
|
||||
scale: 1.0
|
||||
center: 35.71, 50.00
|
||||
|
||||
polyline: 28.57,71.43 28.57,100.00 0.00,100.00 0.00,14.29 14.29,14.29 14.29,0.00 57.14,0.00 57.14,14.29 71.43,14.29 71.43,100.00 42.86,100.00 42.86,71.43 28.57,14.29 28.57,57.14 42.86,57.14 42.86,14.29
|
||||
9
data/shapes/letra_e.shp
Normal file
9
data/shapes/letra_e.shp
Normal file
@@ -0,0 +1,9 @@
|
||||
# letra_e.shp
|
||||
# Generado automáticamente desde jailgames.svg
|
||||
# Dimensiones: 71.43 x 100.00 px
|
||||
|
||||
name: letra_e
|
||||
scale: 1.0
|
||||
center: 35.71, 50.00
|
||||
|
||||
polyline: 57.14,28.57 57.14,42.86 28.57,42.86 28.57,85.71 71.43,85.71 71.43,100.00 0.00,100.00 0.00,0.00 71.43,0.00 71.43,14.29 28.57,14.29 28.57,28.57
|
||||
9
data/shapes/letra_g.shp
Normal file
9
data/shapes/letra_g.shp
Normal file
@@ -0,0 +1,9 @@
|
||||
# letra_g.shp
|
||||
# Generado automáticamente desde jailgames.svg
|
||||
# Dimensiones: 71.43 x 100.00 px
|
||||
|
||||
name: letra_g
|
||||
scale: 1.0
|
||||
center: 35.71, 50.00
|
||||
|
||||
polyline: 14.29,0.00 57.14,0.00 57.14,14.29 28.57,14.29 28.57,85.71 42.86,85.71 42.86,42.86 71.43,42.86 71.43,100.00 14.29,100.00 14.29,85.71 0.00,85.71 0.00,14.29 14.29,14.29
|
||||
9
data/shapes/letra_i.shp
Normal file
9
data/shapes/letra_i.shp
Normal file
@@ -0,0 +1,9 @@
|
||||
# letra_i.shp
|
||||
# Generado automáticamente desde jailgames.svg
|
||||
# Dimensiones: 28.57 x 100.00 px
|
||||
|
||||
name: letra_i
|
||||
scale: 1.0
|
||||
center: 14.29, 50.00
|
||||
|
||||
polyline: 0.00,0.00 28.57,0.00 28.57,100.00 0.00,100.00 0.00,0.00
|
||||
9
data/shapes/letra_j.shp
Normal file
9
data/shapes/letra_j.shp
Normal file
@@ -0,0 +1,9 @@
|
||||
# letra_j.shp
|
||||
# Generado automáticamente desde jailgames.svg
|
||||
# Dimensiones: 57.14 x 100.00 px
|
||||
|
||||
name: letra_j
|
||||
scale: 1.0
|
||||
center: 28.57, 50.00
|
||||
|
||||
polyline: 0.00,100.00 0.00,85.71 28.57,85.71 28.57,0.00 57.14,0.00 57.14,85.71 42.86,85.71 42.86,100.00
|
||||
9
data/shapes/letra_l.shp
Normal file
9
data/shapes/letra_l.shp
Normal file
@@ -0,0 +1,9 @@
|
||||
# letra_l.shp
|
||||
# Generado automáticamente desde jailgames.svg
|
||||
# Dimensiones: 57.14 x 100.00 px
|
||||
|
||||
name: letra_l
|
||||
scale: 1.0
|
||||
center: 28.57, 50.00
|
||||
|
||||
polyline: 0.00,0.00 28.57,0.00 28.57,85.71 57.14,85.71 57.14,100.00 0.00,100.00
|
||||
9
data/shapes/letra_m.shp
Normal file
9
data/shapes/letra_m.shp
Normal file
@@ -0,0 +1,9 @@
|
||||
# letra_m.shp
|
||||
# Generado automáticamente desde jailgames.svg
|
||||
# Dimensiones: 100.00 x 100.00 px
|
||||
|
||||
name: letra_m
|
||||
scale: 1.0
|
||||
center: 50.00, 50.00
|
||||
|
||||
polyline: 71.43,57.14 57.14,57.14 57.14,71.43 42.86,71.43 42.86,57.14 28.57,57.14 28.57,100.00 0.00,100.00 0.00,0.00 14.29,0.00 14.29,14.29 28.57,14.29 28.57,28.57 42.86,28.57 42.86,42.86 57.14,42.86 57.14,28.57 71.43,28.57 71.43,14.29 85.71,14.29 85.71,0.00 100.00,0.00 100.00,100.00 71.43,100.00
|
||||
9
data/shapes/letra_s.shp
Normal file
9
data/shapes/letra_s.shp
Normal file
@@ -0,0 +1,9 @@
|
||||
# letra_s.shp
|
||||
# Generado automáticamente desde jailgames.svg
|
||||
# Dimensiones: 57.14 x 100.00 px
|
||||
|
||||
name: letra_s
|
||||
scale: 1.0
|
||||
center: 28.57, 50.00
|
||||
|
||||
polyline: 0.00,85.71 28.57,85.71 28.57,57.14 14.29,57.14 14.29,42.86 0.00,42.86 0.00,14.29 14.29,14.29 14.29,0.00 57.14,0.00 57.14,14.29 28.57,14.29 28.57,42.86 42.86,42.86 42.86,57.14 57.14,57.14 57.14,85.71 42.86,85.71 42.86,100.00 0.00,100.00
|
||||
15
jailgames.svg
Normal file
15
jailgames.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "escena_joc.hpp"
|
||||
#include "../../core/system/gestor_escenes.hpp"
|
||||
#include "../../core/system/global_events.hpp"
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
@@ -49,29 +50,13 @@ void EscenaJoc::executar() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tecles globals de finestra (F1/F2/F3)
|
||||
if (event.type == SDL_EVENT_KEY_DOWN) {
|
||||
switch (event.key.key) {
|
||||
case SDLK_F1:
|
||||
sdl_.decreaseWindowSize();
|
||||
// Events globals (F1/F2/F3/ESC/QUIT)
|
||||
if (GlobalEvents::handle(event, sdl_)) {
|
||||
continue;
|
||||
case SDLK_F2:
|
||||
sdl_.increaseWindowSize();
|
||||
continue;
|
||||
case SDLK_F3:
|
||||
sdl_.toggleFullscreen();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Processament normal del joc
|
||||
// Processament específic del joc (SPACE per disparar)
|
||||
processar_input(event);
|
||||
|
||||
// Detectar tancament de finestra o ESC
|
||||
if (event.type == SDL_EVENT_QUIT ||
|
||||
(event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE)) {
|
||||
GestorEscenes::actual = GestorEscenes::Escena::EIXIR;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualitzar física del joc amb delta_time real
|
||||
|
||||
@@ -3,11 +3,17 @@
|
||||
|
||||
#include "escena_logo.hpp"
|
||||
#include "../../core/system/gestor_escenes.hpp"
|
||||
#include "../../core/system/global_events.hpp"
|
||||
#include "../../core/graphics/shape_loader.hpp"
|
||||
#include "../../core/rendering/shape_renderer.hpp"
|
||||
#include <iostream>
|
||||
#include <cfloat>
|
||||
#include <algorithm>
|
||||
|
||||
EscenaLogo::EscenaLogo(SDLManager& sdl)
|
||||
: sdl_(sdl), temps_acumulat_(0.0f) {
|
||||
std::cout << "Escena Logo: Inicialitzant...\n";
|
||||
inicialitzar_lletres();
|
||||
}
|
||||
|
||||
void EscenaLogo::executar() {
|
||||
@@ -32,34 +38,21 @@ void EscenaLogo::executar() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tecles globals de finestra (F1/F2/F3)
|
||||
if (event.type == SDL_EVENT_KEY_DOWN) {
|
||||
switch (event.key.key) {
|
||||
case SDLK_F1:
|
||||
sdl_.decreaseWindowSize();
|
||||
// Events globals (F1/F2/F3/ESC/QUIT)
|
||||
if (GlobalEvents::handle(event, sdl_)) {
|
||||
continue;
|
||||
case SDLK_F2:
|
||||
sdl_.increaseWindowSize();
|
||||
continue;
|
||||
case SDLK_F3:
|
||||
sdl_.toggleFullscreen();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Processar events de l'escena
|
||||
// Processar events de l'escena (qualsevol tecla/clic salta al joc)
|
||||
processar_events(event);
|
||||
|
||||
// ESC o tancar finestra = eixir
|
||||
if (event.type == SDL_EVENT_QUIT ||
|
||||
(event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE)) {
|
||||
GestorEscenes::actual = GestorEscenes::Escena::EIXIR;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualitzar lògica
|
||||
actualitzar(delta_time);
|
||||
|
||||
// Actualitzar colors oscil·lats (efecte verd global)
|
||||
sdl_.updateColors(delta_time);
|
||||
|
||||
// Dibuixar
|
||||
dibuixar();
|
||||
}
|
||||
@@ -67,18 +60,117 @@ void EscenaLogo::executar() {
|
||||
std::cout << "Escena Logo: Finalitzant...\n";
|
||||
}
|
||||
|
||||
void EscenaLogo::inicialitzar_lletres() {
|
||||
using namespace Graphics;
|
||||
|
||||
// Llista de fitxers .shp (A repetida per a les dues A's)
|
||||
std::vector<std::string> fitxers = {
|
||||
"letra_j.shp", "letra_a.shp", "letra_i.shp", "letra_l.shp",
|
||||
"letra_g.shp", "letra_a.shp", "letra_m.shp", "letra_e.shp", "letra_s.shp"
|
||||
};
|
||||
|
||||
// Pas 1: Carregar totes les formes i calcular amplades
|
||||
float ancho_total = 0.0f;
|
||||
|
||||
for (const auto& fitxer : fitxers) {
|
||||
auto forma = ShapeLoader::load(fitxer);
|
||||
if (!forma || !forma->es_valida()) {
|
||||
std::cerr << "[EscenaLogo] Error carregant " << fitxer << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calcular bounding box de la forma (trobar ancho)
|
||||
float min_x = FLT_MAX;
|
||||
float max_x = -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);
|
||||
}
|
||||
}
|
||||
|
||||
float ancho = max_x - min_x;
|
||||
|
||||
lletres_.push_back({
|
||||
forma,
|
||||
{0.0f, 0.0f}, // Posició es calcularà després
|
||||
ancho
|
||||
});
|
||||
|
||||
ancho_total += ancho;
|
||||
}
|
||||
|
||||
// Pas 2: Afegir espaiat entre lletres
|
||||
ancho_total += ESPAI_ENTRE_LLETRES * (lletres_.size() - 1);
|
||||
|
||||
// Pas 3: Calcular posició inicial (centrat horitzontal)
|
||||
constexpr float PANTALLA_ANCHO = 640.0f;
|
||||
constexpr float PANTALLA_ALTO = 480.0f;
|
||||
|
||||
float x_inicial = (PANTALLA_ANCHO - ancho_total) / 2.0f;
|
||||
float y_centre = PANTALLA_ALTO / 2.0f;
|
||||
|
||||
// Pas 4: Assignar posicions a cada lletra
|
||||
float x_actual = x_inicial;
|
||||
|
||||
for (auto& lletra : lletres_) {
|
||||
// Centre de la lletra en pantalla
|
||||
lletra.posicio.x = x_actual + lletra.ancho / 2.0f;
|
||||
lletra.posicio.y = y_centre;
|
||||
|
||||
// Avançar per a següent lletra
|
||||
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
|
||||
}
|
||||
|
||||
std::cout << "[EscenaLogo] " << lletres_.size()
|
||||
<< " lletres carregades, ancho total: " << ancho_total << " px\n";
|
||||
}
|
||||
|
||||
float EscenaLogo::calcular_escala_zoom(float temps) const {
|
||||
if (temps >= DURACIO_ZOOM) {
|
||||
return ESCALA_FINAL; // Animació acabada
|
||||
}
|
||||
|
||||
// Progrés normalitzat (0.0 a 1.0)
|
||||
float t = temps / DURACIO_ZOOM;
|
||||
|
||||
// Ease-out quadràtic: y = 1 - (1-t)^2
|
||||
// Comença ràpid, acaba suau
|
||||
float factor = 1.0f - (1.0f - t) * (1.0f - t);
|
||||
|
||||
// Interpolar entre escala inicial i final
|
||||
return ESCALA_INICIAL + (ESCALA_FINAL - ESCALA_INICIAL) * factor;
|
||||
}
|
||||
|
||||
void EscenaLogo::actualitzar(float delta_time) {
|
||||
temps_acumulat_ += delta_time;
|
||||
|
||||
// Després de 2 segons, saltar al joc
|
||||
if (temps_acumulat_ >= 2.0f) {
|
||||
// Després de DURACIO_TOTAL segons, saltar al joc
|
||||
if (temps_acumulat_ >= DURACIO_TOTAL) {
|
||||
GestorEscenes::actual = GestorEscenes::Escena::JOC;
|
||||
}
|
||||
}
|
||||
|
||||
void EscenaLogo::dibuixar() {
|
||||
// Pantalla negra
|
||||
// Fons negre
|
||||
sdl_.neteja(0, 0, 0);
|
||||
|
||||
// Calcular escala actual del zoom
|
||||
float escala = calcular_escala_zoom(temps_acumulat_);
|
||||
|
||||
// Dibuixar cada lletra amb l'escala animada
|
||||
for (const auto& lletra : lletres_) {
|
||||
Rendering::render_shape(
|
||||
sdl_.obte_renderer(),
|
||||
lletra.forma,
|
||||
lletra.posicio, // Posició en pantalla
|
||||
0.0f, // Sense rotació
|
||||
escala, // Escala animada
|
||||
true // Dibuixar
|
||||
);
|
||||
}
|
||||
|
||||
sdl_.presenta();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// escena_logo.hpp - Pantalla d'inici del joc
|
||||
// Mostra pantalla negra durant 2 segons i salta al joc
|
||||
// Mostra logo JAILGAMES animat amb zoom i salta al joc
|
||||
// © 2025 Port a C++20
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../core/rendering/sdl_manager.hpp"
|
||||
#include "../../core/graphics/shape.hpp"
|
||||
#include "../../core/types.hpp"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
class EscenaLogo {
|
||||
public:
|
||||
@@ -16,6 +20,25 @@ private:
|
||||
SDLManager& sdl_;
|
||||
float temps_acumulat_;
|
||||
|
||||
// Estructura per a cada lletra del logo
|
||||
struct LetraLogo {
|
||||
std::shared_ptr<Graphics::Shape> forma;
|
||||
Punt posicio; // Posició final en pantalla
|
||||
float ancho; // Ancho per a càlcul de centrat
|
||||
};
|
||||
|
||||
std::vector<LetraLogo> lletres_; // 9 lletres: J-A-I-L-G-A-M-E-S
|
||||
|
||||
// Constants d'animació
|
||||
static constexpr float DURACIO_ZOOM = 1.5f; // Duració del zoom (segons)
|
||||
static constexpr float DURACIO_TOTAL = 3.0f; // Duració total abans d'anar al joc
|
||||
static constexpr float ESCALA_INICIAL = 0.1f; // Escala inicial (10%)
|
||||
static constexpr float ESCALA_FINAL = 1.0f; // Escala final (100%)
|
||||
static constexpr float ESPAI_ENTRE_LLETRES = 10.0f; // Espaiat entre lletres
|
||||
|
||||
// Mètodes privats
|
||||
void inicialitzar_lletres();
|
||||
float calcular_escala_zoom(float temps) const;
|
||||
void actualitzar(float delta_time);
|
||||
void dibuixar();
|
||||
void processar_events(const SDL_Event& event);
|
||||
|
||||
340
tools/svg_to_shp.py
Executable file
340
tools/svg_to_shp.py
Executable file
@@ -0,0 +1,340 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
svg_to_shp.py - Conversor de jailgames.svg a archivos .shp individuales
|
||||
Extrae letras del SVG y las convierte al formato .shp del juego Orni Attack
|
||||
|
||||
Uso: python3 svg_to_shp.py <input.svg> <output_dir>
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def parse_transform_matrix(transform_str):
|
||||
"""
|
||||
Parsea string "matrix(a,b,c,d,e,f)" → tupla (a,b,c,d,e,f)
|
||||
"""
|
||||
match = re.search(r'matrix\(([\d\.\-,\s]+)\)', transform_str)
|
||||
if not match:
|
||||
return (1, 0, 0, 1, 0, 0) # Identidad por defecto
|
||||
|
||||
values = re.split(r'[,\s]+', match.group(1).strip())
|
||||
values = [float(v) for v in values if v]
|
||||
|
||||
if len(values) == 6:
|
||||
return tuple(values)
|
||||
return (1, 0, 0, 1, 0, 0)
|
||||
|
||||
|
||||
def apply_transform(punto, matrix):
|
||||
"""
|
||||
Aplica transformación matricial 2D a un punto (x, y)
|
||||
matrix = (a, b, c, d, e, f)
|
||||
x' = a*x + c*y + e
|
||||
y' = b*x + d*y + f
|
||||
"""
|
||||
x, y = punto
|
||||
a, b, c, d, e, f = matrix
|
||||
x_new = a * x + c * y + e
|
||||
y_new = b * x + d * y + f
|
||||
return (x_new, y_new)
|
||||
|
||||
|
||||
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), ...]
|
||||
"""
|
||||
# Reemplazar comas por espacios para facilitar parsing
|
||||
d_attr = d_attr.replace(',', ' ')
|
||||
|
||||
# Split por comandos M y L
|
||||
# Añadir marcador antes de cada comando
|
||||
d_attr = re.sub(r'([ML])', r'|\1', d_attr)
|
||||
commands = [c.strip() for c in d_attr.split('|') if c.strip()]
|
||||
|
||||
points = []
|
||||
for cmd in commands:
|
||||
if not cmd:
|
||||
continue
|
||||
|
||||
# Extraer letra de comando y coordenadas
|
||||
cmd_letter = cmd[0]
|
||||
coords_str = cmd[1:].strip()
|
||||
|
||||
if not coords_str:
|
||||
continue
|
||||
|
||||
# Parsear pares de coordenadas
|
||||
coords = coords_str.split()
|
||||
|
||||
# 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))
|
||||
i += 2
|
||||
except (ValueError, IndexError):
|
||||
i += 1
|
||||
|
||||
return points
|
||||
|
||||
|
||||
def rect_to_points(rect_elem):
|
||||
"""
|
||||
Convierte elemento <rect> a lista de puntos (rectángulo cerrado)
|
||||
"""
|
||||
x = float(rect_elem.get('x', 0))
|
||||
y = float(rect_elem.get('y', 0))
|
||||
width = float(rect_elem.get('width', 0))
|
||||
height = float(rect_elem.get('height', 0))
|
||||
|
||||
# Rectángulo cerrado: 5 puntos (último = primero)
|
||||
return [
|
||||
(x, y),
|
||||
(x + width, y),
|
||||
(x + width, y + height),
|
||||
(x, y + height),
|
||||
(x, y) # Cerrar
|
||||
]
|
||||
|
||||
|
||||
def calc_bounding_box(puntos):
|
||||
"""
|
||||
Calcula bounding box de una lista de puntos
|
||||
Retorna: (min_x, max_x, min_y, max_y, ancho, alto)
|
||||
"""
|
||||
if not puntos:
|
||||
return (0, 0, 0, 0, 0, 0)
|
||||
|
||||
xs = [p[0] for p in puntos]
|
||||
ys = [p[1] for p in puntos]
|
||||
|
||||
min_x = min(xs)
|
||||
max_x = max(xs)
|
||||
min_y = min(ys)
|
||||
max_y = max(ys)
|
||||
|
||||
ancho = max_x - min_x
|
||||
alto = max_y - min_y
|
||||
|
||||
return (min_x, max_x, min_y, max_y, ancho, alto)
|
||||
|
||||
|
||||
def normalizar_letra(nombre, puntos, 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
|
||||
"""
|
||||
if not puntos:
|
||||
return None
|
||||
|
||||
min_x, max_x, min_y, max_y, ancho, alto = calc_bounding_box(puntos)
|
||||
|
||||
if alto == 0:
|
||||
print(f" [WARN] Letra {nombre}: altura cero")
|
||||
return None
|
||||
|
||||
# Factor de escala basado en altura
|
||||
escala = altura_objetivo / alto
|
||||
|
||||
# Normalizar puntos:
|
||||
# 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))
|
||||
|
||||
# Calcular dimensiones finales
|
||||
ancho_norm = ancho * escala
|
||||
alto_norm = alto * escala
|
||||
|
||||
# Centro de la letra
|
||||
centro = (ancho_norm / 2.0, alto_norm / 2.0)
|
||||
|
||||
return {
|
||||
'nombre': nombre,
|
||||
'puntos': puntos_norm,
|
||||
'centro': centro,
|
||||
'ancho': ancho_norm,
|
||||
'alto': alto_norm
|
||||
}
|
||||
|
||||
|
||||
def generar_shp(letra_norm, output_dir):
|
||||
"""
|
||||
Genera archivo .shp con formato del juego Orni Attack
|
||||
|
||||
Formato:
|
||||
name: letra_x
|
||||
scale: 1.0
|
||||
center: cx, cy
|
||||
polyline: x1,y1 x2,y2 x3,y3 ...
|
||||
"""
|
||||
if not letra_norm:
|
||||
return
|
||||
|
||||
nombre_archivo = f"letra_{letra_norm['nombre'].lower()}.shp"
|
||||
filepath = os.path.join(output_dir, nombre_archivo)
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
# Header con comentarios
|
||||
f.write(f"# {nombre_archivo}\n")
|
||||
f.write(f"# Generado automáticamente desde jailgames.svg\n")
|
||||
f.write(f"# Dimensiones: {letra_norm['ancho']:.2f} x {letra_norm['alto']:.2f} px\n")
|
||||
f.write(f"\n")
|
||||
|
||||
# Metadatos
|
||||
f.write(f"name: letra_{letra_norm['nombre'].lower()}\n")
|
||||
f.write(f"scale: 1.0\n")
|
||||
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")
|
||||
|
||||
print(f" ✓ {nombre_archivo:20} ({len(letra_norm['puntos']):3} puntos, "
|
||||
f"{letra_norm['ancho']:6.2f} x {letra_norm['alto']:6.2f} px)")
|
||||
|
||||
|
||||
def parse_svg(filepath):
|
||||
"""
|
||||
Parsea jailgames.svg y extrae las 9 letras con sus puntos transformados
|
||||
|
||||
Retorna: lista de dicts con {nombre, puntos}
|
||||
"""
|
||||
tree = ET.parse(filepath)
|
||||
root = tree.getroot()
|
||||
|
||||
# Namespace SVG
|
||||
ns = {'svg': 'http://www.w3.org/2000/svg'}
|
||||
|
||||
# Buscar grupo con transform
|
||||
groups = root.findall('.//svg:g[@transform]', ns)
|
||||
if not groups:
|
||||
print("[ERROR] No se encontró grupo con transform")
|
||||
return []
|
||||
|
||||
group = groups[0]
|
||||
transform_str = group.get('transform', '')
|
||||
transform_matrix = parse_transform_matrix(transform_str)
|
||||
|
||||
print(f"[INFO] Transform matrix: {transform_matrix}")
|
||||
|
||||
# Extraer paths y rects
|
||||
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']
|
||||
|
||||
letras = []
|
||||
|
||||
# Procesar paths
|
||||
for i, path in enumerate(paths):
|
||||
if i >= len(nombres_paths):
|
||||
break
|
||||
|
||||
d_attr = path.get('d')
|
||||
if not d_attr:
|
||||
continue
|
||||
|
||||
# Parsear puntos del path
|
||||
puntos = parse_svg_path(d_attr)
|
||||
|
||||
# Aplicar transformación
|
||||
puntos = [apply_transform(p, transform_matrix) for p in puntos]
|
||||
|
||||
letras.append({
|
||||
'nombre': nombres_paths[i],
|
||||
'puntos': puntos
|
||||
})
|
||||
|
||||
# Procesar rects (la letra I es un rect)
|
||||
for rect in rects:
|
||||
puntos = rect_to_points(rect)
|
||||
|
||||
# Aplicar transformación
|
||||
puntos = [apply_transform(p, transform_matrix) for p in puntos]
|
||||
|
||||
letras.append({
|
||||
'nombre': 'I',
|
||||
'puntos': puntos
|
||||
})
|
||||
|
||||
return letras
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print("Uso: python3 svg_to_shp.py <input.svg> <output_dir>")
|
||||
print("Ejemplo: python3 svg_to_shp.py jailgames.svg data/shapes/")
|
||||
sys.exit(1)
|
||||
|
||||
svg_path = sys.argv[1]
|
||||
output_dir = sys.argv[2]
|
||||
|
||||
# Verificar que el SVG existe
|
||||
if not os.path.exists(svg_path):
|
||||
print(f"[ERROR] No se encuentra el archivo: {svg_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Crear directorio de salida si no existe
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print(f" SVG → .shp Converter para Orni Attack")
|
||||
print(f"{'='*70}\n")
|
||||
print(f"Input: {svg_path}")
|
||||
print(f"Output: {output_dir}/\n")
|
||||
|
||||
# Parsear SVG
|
||||
print("[1/3] Parseando SVG...")
|
||||
letras = parse_svg(svg_path)
|
||||
|
||||
if not letras:
|
||||
print("[ERROR] No se pudieron extraer letras del SVG")
|
||||
sys.exit(1)
|
||||
|
||||
print(f" ✓ Extraídas {len(letras)} letras\n")
|
||||
|
||||
# Filtrar duplicados (solo una 'A')
|
||||
print("[2/3] Filtrando duplicados...")
|
||||
letras_unicas = {}
|
||||
for letra in letras:
|
||||
nombre = letra['nombre']
|
||||
if nombre not in letras_unicas:
|
||||
letras_unicas[nombre] = letra
|
||||
|
||||
print(f" ✓ {len(letras_unicas)} letras únicas: {', '.join(sorted(letras_unicas.keys()))}\n")
|
||||
|
||||
# Normalizar y generar .shp
|
||||
print("[3/3] Generando archivos .shp (altura objetivo: 100px)...\n")
|
||||
|
||||
for nombre in sorted(letras_unicas.keys()):
|
||||
letra = letras_unicas[nombre]
|
||||
letra_norm = normalizar_letra(nombre, letra['puntos'], altura_objetivo=100.0)
|
||||
|
||||
if letra_norm:
|
||||
generar_shp(letra_norm, output_dir)
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print(f" ✓ Conversión completada: {len(letras_unicas)} archivos .shp generados")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user