tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 229TOML 101Markdown 82Shell 24052 Definitions

⎇ main llimphi-voxel/src/mueble.rs Rust · 1644 lines · 55.2 KB History Who touched what raw

chore: refresco desde el monorepo — 132 miembros, 79 widgets, la galería vuelve y licencia dual d8edd9a9c · Sergio ·

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}