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