tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — 132 miembros, 79 widgets, la galería vuelve y licencia dual
1//! **Muebles y objetos de casa**: un catálogo de modelitos voxel paramétricos
2//! (mesa, silla, cama, estante, lámpara, alfombra, cofre, estufa, sofá, maceta,
3//! barril, escritorio) que se **estampan** en una escena con [`stamp_mueble`].
4//!
5//! A diferencia de la [`Forma`](crate::Forma) de flora (un solo color), un mueble
6//! combina **varios materiales** por pieza (madera + acero + tela…) y lleva el
7//! **brillo** de cada material en el alpha del voxel, así el acero de un cofre o la
8//! pantalla de una lámpara **glasean** con el mismo ray-march que las gemas. Se
9//! orientan con `facing` (0=+Z, 1=+X, 2=−Z, 3=−X).
10
11use serde::{Deserialize, Serialize};
12
13use crate::worldgen::Material;
14use llimphi_3d::VoxelGrid;
15
16/// Catálogo de muebles y objetos de casa.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub enum Mueble {
19 /// Mesa: tapa de madera sobre cuatro patas.
20 Mesa,
21 /// Silla: asiento, respaldo y patas de madera.
22 Silla,
23 /// Cama: bastidor de madera, colchón claro y almohada.
24 Cama,
25 /// Estante: librero de madera con libros de colores.
26 Estante,
27 /// Lámpara de pie: base y poste de acero, pantalla cálida.
28 Lampara,
29 /// Alfombra: losa plana coloreada.
30 Alfombra,
31 /// Cofre: caja de madera con herrajes de acero.
32 Cofre,
33 /// Estufa: cuerpo de ladrillo con boca de fuego.
34 Estufa,
35 /// Sofá: bloque tapizado con brazos y respaldo.
36 Sofa,
37 /// Maceta: tiesto de cerámica con tierra y una plantita.
38 Maceta,
39 /// Barril: duela de madera con aros de acero.
40 Barril,
41 /// Escritorio: tapa de madera con un bloque de cajones.
42 Escritorio,
43 // --- Plantas de interior ---
44 /// Ficus: árbol de interior en maceta (copa frondosa).
45 Ficus,
46 /// Palma de interior en maceta.
47 Palma,
48 /// Suculenta en maceta baja.
49 Suculenta,
50 /// Bonsái en maceta plana.
51 Bonsai,
52 /// Planta colgante: cesta a altura con enredaderas que caen.
53 Colgante,
54 /// Cactus de interior en maceta.
55 Nopal,
56 // --- Decoración de pared ---
57 /// Cuadro: marco con lienzo coloreado, montado a altura.
58 Cuadro,
59 /// Espejo: marco con vidrio lustroso.
60 Espejo,
61 /// Reloj de pared: esfera clara con agujas.
62 Reloj,
63 /// Repisa flotante: estante con un par de objetos.
64 Repisa,
65 /// Aplique: brazo de acero con una luz cálida.
66 Aplique,
67 /// Tapiz: paño colgante de colores.
68 Tapiz,
69 // --- Elementos de casas y edificios modernos ---
70 /// Escalera: tramo de peldaños de hormigón.
71 Escalera,
72 /// Baranda: barandal de vidrio con montantes de acero.
73 Baranda,
74 /// Ventanal: paño de vidrio de piso a techo (muro cortina).
75 Ventanal,
76 /// Columna: pilar cuadrado de hormigón.
77 Columna,
78 /// Aire acondicionado: split blanco de pared.
79 Aire,
80 /// Panel solar: placa azul inclinada sobre soporte.
81 Solar,
82 /// Televisor: pantalla plana negra sobre soporte.
83 Tele,
84 /// Isla de cocina: mueble bajo con mesada de mármol.
85 Isla,
86 /// Ducha: mampara de vidrio con base y ducha de acero.
87 Ducha,
88 /// Placard: armario empotrado alto.
89 Placard,
90 /// Farol: poste de acero con luz cálida (ilumina).
91 Farol,
92 /// Pileta: espejo de agua con borde de cerámica.
93 Pileta,
94 // --- Elementos de ciudad ---
95 /// Semáforo: poste con luces roja/amarilla/verde.
96 Semaforo,
97 /// Banco de plaza: tablas sobre patas de acero.
98 Banco,
99 /// Fuente: pileta de piedra con chorro de agua.
100 Fuente,
101 /// Buzón azul sobre poste.
102 Buzon,
103 /// Papelera: tacho cilíndrico de acero.
104 Basura,
105 /// Parada de colectivo: marquesina de vidrio con banco.
106 Parada,
107 /// Hidrante: boca de incendio roja.
108 Hidrante,
109 /// Cabina telefónica de vidrio y techo azul.
110 Cabina,
111 /// Cartel: poste con letrero.
112 Cartel,
113 /// Auto: carrocería azul con ventanas de vidrio y ruedas.
114 Auto,
115 /// Estatua de mármol sobre pedestal.
116 Estatua,
117 /// Bolardo: poste bajo de hormigón.
118 Bolardo,
119 // --- Edificios grandes ---
120 /// Rascacielos: torre alta de vidrio y acero.
121 Rascacielos,
122 /// Edificio: bloque de departamentos de ladrillo con ventanas.
123 Edificio,
124 /// Torre: torre delgada de hormigón con antena.
125 Torre,
126 /// Fábrica: nave baja con chimenea de ladrillo.
127 Fabrica,
128 /// Iglesia: nave con campanario y cruz.
129 Iglesia,
130 /// Galpón: nave ancha con techo de zinc.
131 Galpon,
132 /// Silo: cilindro de acero con cúpula.
133 Silo,
134 /// Estacionamiento: losas y columnas en varios niveles.
135 Estacionamiento,
136 /// Farola: alumbrado público alto (ilumina).
137 Farola,
138 /// Contenedor: volquete metálico.
139 Contenedor,
140 /// Grúa: torre grúa con pluma y contrapeso.
141 Grua,
142 /// Puente: tablero con barandas y pilas.
143 Puente,
144 // --- Transporte ---
145 /// Avión: fuselaje con alas y cola.
146 Avion,
147 /// Helicóptero: cabina, cola y rotor.
148 Helicoptero,
149 /// Camioneta (pickup): cabina y caja abierta.
150 Camioneta,
151 /// Camión: cabina y caja de carga.
152 Camion,
153 /// Colectivo (bus): carrocería larga con ventanas.
154 Colectivo,
155 /// Taxi: auto amarillo con cartel.
156 Taxi,
157 /// Tren: locomotora sobre ruedas.
158 Tren,
159 /// Vagón: caja de tren sobre ruedas.
160 Vagon,
161 /// Moto: dos ruedas y manubrio.
162 Moto,
163 /// Bici: cuadro y dos ruedas.
164 Bici,
165 /// Barco: casco con cabina y mástil.
166 Barco,
167 /// Ambulancia: furgón blanco con cruz roja.
168 Ambulancia,
169}
170
171impl Mueble {
172 /// Todos los muebles (para ciclar en un editor).
173 pub const ALL: [Mueble; 72] = [
174 Mueble::Mesa,
175 Mueble::Silla,
176 Mueble::Cama,
177 Mueble::Estante,
178 Mueble::Lampara,
179 Mueble::Alfombra,
180 Mueble::Cofre,
181 Mueble::Estufa,
182 Mueble::Sofa,
183 Mueble::Maceta,
184 Mueble::Barril,
185 Mueble::Escritorio,
186 Mueble::Ficus,
187 Mueble::Palma,
188 Mueble::Suculenta,
189 Mueble::Bonsai,
190 Mueble::Colgante,
191 Mueble::Nopal,
192 Mueble::Cuadro,
193 Mueble::Espejo,
194 Mueble::Reloj,
195 Mueble::Repisa,
196 Mueble::Aplique,
197 Mueble::Tapiz,
198 Mueble::Escalera,
199 Mueble::Baranda,
200 Mueble::Ventanal,
201 Mueble::Columna,
202 Mueble::Aire,
203 Mueble::Solar,
204 Mueble::Tele,
205 Mueble::Isla,
206 Mueble::Ducha,
207 Mueble::Placard,
208 Mueble::Farol,
209 Mueble::Pileta,
210 Mueble::Semaforo,
211 Mueble::Banco,
212 Mueble::Fuente,
213 Mueble::Buzon,
214 Mueble::Basura,
215 Mueble::Parada,
216 Mueble::Hidrante,
217 Mueble::Cabina,
218 Mueble::Cartel,
219 Mueble::Auto,
220 Mueble::Estatua,
221 Mueble::Bolardo,
222 Mueble::Rascacielos,
223 Mueble::Edificio,
224 Mueble::Torre,
225 Mueble::Fabrica,
226 Mueble::Iglesia,
227 Mueble::Galpon,
228 Mueble::Silo,
229 Mueble::Estacionamiento,
230 Mueble::Farola,
231 Mueble::Contenedor,
232 Mueble::Grua,
233 Mueble::Puente,
234 Mueble::Avion,
235 Mueble::Helicoptero,
236 Mueble::Camioneta,
237 Mueble::Camion,
238 Mueble::Colectivo,
239 Mueble::Taxi,
240 Mueble::Tren,
241 Mueble::Vagon,
242 Mueble::Moto,
243 Mueble::Bici,
244 Mueble::Barco,
245 Mueble::Ambulancia,
246 ];
247
248 /// ¿Es **arquitectura**? Las construcciones salen simétricas izquierda-
249 /// derecha por regla ([`stamp_mueble`]); un mueble, un vehículo o una grúa
250 /// no tienen por qué.
251 pub fn es_construccion(self) -> bool {
252 matches!(
253 self,
254 Mueble::Rascacielos
255 | Mueble::Edificio
256 | Mueble::Torre
257 | Mueble::Fabrica
258 | Mueble::Iglesia
259 | Mueble::Galpon
260 | Mueble::Silo
261 | Mueble::Estacionamiento
262 )
263 }
264
265 /// Nombre legible (español).
266 pub fn label(self) -> &'static str {
267 match self {
268 Mueble::Mesa => "mesa",
269 Mueble::Silla => "silla",
270 Mueble::Cama => "cama",
271 Mueble::Estante => "estante",
272 Mueble::Lampara => "lámpara",
273 Mueble::Alfombra => "alfombra",
274 Mueble::Cofre => "cofre",
275 Mueble::Estufa => "estufa",
276 Mueble::Sofa => "sofá",
277 Mueble::Maceta => "maceta",
278 Mueble::Barril => "barril",
279 Mueble::Escritorio => "escritorio",
280 Mueble::Ficus => "ficus",
281 Mueble::Palma => "palma",
282 Mueble::Suculenta => "suculenta",
283 Mueble::Bonsai => "bonsái",
284 Mueble::Colgante => "colgante",
285 Mueble::Nopal => "nopal",
286 Mueble::Cuadro => "cuadro",
287 Mueble::Espejo => "espejo",
288 Mueble::Reloj => "reloj",
289 Mueble::Repisa => "repisa",
290 Mueble::Aplique => "aplique",
291 Mueble::Tapiz => "tapiz",
292 Mueble::Escalera => "escalera",
293 Mueble::Baranda => "baranda",
294 Mueble::Ventanal => "ventanal",
295 Mueble::Columna => "columna",
296 Mueble::Aire => "aire",
297 Mueble::Solar => "panel solar",
298 Mueble::Tele => "televisor",
299 Mueble::Isla => "isla",
300 Mueble::Ducha => "ducha",
301 Mueble::Placard => "placard",
302 Mueble::Farol => "farol",
303 Mueble::Pileta => "pileta",
304 Mueble::Semaforo => "semáforo",
305 Mueble::Banco => "banco",
306 Mueble::Fuente => "fuente",
307 Mueble::Buzon => "buzón",
308 Mueble::Basura => "papelera",
309 Mueble::Parada => "parada",
310 Mueble::Hidrante => "hidrante",
311 Mueble::Cabina => "cabina",
312 Mueble::Cartel => "cartel",
313 Mueble::Auto => "auto",
314 Mueble::Estatua => "estatua",
315 Mueble::Bolardo => "bolardo",
316 Mueble::Rascacielos => "rascacielos",
317 Mueble::Edificio => "edificio",
318 Mueble::Torre => "torre",
319 Mueble::Fabrica => "fábrica",
320 Mueble::Iglesia => "iglesia",
321 Mueble::Galpon => "galpón",
322 Mueble::Silo => "silo",
323 Mueble::Estacionamiento => "estacionamiento",
324 Mueble::Farola => "farola",
325 Mueble::Contenedor => "contenedor",
326 Mueble::Grua => "grúa",
327 Mueble::Puente => "puente",
328 Mueble::Avion => "avión",
329 Mueble::Helicoptero => "helicóptero",
330 Mueble::Camioneta => "camioneta",
331 Mueble::Camion => "camión",
332 Mueble::Colectivo => "colectivo",
333 Mueble::Taxi => "taxi",
334 Mueble::Tren => "tren",
335 Mueble::Vagon => "vagón",
336 Mueble::Moto => "moto",
337 Mueble::Bici => "bici",
338 Mueble::Barco => "barco",
339 Mueble::Ambulancia => "ambulancia",
340 }
341 }
342
343 /// El mueble siguiente (cicla).
344 pub fn next(self) -> Mueble {
345 let i = Mueble::ALL.iter().position(|&m| m == self).unwrap_or(0);
346 Mueble::ALL[(i + 1) % Mueble::ALL.len()]
347 }
348
349 /// Si el mueble **emite luz**, devuelve `(altura sobre la base, color RGB,
350 /// alcance en voxels)` de su fuente puntual. `None` = no ilumina. Lo consume el
351 /// editor de interiores para sembrar `PointLight`s cálidas.
352 pub fn luz(self) -> Option<(f32, [f32; 3], f32)> {
353 match self {
354 Mueble::Lampara => Some((6.5, [1.55, 1.22, 0.72], 15.0)), // pantalla cálida
355 Mueble::Aplique => Some((5.5, [1.5, 1.16, 0.7], 11.0)), // aplique de pared
356 Mueble::Estufa => Some((1.5, [1.6, 0.72, 0.28], 9.0)), // boca de fuego
357 Mueble::Farol => Some((7.5, [1.4, 1.3, 1.0], 16.0)), // farol de poste (luz fría)
358 Mueble::Farola => Some((10.5, [1.5, 1.42, 1.15], 20.0)), // alumbrado público (alto)
359 _ => None,
360 }
361 }
362}
363
364/// Rota un desplazamiento `(dx, dz)` según `facing` (giros de 90°).
365#[inline]
366fn rot(dx: i32, dz: i32, facing: u8) -> (i32, i32) {
367 match facing & 3 {
368 0 => (dx, dz),
369 1 => (-dz, dx),
370 2 => (-dx, -dz),
371 _ => (dz, -dx),
372 }
373}
374
375/// Escribe un voxel del material `m` (con su brillo en el alpha), recortando fuera
376/// del grid.
377fn place(g: &mut VoxelGrid, x: i32, y: i32, z: i32, m: Material) {
378 if x < 0 || y < 0 || z < 0 {
379 return;
380 }
381 let dim = g.dim();
382 if (x as u32) < dim[0] && (y as u32) < dim[1] && (z as u32) < dim[2] {
383 g.set_rgba(x as u32, y as u32, z as u32, m.color(), VoxelGrid::shine_to_alpha(m.brillo()));
384 }
385}
386
387/// Hash barato de la posición de colocación.
388#[inline]
389fn hashxz(x: u32, z: u32) -> u32 {
390 let mut h = x.wrapping_mul(0x9E37_79B9).wrapping_add(z.wrapping_mul(0x85EB_CA77));
391 h ^= h >> 15;
392 h = h.wrapping_mul(0x2C1B_3C6D);
393 h ^ (h >> 13)
394}
395
396/// **Color de carrocería** de un vehículo, elegido de una paleta por la posición de
397/// colocación (determinista → una flota estampada sale VARIADA, del mismo modo que
398/// la flora varía por posición de mundo). No usa la semilla del mundo: los vehículos
399/// siguen siendo props colocables, pero cada uno toma su color de dónde cae.
400fn vehicle_body(x: u32, z: u32) -> Material {
401 const PAL: [Material; 6] = [
402 Material::Azul,
403 Material::Lava,
404 Material::Wheat,
405 Material::Leaf,
406 Material::Ceramica,
407 Material::Grava,
408 ];
409 PAL[(hashxz(x, z) % PAL.len() as u32) as usize]
410}
411
412/// **Estampa un mueble** con la base (centro del piso) en `(x, y, z)`, orientado
413/// según `facing`. Cada pieza combina sus materiales y lleva su brillo.
414pub fn stamp_mueble(g: &mut VoxelGrid, mueble: Mueble, x: u32, y: u32, z: u32, facing: u8) {
415 let (bx, by, bz) = (x as i32, y as i32, z as i32);
416 let body = vehicle_body(x, z); // color de carrocería (vehículos)
417 // Coloca en coordenadas LOCALES (dx a lo ancho, dz al fondo, dy hacia arriba).
418 // Se anota y se escribe al final, rotado por facing alrededor de la base:
419 // así una construcción se puede espejar entera antes de escribirla.
420 let mut piezas: Vec<(i32, i32, i32, Material)> = Vec::new();
421 let mut put = |dx: i32, dy: i32, dz: i32, m: Material| piezas.push((dx, dy, dz, m));
422
423 // Paleta de lomos de libro / telas.
424 let books = [Material::Ladrillo, Material::Leaf, Material::Bloom, Material::Wheat];
425
426 match mueble {
427 Mueble::Mesa => {
428 for dx in -2..=2 {
429 for dz in -2..=2 {
430 put(dx, 3, dz, Material::Madera); // tapa
431 }
432 }
433 for &(lx, lz) in &[(-2, -2), (2, -2), (-2, 2), (2, 2)] {
434 for dy in 0..3 {
435 put(lx, dy, lz, Material::Madera); // patas
436 }
437 }
438 }
439
440 Mueble::Silla => {
441 for dx in -1..=1 {
442 for dz in -1..=1 {
443 put(dx, 2, dz, Material::Madera); // asiento
444 }
445 }
446 for &(lx, lz) in &[(-1, -1), (1, -1), (-1, 1), (1, 1)] {
447 for dy in 0..2 {
448 put(lx, dy, lz, Material::Madera); // patas
449 }
450 }
451 for dx in -1..=1 {
452 for dy in 3..=4 {
453 put(dx, dy, -1, Material::Madera); // respaldo (al fondo)
454 }
455 }
456 }
457
458 Mueble::Cama => {
459 for dx in -1..=1 {
460 for dz in -2..=3 {
461 put(dx, 1, dz, Material::Madera); // bastidor
462 put(dx, 2, dz, Material::Yeso); // colchón
463 }
464 }
465 for dz in -2..=3 {
466 put(-2, 1, dz, Material::Madera);
467 put(2, 1, dz, Material::Madera);
468 }
469 for dx in -1..=1 {
470 put(dx, 3, 3, Material::Bloom); // almohada (cabecera +Z)
471 }
472 }
473
474 Mueble::Estante => {
475 for dy in 0..=6 {
476 put(-2, dy, 0, Material::Madera); // laterales
477 put(2, dy, 0, Material::Madera);
478 }
479 for &sy in &[0, 2, 4, 6] {
480 for dx in -2..=2 {
481 put(dx, sy, 0, Material::Madera); // estantes
482 }
483 }
484 for (bi, &sy) in [1i32, 3, 5].iter().enumerate() {
485 for dx in -1..=1 {
486 let m = books[((dx + bi as i32).rem_euclid(4)) as usize]; // libros
487 put(dx, sy, 0, m);
488 }
489 }
490 }
491
492 Mueble::Lampara => {
493 for dx in -1..=1 {
494 for dz in -1..=1 {
495 put(dx, 0, dz, Material::Acero); // base
496 }
497 }
498 for dy in 1..=5 {
499 put(0, dy, 0, Material::Acero); // poste
500 }
501 for dx in -1..=1 {
502 for dz in -1..=1 {
503 put(dx, 6, dz, Material::Wheat); // pantalla cálida (lustrosa)
504 }
505 }
506 put(0, 7, 0, Material::Wheat);
507 }
508
509 Mueble::Alfombra => {
510 for dx in -2i32..=2 {
511 for dz in -3i32..=3 {
512 // Borde de un color, centro de otro.
513 let borde = dx.abs() == 2 || dz.abs() == 3;
514 put(dx, 0, dz, if borde { Material::Bloom } else { Material::Ceramica });
515 }
516 }
517 }
518
519 Mueble::Cofre => {
520 for dx in -1..=1 {
521 for dz in -1..=1 {
522 for dy in 0..=1 {
523 put(dx, dy, dz, Material::Madera); // cuerpo
524 }
525 put(dx, 2, dz, Material::Madera); // tapa
526 }
527 }
528 for dx in -1..=1 {
529 put(dx, 0, -1, Material::Acero); // herrajes al frente
530 put(dx, 2, -1, Material::Acero);
531 }
532 put(0, 1, -1, Material::Acero); // cerradura
533 }
534
535 Mueble::Estufa => {
536 for dx in -1..=1 {
537 for dz in -1..=1 {
538 for dy in 0..=2 {
539 put(dx, dy, dz, Material::Hormigon); // cuerpo
540 }
541 put(dx, 3, dz, Material::Acero); // hornalla de acero
542 }
543 }
544 put(0, 1, -1, Material::Lava); // boca de fuego (al frente)
545 put(0, 0, -1, Material::Lava);
546 }
547
548 Mueble::Sofa => {
549 for dx in -2..=2 {
550 put(dx, 0, 0, Material::Madera); // base
551 put(dx, 0, 1, Material::Madera);
552 put(dx, 1, 0, Material::Bloom); // asiento (tela)
553 put(dx, 1, 1, Material::Bloom);
554 put(dx, 2, -1, Material::Bloom); // respaldo
555 put(dx, 3, -1, Material::Bloom);
556 }
557 for dz in -1..=1 {
558 put(-2, 1, dz, Material::Bloom); // brazos
559 put(-2, 2, dz, Material::Bloom);
560 put(2, 1, dz, Material::Bloom);
561 put(2, 2, dz, Material::Bloom);
562 }
563 }
564
565 Mueble::Maceta => {
566 for dx in -1i32..=1 {
567 for dz in -1i32..=1 {
568 put(dx, 0, dz, Material::Ceramica); // tiesto
569 if dx.abs() == 1 || dz.abs() == 1 {
570 put(dx, 1, dz, Material::Ceramica); // borde
571 }
572 }
573 }
574 put(0, 1, 0, Material::Tierra); // tierra
575 put(0, 2, 0, Material::Leaf); // plantita
576 put(0, 3, 0, Material::Leaf);
577 put(1, 2, 0, Material::Leaf);
578 put(-1, 2, 0, Material::Leaf);
579 put(0, 2, 1, Material::Leaf);
580 }
581
582 Mueble::Barril => {
583 for dy in 0..=3 {
584 let m = if dy == 0 || dy == 3 { Material::Acero } else { Material::Madera };
585 for dx in -1..=1 {
586 for dz in -1..=1 {
587 if dx * dx + dz * dz <= 2 {
588 put(dx, dy, dz, m); // duela octogonal + aros
589 }
590 }
591 }
592 }
593 put(0, 4, 0, Material::Madera); // tapa
594 }
595
596 Mueble::Escritorio => {
597 for dx in -2..=2 {
598 for dz in -1..=1 {
599 put(dx, 3, dz, Material::Madera); // tapa
600 }
601 }
602 // Bloque de cajones a un lado.
603 for dy in 0..=2 {
604 for dz in -1..=1 {
605 put(-2, dy, dz, Material::Madera);
606 put(-1, dy, dz, Material::Madera);
607 }
608 }
609 for dy in &[0, 2] {
610 put(-2, *dy, -1, Material::Acero); // tiradores
611 }
612 for dy in 0..3 {
613 put(2, dy, -1, Material::Madera); // pata del otro lado
614 put(2, dy, 1, Material::Madera);
615 }
616 }
617
618 // ---------------- Plantas de interior ----------------
619 Mueble::Ficus => {
620 for dx in -1..=1 {
621 for dz in -1..=1 {
622 put(dx, 0, dz, Material::Ceramica); // maceta
623 }
624 }
625 for dy in 1..=5 {
626 put(0, dy, 0, Material::Madera); // tronco
627 }
628 for dx in -2..=2 {
629 for dy in 5..=8 {
630 for dz in -2..=2 {
631 if dx * dx + (dy - 6) * (dy - 6) + dz * dz <= 5 {
632 put(dx, dy, dz, Material::Leaf); // copa frondosa
633 }
634 }
635 }
636 }
637 }
638
639 Mueble::Palma => {
640 for dx in -1..=1 {
641 for dz in -1..=1 {
642 put(dx, 0, dz, Material::Ceramica);
643 }
644 }
645 for dy in 1..=6 {
646 put(0, dy, 0, Material::Madera); // tronco fino
647 }
648 let ty = 6;
649 put(0, ty + 1, 0, Material::Leaf);
650 for (dxi, dzi) in [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (-1, -1)] {
651 for s in 1..=2 {
652 put(dxi * s, ty + 1 - s, dzi * s, Material::Leaf); // frondas que caen
653 }
654 }
655 }
656
657 Mueble::Suculenta => {
658 for dx in -1..=1 {
659 for dz in -1..=1 {
660 put(dx, 0, dz, Material::Ceramica); // maceta baja
661 }
662 }
663 put(0, 1, 0, Material::Leaf); // roseta
664 for (dxi, dzi) in [(1, 0), (-1, 0), (0, 1), (0, -1)] {
665 put(dxi, 1, dzi, Material::Leaf);
666 }
667 put(0, 2, 0, Material::Leaf);
668 }
669
670 Mueble::Bonsai => {
671 for dx in -1..=1 {
672 for dz in -1..=1 {
673 put(dx, 0, dz, Material::Ceramica); // maceta plana ancha
674 }
675 }
676 put(0, 1, 0, Material::Madera); // tronco corto
677 put(0, 2, 0, Material::Madera);
678 put(1, 2, 0, Material::Madera);
679 for (dxi, dzi) in [(1, 0), (2, 0), (0, 1), (-1, 1)] {
680 put(dxi, 3, dzi, Material::Leaf); // copas plato
681 }
682 put(0, 3, 0, Material::Leaf);
683 }
684
685 Mueble::Colgante => {
686 // Cesta a altura + enredaderas que caen.
687 for dx in -1..=1 {
688 put(dx, 6, 0, Material::Ceramica);
689 }
690 for &dx in &[-1, 0, 1] {
691 for dy in 2..=5 {
692 if (dx + dy) % 2 == 0 {
693 put(dx, dy, 0, Material::Leaf); // hebras verdes
694 }
695 }
696 }
697 }
698
699 Mueble::Nopal => {
700 for dx in -1..=1 {
701 for dz in -1..=1 {
702 put(dx, 0, dz, Material::Ceramica);
703 }
704 }
705 for dy in 1..=4 {
706 put(0, dy, 0, Material::Cactus); // columna
707 }
708 put(1, 2, 0, Material::Cactus); // brazos
709 put(1, 3, 0, Material::Cactus);
710 put(-1, 3, 0, Material::Cactus);
711 }
712
713 // ---------------- Decoración de pared ----------------
714 Mueble::Cuadro => {
715 let (y0, y1) = (4i32, 7i32);
716 for dy in y0..=y1 {
717 for dx in -1i32..=1 {
718 let borde = dx.abs() == 1 || dy == y0 || dy == y1;
719 put(dx, dy, 0, if borde { Material::Madera } else { Material::Bloom });
720 }
721 }
722 }
723
724 Mueble::Espejo => {
725 let (y0, y1) = (4i32, 7i32);
726 for dy in y0..=y1 {
727 for dx in -1i32..=1 {
728 let borde = dx.abs() == 1 || dy == y0 || dy == y1;
729 put(dx, dy, 0, if borde { Material::Acero } else { Material::Vidrio });
730 }
731 }
732 }
733
734 Mueble::Reloj => {
735 let cy = 5i32;
736 for dx in -1i32..=1 {
737 for dy in (cy - 1)..=(cy + 1) {
738 if dx.abs() + (dy - cy).abs() <= 2 {
739 put(dx, dy, 0, Material::Yeso); // esfera
740 }
741 }
742 }
743 put(0, cy, 0, Material::Pizarra); // centro/agujas
744 put(0, cy + 1, 0, Material::Pizarra);
745 put(1, cy, 0, Material::Madera); // marco
746 put(-1, cy, 0, Material::Madera);
747 }
748
749 Mueble::Repisa => {
750 for dx in -2..=2 {
751 put(dx, 5, 0, Material::Madera); // estante flotante
752 }
753 put(-1, 6, 0, Material::Bloom); // un libro
754 put(-1, 7, 0, Material::Leaf);
755 put(1, 6, 0, Material::Ceramica); // una maceta chica
756 put(1, 7, 0, Material::Leaf);
757 }
758
759 Mueble::Aplique => {
760 put(0, 4, 0, Material::Acero); // placa
761 put(0, 5, 0, Material::Acero);
762 put(0, 6, 0, Material::Acero);
763 put(0, 5, 1, Material::Acero); // brazo
764 put(0, 6, 1, Material::Wheat); // luz cálida (lustrosa)
765 put(0, 4, 1, Material::Wheat);
766 }
767
768 Mueble::Tapiz => {
769 for dy in 2..=7 {
770 for dx in -1..=1 {
771 let m = if (dx + dy) % 2 == 0 { Material::Bloom } else { Material::Leaf };
772 put(dx, dy, 0, m); // paño de colores
773 }
774 }
775 }
776
777 // ---------------- Casas y edificios modernos ----------------
778 Mueble::Escalera => {
779 for s in 0..7i32 {
780 for dx in -1..=1 {
781 for k in 0..=s {
782 put(dx, k, s, Material::Hormigon); // peldaños macizos que suben en +Z
783 }
784 }
785 }
786 }
787
788 Mueble::Baranda => {
789 for dx in -2..=2 {
790 put(dx, 1, 0, Material::Vidrio); // paño de vidrio
791 put(dx, 2, 0, Material::Vidrio);
792 put(dx, 3, 0, Material::Acero); // pasamanos
793 }
794 for &dx in &[-2i32, 0, 2] {
795 for dy in 0..3 {
796 put(dx, dy, 0, Material::Acero); // montantes
797 }
798 }
799 }
800
801 Mueble::Ventanal => {
802 for dy in 0..9 {
803 for dx in -1..=1 {
804 put(dx, dy, 0, Material::Vidrio); // paño de piso a techo
805 }
806 put(-2, dy, 0, Material::Acero); // parantes
807 put(2, dy, 0, Material::Acero);
808 }
809 for dx in -2..=2 {
810 put(dx, 0, 0, Material::Acero); // marco inf/sup
811 put(dx, 8, 0, Material::Acero);
812 }
813 }
814
815 Mueble::Columna => {
816 for dy in 0..10 {
817 for dx in 0..=1 {
818 for dz in 0..=1 {
819 put(dx, dy, dz, Material::Hormigon); // pilar 2×2
820 }
821 }
822 }
823 }
824
825 Mueble::Aire => {
826 for dx in -2..=2 {
827 for dy in 6..8 {
828 put(dx, dy, 0, Material::Yeso); // caja del split
829 }
830 put(dx, 6, 0, Material::Acero); // rejilla
831 }
832 }
833
834 Mueble::Solar => {
835 for dx in -2..=2 {
836 for dz in 0..4 {
837 put(dx, 2 + dz, dz, Material::Azul); // placa azul inclinada
838 }
839 }
840 for &dx in &[-2i32, 2] {
841 for dy in 0..2 {
842 put(dx, dy, 0, Material::Acero); // patas
843 }
844 }
845 put(0, 0, 0, Material::Acero);
846 }
847
848 Mueble::Tele => {
849 for dx in -2..=2 {
850 for dy in 3..6 {
851 put(dx, dy, 0, Material::Pizarra); // pantalla negra
852 }
853 }
854 for dy in 0..3 {
855 put(0, dy, 0, Material::Acero); // pie
856 }
857 put(-1, 0, 1, Material::Acero); // base
858 put(1, 0, 1, Material::Acero);
859 }
860
861 Mueble::Isla => {
862 for dx in -2..=2 {
863 for dz in -1..=1 {
864 for dy in 0..2 {
865 put(dx, dy, dz, Material::Yeso); // bajomesada
866 }
867 put(dx, 2, dz, Material::Marmol); // mesada de mármol (lustrosa)
868 }
869 }
870 }
871
872 Mueble::Ducha => {
873 for dx in -1..=1 {
874 for dz in -1..=1 {
875 put(dx, 0, dz, Material::Ceramica); // base
876 }
877 }
878 for dy in 1..7 {
879 for dx in -1..=1 {
880 put(dx, dy, -1, Material::Vidrio); // mampara fondo
881 }
882 for dz in -1..=1 {
883 put(-1, dy, dz, Material::Vidrio); // mampara lateral
884 }
885 }
886 put(0, 6, -1, Material::Acero); // ducha
887 put(0, 5, -1, Material::Acero);
888 }
889
890 Mueble::Placard => {
891 for dx in -1..=1 {
892 for dy in 0..7 {
893 for dz in -1..=0 {
894 put(dx, dy, dz, Material::Madera); // cuerpo alto
895 }
896 }
897 }
898 put(-1, 3, 0, Material::Acero); // manijas
899 put(1, 3, 0, Material::Acero);
900 }
901
902 Mueble::Farol => {
903 for dy in 0..7 {
904 put(0, dy, 0, Material::Acero); // poste
905 }
906 for dx in -1..=1 {
907 for dz in -1..=1 {
908 put(dx, 7, dz, Material::Vidrio); // farol de vidrio
909 put(dx, 9, dz, Material::Acero); // capucha
910 }
911 }
912 put(0, 8, 0, Material::Wheat); // luz
913 }
914
915 Mueble::Pileta => {
916 for dx in -3i32..=3 {
917 for dz in -2i32..=2 {
918 let borde = dx.abs() == 3 || dz.abs() == 2;
919 put(dx, 0, dz, if borde { Material::Ceramica } else { Material::Water });
920 }
921 }
922 }
923
924 // ---------------- Elementos de ciudad ----------------
925 Mueble::Semaforo => {
926 for dy in 0..6 {
927 put(0, dy, 0, Material::Acero); // poste
928 }
929 for dy in 5..9 {
930 put(0, dy, 0, Material::Pizarra); // caja
931 }
932 put(0, 8, 1, Material::Lava); // rojo
933 put(0, 7, 1, Material::Wheat); // amarillo
934 put(0, 6, 1, Material::Leaf); // verde
935 put(1, 7, 0, Material::Acero); // brazo
936 }
937
938 Mueble::Banco => {
939 for dx in -2..=2 {
940 put(dx, 1, 0, Material::Madera); // asiento
941 put(dx, 2, -1, Material::Madera); // respaldo
942 }
943 for &dx in &[-2i32, 2] {
944 put(dx, 0, 0, Material::Acero); // patas
945 }
946 }
947
948 Mueble::Fuente => {
949 for dx in -2i32..=2 {
950 for dz in -2i32..=2 {
951 if dx * dx + dz * dz <= 4 {
952 put(dx, 0, dz, Material::Hormigon); // pileta de piedra
953 if dx * dx + dz * dz <= 2 {
954 put(dx, 1, dz, Material::Water); // agua
955 }
956 }
957 }
958 }
959 put(0, 1, 0, Material::Hormigon); // columna
960 put(0, 2, 0, Material::Hormigon);
961 put(0, 3, 0, Material::Water); // chorro
962 }
963
964 Mueble::Buzon => {
965 put(0, 0, 0, Material::Acero); // poste
966 put(0, 1, 0, Material::Acero);
967 for dx in -1..=1 {
968 for dy in 2..4 {
969 put(dx, dy, 0, Material::Azul); // caja azul
970 }
971 }
972 put(1, 3, 0, Material::Azul);
973 }
974
975 Mueble::Basura => {
976 for dy in 0..3 {
977 for dx in -1i32..=1 {
978 for dz in -1i32..=1 {
979 if dx * dx + dz * dz <= 2 {
980 put(dx, dy, dz, Material::Acero); // tacho
981 }
982 }
983 }
984 }
985 for dx in -1i32..=1 {
986 for dz in -1i32..=1 {
987 if dx * dx + dz * dz <= 2 {
988 put(dx, 3, dz, Material::Pizarra); // tapa
989 }
990 }
991 }
992 }
993
994 Mueble::Parada => {
995 for &(px, pz) in &[(-2i32, -1i32), (2, -1), (-2, 1), (2, 1)] {
996 for dy in 0..5 {
997 put(px, dy, pz, Material::Acero); // columnas
998 }
999 }
1000 for dx in -2..=2 {
1001 for dz in -1..=1 {
1002 put(dx, 5, dz, Material::Vidrio); // techo de vidrio
1003 }
1004 for dy in 1..4 {
1005 put(dx, dy, -1, Material::Vidrio); // fondo de vidrio
1006 }
1007 put(dx, 1, 0, Material::Madera); // banco
1008 }
1009 }
1010
1011 Mueble::Hidrante => {
1012 for dy in 0..3 {
1013 put(0, dy, 0, Material::Lava); // cuerpo rojo
1014 }
1015 put(1, 1, 0, Material::Lava); // brazos
1016 put(-1, 1, 0, Material::Lava);
1017 put(0, 3, 0, Material::Lava); // tapa
1018 }
1019
1020 Mueble::Cabina => {
1021 for dy in 0..7 {
1022 for dx in -1..=1 {
1023 put(dx, dy, -1, Material::Vidrio); // paredes de vidrio
1024 put(dx, dy, 1, Material::Vidrio);
1025 }
1026 put(-1, dy, 0, Material::Vidrio);
1027 for &(cx, cz) in &[(-1i32, -1i32), (1, -1), (-1, 1), (1, 1)] {
1028 put(cx, dy, cz, Material::Acero); // esquinas
1029 }
1030 }
1031 for dx in -1..=1 {
1032 for dz in -1..=1 {
1033 put(dx, 7, dz, Material::Azul); // techo azul
1034 }
1035 }
1036 }
1037
1038 Mueble::Cartel => {
1039 for dy in 0..5 {
1040 put(0, dy, 0, Material::Acero); // poste
1041 }
1042 for dx in -1..=1 {
1043 for dy in 5..7 {
1044 put(dx, dy, 0, Material::Yeso); // letrero blanco
1045 }
1046 }
1047 put(0, 6, 0, Material::Azul); // franja
1048 }
1049
1050 Mueble::Auto => {
1051 for dx in -3..=3 {
1052 for dz in -1..=1 {
1053 put(dx, 1, dz, body); // carrocería (color por posición)
1054 }
1055 }
1056 for dx in -1..=1 {
1057 for dz in -1..=1 {
1058 put(dx, 2, dz, Material::Vidrio); // habitáculo/ventanas
1059 }
1060 }
1061 for &(wx, wz) in &[(-2i32, -1i32), (2, -1), (-2, 1), (2, 1)] {
1062 put(wx, 0, wz, Material::Pizarra); // ruedas
1063 }
1064 }
1065
1066 Mueble::Estatua => {
1067 for dx in -1..=1 {
1068 for dz in -1..=1 {
1069 put(dx, 0, dz, Material::Marmol); // pedestal
1070 put(dx, 1, dz, Material::Marmol);
1071 }
1072 }
1073 for dy in 2..5 {
1074 put(0, dy, 0, Material::Marmol); // figura
1075 }
1076 put(1, 3, 0, Material::Marmol); // brazos
1077 put(-1, 3, 0, Material::Marmol);
1078 }
1079
1080 Mueble::Bolardo => {
1081 for dy in 0..4 {
1082 put(0, dy, 0, Material::Hormigon); // poste bajo
1083 }
1084 for &(dx, dz) in &[(1, 0), (-1, 0), (0, 1), (0, -1)] {
1085 put(dx, 0, dz, Material::Hormigon); // base ancha
1086 }
1087 put(0, 4, 0, Material::Acero); // tapa
1088 }
1089
1090 // ---------------- Edificios grandes ----------------
1091 Mueble::Rascacielos => {
1092 let h = 20i32;
1093 for dy in 0..h {
1094 for dx in -2..=1 {
1095 for dz in -2..=1 {
1096 let borde = dx == -2 || dx == 1 || dz == -2 || dz == 1;
1097 if borde {
1098 let esquina = (dx == -2 || dx == 1) && (dz == -2 || dz == 1);
1099 let banda = dy % 4 == 0;
1100 put(dx, dy, dz, if esquina || banda { Material::Acero } else { Material::Vidrio });
1101 }
1102 }
1103 }
1104 }
1105 for dx in -2..=1 {
1106 for dz in -2..=1 {
1107 put(dx, h, dz, Material::Hormigon); // azotea
1108 }
1109 }
1110 put(0, h + 1, 0, Material::Acero);
1111 put(0, h + 2, 0, Material::Acero); // antena
1112 }
1113
1114 Mueble::Edificio => {
1115 let h = 12i32;
1116 for dy in 0..h {
1117 for dx in -2i32..=2 {
1118 for dz in -2..=1 {
1119 let borde = dx == -2 || dx == 2 || dz == -2 || dz == 1;
1120 if borde {
1121 let ventana = dy > 0 && dy % 3 != 0 && dx.rem_euclid(2) == 0;
1122 put(dx, dy, dz, if ventana { Material::Vidrio } else { Material::Ladrillo });
1123 }
1124 }
1125 }
1126 }
1127 for dx in -2..=2 {
1128 for dz in -2..=1 {
1129 put(dx, h, dz, Material::Hormigon); // azotea
1130 }
1131 }
1132 }
1133
1134 Mueble::Torre => {
1135 let h = 22i32;
1136 for dy in 0..h {
1137 for dx in 0..=1 {
1138 for dz in 0..=1 {
1139 put(dx, dy, dz, Material::Hormigon);
1140 }
1141 }
1142 }
1143 let mut dy = 2;
1144 while dy < h {
1145 put(0, dy, 0, Material::Vidrio); // ventanas
1146 put(1, dy, 1, Material::Vidrio);
1147 dy += 3;
1148 }
1149 for k in 0..4 {
1150 put(0, h + k, 0, Material::Acero); // antena
1151 }
1152 }
1153
1154 Mueble::Fabrica => {
1155 for dy in 0..6 {
1156 for dx in -3..=2 {
1157 for dz in -2..=1 {
1158 if dx == -3 || dx == 2 || dz == -2 || dz == 1 {
1159 put(dx, dy, dz, Material::Hormigon);
1160 }
1161 }
1162 }
1163 }
1164 for dx in -3..=2 {
1165 for dz in -2..=1 {
1166 put(dx, 6, dz, Material::Acero); // techo de zinc
1167 }
1168 }
1169 for dy in 0..12 {
1170 put(2, dy, 1, Material::Ladrillo); // chimenea
1171 }
1172 put(2, 12, 1, Material::Pizarra);
1173 }
1174
1175 Mueble::Iglesia => {
1176 for dy in 0..6 {
1177 for dx in -2..=1 {
1178 for dz in -3..=2 {
1179 if dx == -2 || dx == 1 || dz == -3 || dz == 2 {
1180 put(dx, dy, dz, Material::Yeso);
1181 }
1182 }
1183 }
1184 }
1185 for dx in -2..=1 {
1186 for dz in -3..=2 {
1187 put(dx, 6, dz, Material::Teja); // techo
1188 }
1189 }
1190 for dy in 0..11 {
1191 for cdx in -2..=-1 {
1192 for cdz in -3..=-2 {
1193 put(cdx, dy, cdz, Material::Yeso); // campanario
1194 }
1195 }
1196 }
1197 put(-2, 11, -3, Material::Teja);
1198 // cruz
1199 put(-2, 12, -3, Material::Acero);
1200 put(-2, 13, -3, Material::Acero);
1201 put(-3, 13, -3, Material::Acero);
1202 put(-1, 13, -3, Material::Acero);
1203 }
1204
1205 Mueble::Galpon => {
1206 for dy in 0..5 {
1207 for dx in -4..=3 {
1208 for dz in -3..=2 {
1209 if dx == -4 || dx == 3 || dz == -3 || dz == 2 {
1210 put(dx, dy, dz, Material::Hormigon);
1211 }
1212 }
1213 }
1214 }
1215 for dx in -4..=3 {
1216 for dz in -3..=2 {
1217 let slope = (dz + 3).min(2 - dz).min(3);
1218 put(dx, 5 + slope, dz, Material::Acero); // techo de zinc a dos aguas
1219 }
1220 }
1221 for dx in -1..=1 {
1222 for dy in 0..4 {
1223 put(dx, dy, 2, Material::Acero); // portón
1224 }
1225 }
1226 }
1227
1228 Mueble::Silo => {
1229 for dy in 0..14 {
1230 for dx in -1i32..=1 {
1231 for dz in -1i32..=1 {
1232 if dx * dx + dz * dz <= 2 {
1233 put(dx, dy, dz, Material::Acero); // cilindro
1234 }
1235 }
1236 }
1237 }
1238 for dx in -1i32..=1 {
1239 for dz in -1i32..=1 {
1240 if dx * dx + dz * dz <= 2 {
1241 put(dx, 14, dz, Material::Yeso); // cúpula
1242 }
1243 }
1244 }
1245 put(0, 15, 0, Material::Yeso);
1246 }
1247
1248 Mueble::Estacionamiento => {
1249 for lvl in 0..4 {
1250 let y = lvl * 3;
1251 for dx in -3..=2 {
1252 for dz in -2..=1 {
1253 put(dx, y, dz, Material::Hormigon); // losa
1254 }
1255 }
1256 for &(cx, cz) in &[(-3, -2), (2, -2), (-3, 1), (2, 1), (0, -2), (0, 1)] {
1257 for k in 1..3 {
1258 put(cx, y + k, cz, Material::Hormigon); // columnas
1259 }
1260 }
1261 }
1262 }
1263
1264 // ---------------- Más ciudad ----------------
1265 Mueble::Farola => {
1266 for dy in 0..10 {
1267 put(0, dy, 0, Material::Acero); // poste alto
1268 }
1269 for dx in 0..=2 {
1270 put(dx, 10, 0, Material::Acero); // brazo
1271 }
1272 put(2, 9, 0, Material::Wheat); // luminaria
1273 }
1274
1275 Mueble::Contenedor => {
1276 for dy in 0..2 {
1277 for dx in -2..=2 {
1278 for dz in -1..=1 {
1279 if dx == -2 || dx == 2 || dz == -1 || dz == 1 || dy == 0 {
1280 put(dx, dy, dz, Material::Acero); // volquete
1281 }
1282 }
1283 }
1284 }
1285 for dx in -2..=2 {
1286 for dz in -1..=1 {
1287 put(dx, 2, dz, Material::Pizarra); // tapa
1288 }
1289 }
1290 }
1291
1292 Mueble::Grua => {
1293 for dy in 0..18 {
1294 put(0, dy, 0, Material::Acero); // mástil
1295 }
1296 for dx in 0..=6 {
1297 put(dx, 17, 0, Material::Acero); // pluma
1298 }
1299 for dx in -3..=0 {
1300 put(dx, 17, 0, Material::Acero); // contrapluma
1301 }
1302 put(-3, 16, 0, Material::Hormigon); // contrapeso
1303 put(4, 16, 0, Material::Acero); // cable
1304 put(4, 15, 0, Material::Acero); // gancho
1305 }
1306
1307 Mueble::Puente => {
1308 for dx in -4..=4 {
1309 for dz in -1..=1 {
1310 put(dx, 3, dz, Material::Hormigon); // tablero
1311 }
1312 put(dx, 4, -1, Material::Acero); // barandas
1313 put(dx, 4, 1, Material::Acero);
1314 }
1315 for &px in &[-3i32, 3] {
1316 for dy in 0..3 {
1317 for dz in -1..=1 {
1318 put(px, dy, dz, Material::Hormigon); // pilas
1319 }
1320 }
1321 }
1322 }
1323
1324 // ---------------- Transporte ----------------
1325 Mueble::Avion => {
1326 for dx in -4..=4 {
1327 for dy in 1..=2 {
1328 put(dx, dy, 0, Material::Yeso); // fuselaje
1329 }
1330 }
1331 put(4, 2, 0, Material::Vidrio); // cabina
1332 for dz in -3..=3 {
1333 put(0, 1, dz, body); // alas (color por posición)
1334 }
1335 put(-4, 3, 0, Material::Yeso); // cola vertical
1336 put(-4, 4, 0, Material::Yeso);
1337 put(-4, 3, 1, Material::Yeso); // cola horizontal
1338 put(-4, 3, -1, Material::Yeso);
1339 put(0, 0, 2, Material::Pizarra); // turbinas
1340 put(0, 0, -2, Material::Pizarra);
1341 }
1342
1343 Mueble::Helicoptero => {
1344 for dx in -1..=1 {
1345 for dz in -1..=1 {
1346 for dy in 1..3 {
1347 put(dx, dy, dz, body); // cabina
1348 }
1349 }
1350 }
1351 put(1, 2, 0, Material::Vidrio); // burbuja
1352 for dx in -4..=-2 {
1353 put(dx, 2, 0, Material::Yeso); // cola
1354 }
1355 put(-4, 3, 0, Material::Pizarra); // rotor de cola
1356 for dx in -3..=3 {
1357 put(dx, 4, 0, Material::Pizarra); // rotor principal
1358 }
1359 for dz in -3..=3 {
1360 put(0, 4, dz, Material::Pizarra);
1361 }
1362 put(0, 3, 0, Material::Acero); // mástil
1363 for dx in -1..=1 {
1364 put(dx, 0, -1, Material::Acero); // patines
1365 put(dx, 0, 1, Material::Acero);
1366 }
1367 }
1368
1369 Mueble::Camioneta => {
1370 for dx in -3..=3 {
1371 for dz in -1..=1 {
1372 put(dx, 1, dz, body); // chasis
1373 }
1374 }
1375 for dx in 1..=3 {
1376 for dz in -1..=1 {
1377 put(dx, 2, dz, if dz == 0 && dx == 2 { Material::Vidrio } else { body }); // cabina
1378 }
1379 }
1380 for dx in -3..=0 {
1381 put(dx, 2, -1, body); // caja
1382 put(dx, 2, 1, body);
1383 }
1384 for &(wx, wz) in &[(-2i32, -1i32), (2, -1), (-2, 1), (2, 1)] {
1385 put(wx, 0, wz, Material::Pizarra); // ruedas
1386 }
1387 }
1388
1389 Mueble::Camion => {
1390 for dx in 2..=3 {
1391 for dz in -1..=1 {
1392 for dy in 1..3 {
1393 put(dx, dy, dz, body); // cabina
1394 }
1395 }
1396 }
1397 put(3, 2, 0, Material::Vidrio);
1398 for dx in -4..=1 {
1399 for dz in -1..=1 {
1400 for dy in 1..4 {
1401 if dx == -4 || dz == -1 || dz == 1 || dy == 3 {
1402 put(dx, dy, dz, Material::Yeso); // caja de carga
1403 }
1404 }
1405 }
1406 }
1407 for &(wx, wz) in &[(-3i32, -1i32), (0, -1), (3, -1), (-3, 1), (0, 1), (3, 1)] {
1408 put(wx, 0, wz, Material::Pizarra);
1409 }
1410 }
1411
1412 Mueble::Colectivo => {
1413 for dx in -5..=5 {
1414 for dz in -1..=1 {
1415 for dy in 1..4 {
1416 if dx == -5 || dx == 5 || dz == -1 || dz == 1 {
1417 put(dx, dy, dz, if dy == 3 { Material::Vidrio } else { body });
1418 }
1419 }
1420 }
1421 for dz in -1..=1 {
1422 put(dx, 4, dz, body); // techo
1423 }
1424 }
1425 for &wx in &[-4i32, -1, 4] {
1426 put(wx, 0, -1, Material::Pizarra);
1427 put(wx, 0, 1, Material::Pizarra);
1428 }
1429 }
1430
1431 Mueble::Taxi => {
1432 for dx in -3..=3 {
1433 for dz in -1..=1 {
1434 put(dx, 1, dz, Material::Wheat); // amarillo
1435 }
1436 }
1437 for dx in -1..=1 {
1438 for dz in -1..=1 {
1439 put(dx, 2, dz, if dz == 0 { Material::Vidrio } else { Material::Wheat });
1440 }
1441 }
1442 put(0, 3, 0, Material::Pizarra); // cartel de techo
1443 for &(wx, wz) in &[(-2i32, -1i32), (2, -1), (-2, 1), (2, 1)] {
1444 put(wx, 0, wz, Material::Pizarra);
1445 }
1446 }
1447
1448 Mueble::Tren => {
1449 for dx in -5..=5 {
1450 for dz in -1..=1 {
1451 for dy in 1..3 {
1452 put(dx, dy, dz, Material::Lava); // cuerpo rojo
1453 }
1454 }
1455 }
1456 for dx in 3..=5 {
1457 for dz in -1..=1 {
1458 put(dx, 3, dz, if dz == 0 { Material::Vidrio } else { Material::Lava }); // cabina
1459 }
1460 }
1461 put(-5, 3, 0, Material::Pizarra); // chimenea
1462 let mut wx = -4;
1463 while wx <= 4 {
1464 put(wx, 0, -1, Material::Pizarra);
1465 put(wx, 0, 1, Material::Pizarra);
1466 wx += 2;
1467 }
1468 }
1469
1470 Mueble::Vagon => {
1471 for dx in -4..=4 {
1472 for dz in -1..=1 {
1473 for dy in 1..3 {
1474 if dx == -4 || dx == 4 || dz == -1 || dz == 1 || dy == 2 {
1475 put(dx, dy, dz, body);
1476 }
1477 }
1478 }
1479 }
1480 let mut wx = -3;
1481 while wx <= 3 {
1482 put(wx, 0, -1, Material::Pizarra);
1483 put(wx, 0, 1, Material::Pizarra);
1484 wx += 3;
1485 }
1486 }
1487
1488 Mueble::Moto => {
1489 put(1, 0, 0, Material::Pizarra); // rueda delantera
1490 put(1, 1, 0, Material::Pizarra);
1491 put(-1, 0, 0, Material::Pizarra); // rueda trasera
1492 put(-1, 1, 0, Material::Pizarra);
1493 put(0, 1, 0, body); // cuerpo
1494 put(0, 2, 0, body); // asiento
1495 put(1, 2, 0, Material::Acero); // manubrio
1496 }
1497
1498 Mueble::Bici => {
1499 put(1, 0, 0, Material::Pizarra); // rueda delantera
1500 put(1, 1, 0, Material::Pizarra);
1501 put(-1, 0, 0, Material::Pizarra); // rueda trasera
1502 put(-1, 1, 0, Material::Pizarra);
1503 put(0, 1, 0, Material::Acero); // cuadro
1504 put(0, 2, 0, Material::Acero); // asiento
1505 put(1, 3, 0, Material::Acero); // manubrio
1506 }
1507
1508 Mueble::Barco => {
1509 for dx in -4..=4 {
1510 put(dx, 1, 0, body); // casco superior
1511 }
1512 for dx in -3..=3 {
1513 put(dx, 0, 0, body); // casco inferior
1514 }
1515 for dx in -1..=1 {
1516 put(dx, 2, 0, Material::Yeso); // cabina
1517 put(dx, 3, 0, Material::Vidrio);
1518 }
1519 put(0, 4, 0, Material::Acero); // mástil
1520 put(0, 5, 0, Material::Acero);
1521 }
1522
1523 Mueble::Ambulancia => {
1524 for dx in -3..=3 {
1525 for dz in -1..=1 {
1526 for dy in 1..4 {
1527 if dx == -3 || dx == 3 || dz == -1 || dz == 1 || dy == 3 {
1528 put(dx, dy, dz, Material::Yeso); // furgón blanco
1529 }
1530 }
1531 }
1532 }
1533 put(3, 2, 0, Material::Vidrio); // parabrisas
1534 put(0, 4, 0, Material::Lava); // baliza roja
1535 put(0, 2, 1, Material::Lava); // cruz roja
1536 put(0, 2, -1, Material::Lava);
1537 for &(wx, wz) in &[(-2i32, -1i32), (2, -1), (-2, 1), (2, 1)] {
1538 put(wx, 0, wz, Material::Pizarra);
1539 }
1540 }
1541 }
1542
1543 // **Toda construcción es simétrica izquierda-derecha** (la regla de
1544 // `casa.rs`). El eje es el centro de lo dibujado y no `dx = 0`: varios
1545 // edificios tienen ancho par (`-2..=1`), y espejar en 0 los correría un
1546 // voxel. Cada pieza se escribe con su espejo en el mismo orden, así el
1547 // último que escribe escribe en los dos lados.
1548 let espejo = if mueble.es_construccion() && !piezas.is_empty() {
1549 let (lo, hi) = piezas.iter().fold((i32::MAX, i32::MIN), |(lo, hi), p| (lo.min(p.0), hi.max(p.0)));
1550 Some(lo + hi)
1551 } else {
1552 None
1553 };
1554 for (dx, dy, dz, m) in piezas {
1555 let (rx, rz) = rot(dx, dz, facing);
1556 place(g, bx + rx, by + dy, bz + rz, m);
1557 if let Some(suma) = espejo {
1558 let (rx, rz) = rot(suma - dx, dz, facing);
1559 place(g, bx + rx, by + dy, bz + rz, m);
1560 }
1561 }
1562}
1563
1564#[cfg(test)]
1565mod tests {
1566 use super::*;
1567
1568 /// Las construcciones de la lista salen simétricas izquierda-derecha, con
1569 /// las cuatro orientaciones: el espejo se hace en coords locales, antes de
1570 /// rotar, así que «izquierda» es la del que la mira de frente.
1571 #[test]
1572 fn toda_construccion_es_simetrica_izquierda_derecha() {
1573 let dim = [48u32, 48, 48];
1574 let mut malas = Vec::new();
1575 for m in Mueble::ALL.iter().copied().filter(|m| m.es_construccion()) {
1576 for facing in 0..4u8 {
1577 let mut g = VoxelGrid::new(dim);
1578 stamp_mueble(&mut g, m, 24, 1, 24, facing);
1579 let pts: Vec<(u32, u32, u32)> = (0..dim[0])
1580 .flat_map(|x| (0..dim[1]).flat_map(move |y| (0..dim[2]).map(move |z| (x, y, z))))
1581 .filter(|&(x, y, z)| g.get(x, y, z).is_some_and(|c| c[3] > 0))
1582 .collect();
1583 // El eje de espejo, en coords de grilla: a lo ancho del frente,
1584 // que con facing 0/2 es `x` y con 1/3 es `z`.
1585 let en_x = facing % 2 == 0;
1586 let (lo, hi) = pts.iter().fold((u32::MAX, 0), |(lo, hi), p| {
1587 let v = if en_x { p.0 } else { p.2 };
1588 (lo.min(v), hi.max(v))
1589 });
1590 let distintos = pts
1591 .iter()
1592 .filter(|&&(x, y, z)| {
1593 let (ex, ez) = if en_x { (lo + hi - x, z) } else { (x, lo + hi - z) };
1594 g.get(x, y, z) != g.get(ex, y, ez)
1595 })
1596 .count();
1597 if distintos > 0 {
1598 malas.push(format!("{} (facing {facing}): {distintos}", m.label()));
1599 }
1600 }
1601 }
1602 assert!(malas.is_empty(), "construcciones asimétricas: {malas:?}");
1603 }
1604
1605 fn solids(g: &VoxelGrid, dim: [u32; 3]) -> usize {
1606 let mut n = 0;
1607 for x in 0..dim[0] {
1608 for y in 0..dim[1] {
1609 for z in 0..dim[2] {
1610 if g.is_solid(x as i32, y as i32, z as i32) {
1611 n += 1;
1612 }
1613 }
1614 }
1615 }
1616 n
1617 }
1618
1619 #[test]
1620 fn cada_mueble_estampa_voxels() {
1621 let dim = [24u32, 24, 24];
1622 for m in Mueble::ALL {
1623 let mut g = VoxelGrid::new(dim);
1624 stamp_mueble(&mut g, m, 12, 2, 12, 0);
1625 assert!(solids(&g, dim) >= 5, "{} estampó muy poco", m.label());
1626 }
1627 }
1628
1629 #[test]
1630 fn facing_rota_la_silueta() {
1631 // La silla (asimétrica por el respaldo) cambia de huella al rotar.
1632 let dim = [24u32, 24, 24];
1633 let mut a = VoxelGrid::new(dim);
1634 let mut b = VoxelGrid::new(dim);
1635 stamp_mueble(&mut a, Mueble::Silla, 12, 2, 12, 0);
1636 stamp_mueble(&mut b, Mueble::Silla, 12, 2, 12, 1);
1637 // El respaldo cae en una celda distinta según la orientación. Base (12,2,12),
1638 // respaldo local (0, 3, -1): facing 0 → (12,5,11); facing 1 → (13,5,12).
1639 let back0 = a.is_solid(12, 5, 11); // -Z de la base
1640 let back1 = b.is_solid(13, 5, 12); // rotado a +X
1641 assert!(back0, "respaldo en facing 0");
1642 assert!(back1, "respaldo rotó con el facing 1");
1643 }
1644}