veloren_world/site/plot/
cliff_town_airship_dock.rs

1use super::*;
2use crate::{
3    Land,
4    site::{generation::PrimitiveTransform, util::gradient::WrapMode},
5    util::{DIAGONALS, LOCALITY, NEIGHBORS, RandomField, Sampler, within_distance},
6};
7use common::{
8    generation::SpecialEntity,
9    terrain::{BlockKind, SpriteKind},
10};
11use rand::prelude::*;
12use std::{f32::consts::TAU, mem};
13use vek::*;
14
15/// Represents house data generated by the `generate()` method
16pub struct CliffTownAirshipDock {
17    /// Tile position of the door tile
18    pub door_tile: Vec2<i32>,
19    /// Approximate altitude of the door tile
20    pub(crate) alt: i32,
21    door_dir: Vec2<i32>,
22    surface_color: Rgb<f32>,
23    sub_surface_color: Rgb<f32>,
24    pub center: Vec2<i32>,
25    variant: i32,
26    storeys: i32,
27    platform_length: i32,
28    pub docking_positions: Vec<Vec3<i32>>,
29}
30
31impl CliffTownAirshipDock {
32    pub fn generate(
33        land: &Land,
34        index: IndexRef,
35        _rng: &mut impl Rng,
36        site: &Site,
37        door_tile: Vec2<i32>,
38        door_dir: Vec2<i32>,
39        tile_aabr: Aabr<i32>,
40    ) -> Self {
41        let door_tile_pos = site.tile_center_wpos(door_tile);
42        let bounds = Aabr {
43            min: site.tile_wpos(tile_aabr.min),
44            max: site.tile_wpos(tile_aabr.max),
45        };
46        let center = bounds.center();
47
48        // dock is 5 tiles = 30 blocks in radius
49        // airships are 37 blocks wide = 6 tiles.
50        // distance from the center to the outside edge of the airship when docked is 11
51        // tiles. The area covered by all four airships is a square 22 tiles on
52        // a side.
53
54        // Sample the surface altitude every 4 tiles (= 24 blocks) around the dock
55        // center to find the highest point of terrain surrounding the dock.
56        let mut max_surface_alt = i32::MIN;
57        // -12 -8 -4 0 +4 +8 +12
58        for dx in -3..=3 {
59            for dy in -3..=3 {
60                let pos = center + Vec2::new(dx * 24, dy * 24);
61                let surface_alt = land.get_surface_alt_approx(pos) as i32;
62                if surface_alt > max_surface_alt {
63                    max_surface_alt = surface_alt;
64                }
65            }
66        }
67
68        // Sample the surface altitude over the foundation area to get the
69        // minimum foundation alt. The foundation will be 10 tiles (60 blocks) square.
70        let mut min_foundation_alt = i32::MAX;
71        for dx in -2..=2 {
72            for dy in -2..=2 {
73                let pos = center + Vec2::new(dx * 15, dy * 15);
74                let alt = land.get_surface_alt_approx(pos) as i32;
75                if alt < min_foundation_alt {
76                    min_foundation_alt = alt;
77                }
78            }
79        }
80
81        let variant = 15;
82        let storeys = 5 + (variant / 2);
83        let platform_length = 2 * variant;
84        let mut docking_positions = vec![];
85        let platform_height = 18 + variant / 2 - 1;
86        // Try to place the dock so that the base of the tower is at min_foundation_alt.
87        // If that results in the docking platforms being below max_surface_alt,
88        // increase the base altitude.
89        let mut base_alt = min_foundation_alt;
90        // The docking platforms are at the floor level of the penultimate storey.
91        // The storeys are stacked with decreasing height (by 1).
92        // The tower base is sunk one floor below the foundation altitude in the
93        // rendering function below.
94        let mut platform_level = base_alt - platform_height + (storeys - 1) * platform_height
95            - (storeys - 2) * (storeys - 1) / 2;
96        let clearance = platform_level - (max_surface_alt + 6);
97        if clearance < 0 {
98            base_alt += -clearance;
99            platform_level += -clearance;
100        }
101        for dir in CARDINALS {
102            let docking_pos = center + dir * (platform_length + 9);
103            docking_positions.push(docking_pos.with_z(platform_level + 1));
104        }
105
106        let (surface_color, sub_surface_color) =
107            if let Some(sample) = land.column_sample(bounds.center(), index) {
108                (sample.surface_color, sample.sub_surface_color)
109            } else {
110                (Rgb::new(161.0, 116.0, 86.0), Rgb::new(88.0, 64.0, 64.0))
111            };
112        Self {
113            door_tile: door_tile_pos,
114            alt: base_alt,
115            door_dir,
116            surface_color,
117            sub_surface_color,
118            center,
119            variant,
120            storeys,
121            platform_length,
122            docking_positions,
123        }
124    }
125
126    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
127        SpawnRules {
128            trees: {
129                // dock is 3 tiles = 18 blocks in radius
130                // airships are 20 blocks wide.
131                // Leave extra space for tree width (at lease 15 extra).
132                // Don't allow trees within 18 + 20 + 15 = 53 blocks of the dock center
133                const AIRSHIP_MIN_TREE_DIST2: i32 = 53;
134                !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST2)
135            },
136            waypoints: false,
137            ..SpawnRules::default()
138        }
139    }
140}
141
142impl Structure for CliffTownAirshipDock {
143    #[cfg(feature = "use-dyn-lib")]
144    const UPDATE_FN: &'static [u8] = b"render_cliff_town_airship_dock\0";
145
146    #[cfg_attr(
147        feature = "be-dyn-lib",
148        unsafe(export_name = "render_cliff_town_airship_dock")
149    )]
150    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
151        let base = self.alt;
152        let plot_center = self.center;
153        let door_dir = self.door_dir;
154
155        let surface_color = self.surface_color.map(|e| (e * 255.0) as u8);
156        let sub_surface_color = self.sub_surface_color.map(|e| (e * 255.0) as u8);
157        let gradient_center = Vec3::new(
158            plot_center.x as f32,
159            plot_center.y as f32,
160            (base + 1) as f32,
161        );
162        let gradient_var_1 = (RandomField::new(0).get(plot_center.with_z(base)) % 8) as i32;
163        let gradient_var_2 = (RandomField::new(0).get(plot_center.with_z(base + 1)) % 10) as i32;
164
165        let brick = Fill::Gradient(
166            util::gradient::Gradient::new(
167                gradient_center,
168                8.0 + gradient_var_1 as f32,
169                util::gradient::Shape::Point,
170                (surface_color, sub_surface_color),
171            )
172            .with_repeat(if gradient_var_2 > 5 {
173                WrapMode::Repeat
174            } else {
175                WrapMode::PingPong
176            }),
177            BlockKind::Rock,
178        );
179
180        let wood = Fill::Brick(BlockKind::Wood, Rgb::new(106, 83, 51), 12);
181        let color = Fill::Block(Block::air(SpriteKind::CliffDecorBlock));
182        let window = Fill::Block(Block::air(SpriteKind::WindowArabic));
183        let window2 = Fill::Block(Block::air(SpriteKind::WindowArabic).with_ori(2).unwrap());
184        let rope = Fill::Block(Block::air(SpriteKind::Rope));
185
186        let tube_var = (RandomField::new(0).get(plot_center.with_z(base)) % 6) as i32;
187        let radius = 10.0 + tube_var as f32;
188        let tubes = 3.0 + tube_var as f32;
189        let phi = TAU / tubes;
190        for n in 1..=tubes as i32 {
191            let center = Vec2::new(
192                plot_center.x + (radius * ((n as f32 * phi).cos())) as i32,
193                plot_center.y + (radius * ((n as f32 * phi).sin())) as i32,
194            );
195            // common superquadric degree for rooms
196            let sq_type = 3.5;
197            let storeys = self.storeys;
198            let variant = self.variant;
199            let mut length = 16 + (variant / 2);
200            let mut width = 7 * length / 8;
201            let mut height = 18 + variant / 2;
202            let mut floor_level = self.alt - height + 1;
203            let platform_length = self.platform_length;
204            let mut ground_entries = 0;
205            for s in 0..storeys {
206                let x_offset =
207                    (RandomField::new(0).get((center - length).with_z(base)) % 10) as i32;
208                let y_offset =
209                    (RandomField::new(0).get((center + length).with_z(base)) % 10) as i32;
210                let super_center =
211                    Vec2::new(center.x - 3 + x_offset / 2, center.y - 3 + y_offset / 2);
212                // CliffTower Hoodoo Overlay
213                painter
214                    .cubic_bezier(
215                        super_center.with_z(floor_level + (height / 2)),
216                        (super_center - x_offset).with_z(floor_level + height),
217                        (super_center - y_offset).with_z(floor_level + (height) + (height / 2)),
218                        super_center.with_z(floor_level + (2 * height)),
219                        (length - 1) as f32,
220                    )
221                    .fill(brick.clone());
222                if s == (storeys - 1) {
223                    for dir in LOCALITY {
224                        let cone_pos = super_center + (dir * 2);
225                        let cone_var =
226                            4 + (RandomField::new(0).get(cone_pos.with_z(base)) % 4) as i32;
227                        painter
228                            .cone_with_radius(
229                                cone_pos.with_z(floor_level + (2 * height) + 5),
230                                (length / 2) as f32,
231                                (length + cone_var) as f32,
232                            )
233                            .fill(brick.clone());
234                    }
235                }
236                // center tube with  rooms
237                if n == tubes as i32 {
238                    // ground_entries
239                    if ground_entries < 1 && floor_level > (base - 6) {
240                        for dir in CARDINALS {
241                            let entry_pos_inner = plot_center + (dir * (2 * length) - 4);
242                            let entry_pos_outer = plot_center + (dir * (3 * length) + 4);
243                            painter
244                                .line(
245                                    entry_pos_inner.with_z(floor_level + 6),
246                                    entry_pos_outer.with_z(base + 35),
247                                    6.0,
248                                )
249                                .clear();
250                        }
251                        let door_start = plot_center + door_dir * ((3 * (length / 2)) + 1);
252                        painter
253                            .line(
254                                door_start.with_z(floor_level + 2),
255                                self.door_tile.with_z(base),
256                                4.0,
257                            )
258                            .fill(wood.clone());
259                        painter
260                            .line(
261                                door_start.with_z(floor_level + 7),
262                                self.door_tile.with_z(base + 6),
263                                7.0,
264                            )
265                            .clear();
266                        ground_entries += 1;
267                    }
268                    painter
269                        .cubic_bezier(
270                            plot_center.with_z(floor_level + (height / 2)),
271                            (plot_center - x_offset).with_z(floor_level + height),
272                            (plot_center - y_offset).with_z(floor_level + (height) + (height / 2)),
273                            plot_center.with_z(floor_level + (2 * height)),
274                            (length + 2) as f32,
275                        )
276                        .fill(brick.clone());
277                    // platform
278                    if s == (storeys - 1) {
279                        let limit_up = painter.aabb(Aabb {
280                            min: (plot_center - platform_length - 2).with_z(floor_level - 4),
281                            max: (plot_center + platform_length + 2).with_z(floor_level + 1),
282                        });
283                        painter
284                            .superquadric(
285                                Aabb {
286                                    min: (plot_center - platform_length - 2)
287                                        .with_z(floor_level - 4),
288                                    max: (plot_center + platform_length + 2)
289                                        .with_z(floor_level + 6),
290                                },
291                                4.0,
292                            )
293                            .intersect(limit_up)
294                            .fill(wood.clone());
295
296                        // Travel Agent desk
297                        let rotation = -f32::atan2(door_dir.x as f32, door_dir.y as f32);
298                        painter
299                            .aabb(Aabb {
300                                min: Vec2::new(plot_center.x - 5, plot_center.y - 5)
301                                    .with_z(floor_level + 1),
302                                max: Vec2::new(plot_center.x - 10, plot_center.y - 10)
303                                    .with_z(floor_level + 6),
304                            })
305                            .rotate_about(
306                                Mat3::rotation_z(rotation).as_(),
307                                plot_center.with_z(base),
308                            )
309                            .clear();
310                        painter
311                            .line(
312                                Vec2::new(plot_center.x - 6, plot_center.y - 9)
313                                    .with_z(floor_level + 1),
314                                Vec2::new(plot_center.x - 9, plot_center.y - 6)
315                                    .with_z(floor_level + 1),
316                                0.5,
317                            )
318                            .rotate_about(
319                                Mat3::rotation_z(rotation).as_(),
320                                plot_center.with_z(base),
321                            )
322                            .fill(brick.clone());
323                        painter
324                            .line(
325                                Vec2::new(plot_center.x - 10, plot_center.y - 10)
326                                    .with_z(floor_level + 1),
327                                Vec2::new(plot_center.x - 10, plot_center.y - 10)
328                                    .with_z(floor_level + 6),
329                                0.5,
330                            )
331                            .rotate_about(
332                                Mat3::rotation_z(rotation).as_(),
333                                plot_center.with_z(base),
334                            )
335                            .fill(brick.clone());
336                        let agent_lantern_pos = Vec2::new(plot_center.x - 9, plot_center.y - 9);
337                        let agent_lantern_pos_rotated = (plot_center.map(|i| i as f32)
338                            + Mat2::rotation_z(rotation)
339                                * (agent_lantern_pos - plot_center).map(|i| i as f32))
340                        .map(|f| f.round() as i32);
341                        painter.sprite(
342                            agent_lantern_pos_rotated.with_z(floor_level + 5),
343                            SpriteKind::WallLampMesa,
344                        );
345
346                        // lanterns & cargo
347                        for dir in NEIGHBORS {
348                            let lantern_pos = plot_center + (dir * (platform_length - 6));
349
350                            painter.sprite(
351                                lantern_pos.with_z(floor_level + 1),
352                                SpriteKind::StreetLamp,
353                            );
354                        }
355                        for dir in DIAGONALS {
356                            let cargo_pos = plot_center + (dir * (2 * length));
357
358                            for dir in CARDINALS {
359                                let sprite_pos = cargo_pos + dir;
360                                let rows =
361                                    (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
362                                for r in 0..rows {
363                                    painter
364                                        .aabb(Aabb {
365                                            min: (sprite_pos).with_z(floor_level + 1 + r),
366                                            max: (sprite_pos + 1).with_z(floor_level + 2 + r),
367                                        })
368                                        .fill(Fill::Block(Block::air(
369                                            match (RandomField::new(0)
370                                                .get(sprite_pos.with_z(base + r))
371                                                % 2)
372                                                as i32
373                                            {
374                                                0 => SpriteKind::Barrel,
375                                                _ => SpriteKind::CrateBlock,
376                                            },
377                                        )));
378                                    if r > 0 {
379                                        painter.owned_resource_sprite(
380                                            sprite_pos.with_z(floor_level + 2 + r),
381                                            SpriteKind::Crate,
382                                            0,
383                                        );
384                                    }
385                                }
386                            }
387                        }
388                        for dir in CARDINALS {
389                            // docks
390                            let dock_pos = plot_center + (dir * platform_length);
391
392                            painter
393                                .cylinder(Aabb {
394                                    min: (dock_pos - 8).with_z(floor_level),
395                                    max: (dock_pos + 8).with_z(floor_level + 1),
396                                })
397                                .fill(wood.clone());
398                            painter
399                                .cylinder(Aabb {
400                                    min: (dock_pos - 7).with_z(floor_level - 1),
401                                    max: (dock_pos + 7).with_z(floor_level),
402                                })
403                                .fill(wood.clone());
404                        }
405                        // campfire
406                        let campfire_pos =
407                            Vec2::new(plot_center.x - platform_length - 2, plot_center.y)
408                                .with_z(floor_level);
409                        painter.spawn(
410                            EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
411                                .into_special(SpecialEntity::Waypoint),
412                        );
413                    }
414
415                    // clear rooms and entries & decor
416                    if floor_level > (base - 6) {
417                        // decor
418                        painter
419                            .line(
420                                Vec2::new(plot_center.x, plot_center.y - length)
421                                    .with_z(floor_level + 5),
422                                Vec2::new(plot_center.x, plot_center.y + length)
423                                    .with_z(floor_level + 5),
424                                4.0,
425                            )
426                            .fill(color.clone());
427                        painter
428                            .line(
429                                Vec2::new(plot_center.x - length, plot_center.y)
430                                    .with_z(floor_level + 5),
431                                Vec2::new(plot_center.x + length, plot_center.y)
432                                    .with_z(floor_level + 5),
433                                4.0,
434                            )
435                            .fill(color.clone());
436                        // entries
437                        painter
438                            .line(
439                                Vec2::new(plot_center.x, plot_center.y - (2 * length) - 4)
440                                    .with_z(floor_level + 4),
441                                Vec2::new(plot_center.x, plot_center.y + (2 * length) + 4)
442                                    .with_z(floor_level + 4),
443                                4.0,
444                            )
445                            .clear();
446                        painter
447                            .line(
448                                Vec2::new(plot_center.x - (2 * length) - 4, plot_center.y)
449                                    .with_z(floor_level + 4),
450                                Vec2::new(plot_center.x + (2 * length) + 4, plot_center.y)
451                                    .with_z(floor_level + 4),
452                                4.0,
453                            )
454                            .clear();
455                        painter
456                            .superquadric(
457                                Aabb {
458                                    min: (plot_center - length - 1).with_z(floor_level),
459                                    max: (plot_center + length + 1)
460                                        .with_z(floor_level + height - 4),
461                                },
462                                sq_type,
463                            )
464                            .clear();
465                        // room floor
466                        painter
467                            .cylinder(Aabb {
468                                min: (plot_center - length - 3).with_z(floor_level),
469                                max: (plot_center + length + 3).with_z(floor_level + 1),
470                            })
471                            .fill(brick.clone());
472                        painter
473                            .cylinder(Aabb {
474                                min: (plot_center - length + 1).with_z(floor_level),
475                                max: (plot_center + length - 1).with_z(floor_level + 1),
476                            })
477                            .fill(color.clone());
478                        painter
479                            .cylinder(Aabb {
480                                min: (plot_center - length + 2).with_z(floor_level),
481                                max: (plot_center + length - 2).with_z(floor_level + 1),
482                            })
483                            .fill(brick.clone());
484                        // entry sprites
485                        painter
486                            .aabb(Aabb {
487                                min: Vec2::new(plot_center.x - 3, plot_center.y + length)
488                                    .with_z(floor_level + 2),
489                                max: Vec2::new(plot_center.x + 4, plot_center.y + length + 1)
490                                    .with_z(floor_level + 7),
491                            })
492                            .fill(window2.clone());
493                        painter
494                            .aabb(Aabb {
495                                min: Vec2::new(plot_center.x - 2, plot_center.y + length)
496                                    .with_z(floor_level + 2),
497                                max: Vec2::new(plot_center.x + 3, plot_center.y + length + 1)
498                                    .with_z(floor_level + 7),
499                            })
500                            .clear();
501
502                        painter
503                            .aabb(Aabb {
504                                min: Vec2::new(plot_center.x - 3, plot_center.y - length - 1)
505                                    .with_z(floor_level + 2),
506                                max: Vec2::new(plot_center.x + 4, plot_center.y - length)
507                                    .with_z(floor_level + 7),
508                            })
509                            .fill(window2.clone());
510                        painter
511                            .aabb(Aabb {
512                                min: Vec2::new(plot_center.x - 2, plot_center.y - length - 1)
513                                    .with_z(floor_level + 2),
514                                max: Vec2::new(plot_center.x + 3, plot_center.y - length)
515                                    .with_z(floor_level + 7),
516                            })
517                            .clear();
518                        painter
519                            .aabb(Aabb {
520                                min: Vec2::new(plot_center.x + length, plot_center.y - 3)
521                                    .with_z(floor_level + 2),
522                                max: Vec2::new(plot_center.x + length + 1, plot_center.y + 4)
523                                    .with_z(floor_level + 7),
524                            })
525                            .fill(window.clone());
526                        painter
527                            .aabb(Aabb {
528                                min: Vec2::new(plot_center.x + length, plot_center.y - 2)
529                                    .with_z(floor_level + 2),
530                                max: Vec2::new(plot_center.x + length + 1, plot_center.y + 3)
531                                    .with_z(floor_level + 7),
532                            })
533                            .clear();
534
535                        painter
536                            .aabb(Aabb {
537                                min: Vec2::new(plot_center.x - length - 1, plot_center.y - 3)
538                                    .with_z(floor_level + 2),
539                                max: Vec2::new(plot_center.x - length, plot_center.y + 4)
540                                    .with_z(floor_level + 7),
541                            })
542                            .fill(window.clone());
543                        painter
544                            .aabb(Aabb {
545                                min: Vec2::new(plot_center.x - length - 1, plot_center.y - 2)
546                                    .with_z(floor_level + 2),
547                                max: Vec2::new(plot_center.x - length, plot_center.y + 3)
548                                    .with_z(floor_level + 7),
549                            })
550                            .clear();
551                        // cargo in rooms
552                        for dir in DIAGONALS {
553                            let cargo_pos = plot_center + (dir * (length / 2));
554                            for dir in CARDINALS {
555                                let sprite_pos = cargo_pos + dir;
556                                let rows =
557                                    (RandomField::new(0).get(sprite_pos.with_z(base)) % 4) as i32;
558                                for r in 0..rows {
559                                    painter
560                                        .aabb(Aabb {
561                                            min: (sprite_pos).with_z(floor_level + 1 + r),
562                                            max: (sprite_pos + 1).with_z(floor_level + 2 + r),
563                                        })
564                                        .fill(Fill::Block(Block::air(
565                                            match (RandomField::new(0)
566                                                .get(sprite_pos.with_z(base + r))
567                                                % 2)
568                                                as i32
569                                            {
570                                                0 => SpriteKind::Barrel,
571                                                _ => SpriteKind::CrateBlock,
572                                            },
573                                        )));
574                                }
575                            }
576                        }
577
578                        // wall lamps
579                        let corner_pos_1 = Vec2::new(plot_center.x - length, plot_center.y - 5);
580                        let corner_pos_2 = Vec2::new(plot_center.x - 5, plot_center.y - length);
581                        for dir in SQUARE_4 {
582                            let lamp_pos_1 = Vec2::new(
583                                corner_pos_1.x + (dir.x * ((2 * length) - 1)),
584                                corner_pos_1.y + (dir.y * 10),
585                            )
586                            .with_z(floor_level + 7);
587                            painter.rotated_sprite(
588                                lamp_pos_1,
589                                SpriteKind::WallLampMesa,
590                                (2 + (4 * dir.x)) as u8,
591                            );
592                            let lamp_pos_2 = Vec2::new(
593                                corner_pos_2.x + (dir.x * 10),
594                                corner_pos_2.y + (dir.y * ((2 * length) - 1)),
595                            )
596                            .with_z(floor_level + 7);
597                            painter.rotated_sprite(
598                                lamp_pos_2,
599                                SpriteKind::WallLampMesa,
600                                (4 - (4 * dir.y)) as u8,
601                            );
602                        }
603                    }
604                    // stairs
605                    if floor_level > (base + 8) {
606                        let stairs_level = floor_level + 1;
607                        let stairs_start = plot_center + door_dir * ((2 * length) - 7);
608                        let mid_dir = if door_dir.x != 0 {
609                            door_dir.x
610                        } else {
611                            door_dir.y
612                        };
613                        let stairs_mid = Vec2::new(
614                            plot_center.x + mid_dir * (3 * (length / 2)),
615                            plot_center.y + mid_dir * (3 * (length / 2)),
616                        );
617                        let stairs_end = Vec2::new(
618                            plot_center.x + door_dir.y * ((2 * length) - 7),
619                            plot_center.y + door_dir.x * ((2 * length) - 7),
620                        );
621                        let rope_pos = Vec2::new(
622                            plot_center.x + mid_dir * ((3 * (length / 2)) + 2),
623                            plot_center.y + mid_dir * ((3 * (length / 2)) + 2),
624                        );
625
626                        painter
627                            .cylinder(Aabb {
628                                min: (stairs_start - 6).with_z(stairs_level - 1),
629                                max: (stairs_start + 6).with_z(stairs_level),
630                            })
631                            .fill(wood.clone());
632
633                        painter
634                            .cylinder(Aabb {
635                                min: (stairs_mid - 6).with_z(stairs_level - (height / 2) - 1),
636                                max: (stairs_mid + 6).with_z(stairs_level - (height / 2)),
637                            })
638                            .fill(wood.clone());
639
640                        painter
641                            .cylinder(Aabb {
642                                min: (stairs_end - 6).with_z(stairs_level - height - 1),
643                                max: (stairs_end + 6).with_z(stairs_level - height),
644                            })
645                            .fill(wood.clone());
646
647                        for n in 0..2 {
648                            let stairs = painter
649                                .line(
650                                    stairs_start.with_z(stairs_level + (n * 2)),
651                                    stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
652                                    4.0 + (n as f32 / 2.0),
653                                )
654                                .union(painter.line(
655                                    stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
656                                    stairs_end.with_z(stairs_level - height + (n * 2)),
657                                    4.0 + (n as f32 / 2.0),
658                                ));
659                            match n {
660                                0 => stairs.fill(wood.clone()),
661                                _ => stairs.clear(),
662                            };
663                        }
664                        painter
665                            .line(
666                                rope_pos.with_z(stairs_level + (height / 2) - 3),
667                                (plot_center - (length / 2))
668                                    .with_z(stairs_level + (height / 2) + 2),
669                                1.5,
670                            )
671                            .fill(wood.clone());
672
673                        painter
674                            .aabb(Aabb {
675                                min: rope_pos.with_z(stairs_level - (height / 2) - 1),
676                                max: (rope_pos + 1).with_z(stairs_level + (height / 2) - 3),
677                            })
678                            .fill(rope.clone());
679                    }
680                }
681                // vary next storey
682                length += -1;
683                width += -1;
684                height += -1;
685                floor_level += height;
686                mem::swap(&mut length, &mut width);
687            }
688        }
689    }
690}