Skip to main content

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
127impl Structure for CliffTownAirshipDock {
128    #[cfg(feature = "dyn-lib")]
129    #[unsafe(export_name = "as_dyn_structure_clifftownairshipdock")]
130    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
131        Some((
132            Self::as_dyn_impl(self),
133            "as_dyn_structure_clifftownairshipdock",
134        ))
135    }
136
137    fn spawn_rules_inner(
138        &self,
139        spawn_rules: &mut SpawnRules,
140        _land: &Land,
141        wpos: Vec2<i32>,
142        _weight: f32,
143    ) {
144        // dock is 3 tiles = 18 blocks in radius
145        // airships are 20 blocks wide.
146        // Leave extra space for tree width (at lease 15 extra).
147        // Don't allow trees within 18 + 20 + 15 = 53 blocks of the dock center
148        const AIRSHIP_MIN_TREE_DIST: i32 = 53;
149        spawn_rules.trees &= !within_distance(wpos, self.center, AIRSHIP_MIN_TREE_DIST);
150        spawn_rules.waypoints = false;
151    }
152
153    fn airship_dock_info(&self) -> Option<AirshipDockInfo<'_>> {
154        Some(AirshipDockInfo {
155            door_tile: self.door_tile,
156            center: self.center,
157            docking_positions: &self.docking_positions,
158        })
159    }
160
161    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
162        let base = self.alt;
163        let plot_center = self.center;
164        let door_dir = self.door_dir;
165
166        let surface_color = self.surface_color.map(|e| (e * 255.0) as u8);
167        let sub_surface_color = self.sub_surface_color.map(|e| (e * 255.0) as u8);
168        let gradient_center = Vec3::new(
169            plot_center.x as f32,
170            plot_center.y as f32,
171            (base + 1) as f32,
172        );
173        let gradient_var_1 = (RandomField::new(0).get(plot_center.with_z(base)) % 8) as i32;
174        let gradient_var_2 = (RandomField::new(0).get(plot_center.with_z(base + 1)) % 10) as i32;
175
176        let brick = Fill::Gradient(
177            util::gradient::Gradient::new(
178                gradient_center,
179                8.0 + gradient_var_1 as f32,
180                util::gradient::Shape::Point,
181                (surface_color, sub_surface_color),
182            )
183            .with_repeat(if gradient_var_2 > 5 {
184                WrapMode::Repeat
185            } else {
186                WrapMode::PingPong
187            }),
188            BlockKind::Rock,
189        );
190
191        let wood = Fill::Brick(BlockKind::Wood, Rgb::new(106, 83, 51), 12);
192        let color = Fill::Block(Block::air(SpriteKind::CliffDecorBlock));
193        let window = Fill::Block(Block::air(SpriteKind::WindowArabic));
194        let window2 = Fill::Block(Block::air(SpriteKind::WindowArabic).with_ori(2).unwrap());
195        let rope = Fill::Block(Block::air(SpriteKind::Rope));
196
197        let tube_var = (RandomField::new(0).get(plot_center.with_z(base)) % 6) as i32;
198        let radius = 10.0 + tube_var as f32;
199        let tubes = 3.0 + tube_var as f32;
200        let phi = TAU / tubes;
201        for n in 1..=tubes as i32 {
202            let center = Vec2::new(
203                plot_center.x + (radius * ((n as f32 * phi).cos())) as i32,
204                plot_center.y + (radius * ((n as f32 * phi).sin())) as i32,
205            );
206            // common superquadric degree for rooms
207            let sq_type = 3.5;
208            let storeys = self.storeys;
209            let variant = self.variant;
210            let mut length = 16 + (variant / 2);
211            let mut width = 7 * length / 8;
212            let mut height = 18 + variant / 2;
213            let mut floor_level = self.alt - height + 1;
214            let platform_length = self.platform_length;
215            let mut ground_entries = 0;
216            for s in 0..storeys {
217                let x_offset =
218                    (RandomField::new(0).get((center - length).with_z(base)) % 10) as i32;
219                let y_offset =
220                    (RandomField::new(0).get((center + length).with_z(base)) % 10) as i32;
221                let super_center =
222                    Vec2::new(center.x - 3 + x_offset / 2, center.y - 3 + y_offset / 2);
223                // CliffTower Hoodoo Overlay
224                painter
225                    .cubic_bezier(
226                        super_center.with_z(floor_level + (height / 2)),
227                        (super_center - x_offset).with_z(floor_level + height),
228                        (super_center - y_offset).with_z(floor_level + (height) + (height / 2)),
229                        super_center.with_z(floor_level + (2 * height)),
230                        (length - 1) as f32,
231                    )
232                    .fill(brick.clone());
233                if s == (storeys - 1) {
234                    for dir in LOCALITY {
235                        let cone_pos = super_center + (dir * 2);
236                        let cone_var =
237                            4 + (RandomField::new(0).get(cone_pos.with_z(base)) % 4) as i32;
238                        painter
239                            .cone_with_radius(
240                                cone_pos.with_z(floor_level + (2 * height) + 5),
241                                (length / 2) as f32,
242                                (length + cone_var) as f32,
243                            )
244                            .fill(brick.clone());
245                    }
246                }
247                // center tube with  rooms
248                if n == tubes as i32 {
249                    // ground_entries
250                    if ground_entries < 1 && floor_level > (base - 6) {
251                        for dir in CARDINALS {
252                            let entry_pos_inner = plot_center + (dir * (2 * length) - 4);
253                            let entry_pos_outer = plot_center + (dir * (3 * length) + 4);
254                            painter
255                                .line(
256                                    entry_pos_inner.with_z(floor_level + 6),
257                                    entry_pos_outer.with_z(base + 35),
258                                    6.0,
259                                )
260                                .clear();
261                        }
262                        let door_start = plot_center + door_dir * ((3 * (length / 2)) + 1);
263                        painter
264                            .line(
265                                door_start.with_z(floor_level + 2),
266                                self.door_tile.with_z(base),
267                                4.0,
268                            )
269                            .fill(wood.clone());
270                        painter
271                            .line(
272                                door_start.with_z(floor_level + 7),
273                                self.door_tile.with_z(base + 6),
274                                7.0,
275                            )
276                            .clear();
277                        ground_entries += 1;
278                    }
279                    painter
280                        .cubic_bezier(
281                            plot_center.with_z(floor_level + (height / 2)),
282                            (plot_center - x_offset).with_z(floor_level + height),
283                            (plot_center - y_offset).with_z(floor_level + (height) + (height / 2)),
284                            plot_center.with_z(floor_level + (2 * height)),
285                            (length + 2) as f32,
286                        )
287                        .fill(brick.clone());
288                    // platform
289                    if s == (storeys - 1) {
290                        let limit_up = painter.aabb(Aabb {
291                            min: (plot_center - platform_length - 2).with_z(floor_level - 4),
292                            max: (plot_center + platform_length + 2).with_z(floor_level + 1),
293                        });
294                        painter
295                            .superquadric(
296                                Aabb {
297                                    min: (plot_center - platform_length - 2)
298                                        .with_z(floor_level - 4),
299                                    max: (plot_center + platform_length + 2)
300                                        .with_z(floor_level + 6),
301                                },
302                                4.0,
303                            )
304                            .intersect(limit_up)
305                            .fill(wood.clone());
306
307                        // Travel Agent desk
308                        let rotation = -f32::atan2(door_dir.x as f32, door_dir.y as f32);
309                        painter
310                            .aabb(Aabb {
311                                min: Vec2::new(plot_center.x - 5, plot_center.y - 5)
312                                    .with_z(floor_level + 1),
313                                max: Vec2::new(plot_center.x - 10, plot_center.y - 10)
314                                    .with_z(floor_level + 6),
315                            })
316                            .rotate_about(
317                                Mat3::rotation_z(rotation).as_(),
318                                plot_center.with_z(base),
319                            )
320                            .clear();
321                        painter
322                            .line(
323                                Vec2::new(plot_center.x - 6, plot_center.y - 9)
324                                    .with_z(floor_level + 1),
325                                Vec2::new(plot_center.x - 9, plot_center.y - 6)
326                                    .with_z(floor_level + 1),
327                                0.5,
328                            )
329                            .rotate_about(
330                                Mat3::rotation_z(rotation).as_(),
331                                plot_center.with_z(base),
332                            )
333                            .fill(brick.clone());
334                        painter
335                            .line(
336                                Vec2::new(plot_center.x - 10, plot_center.y - 10)
337                                    .with_z(floor_level + 1),
338                                Vec2::new(plot_center.x - 10, plot_center.y - 10)
339                                    .with_z(floor_level + 6),
340                                0.5,
341                            )
342                            .rotate_about(
343                                Mat3::rotation_z(rotation).as_(),
344                                plot_center.with_z(base),
345                            )
346                            .fill(brick.clone());
347                        let agent_lantern_pos = Vec2::new(plot_center.x - 9, plot_center.y - 9);
348                        let agent_lantern_pos_rotated = (plot_center.map(|i| i as f32)
349                            + Mat2::rotation_z(rotation)
350                                * (agent_lantern_pos - plot_center).map(|i| i as f32))
351                        .map(|f| f.round() as i32);
352                        painter.sprite(
353                            agent_lantern_pos_rotated.with_z(floor_level + 5),
354                            SpriteKind::WallLampMesa,
355                        );
356
357                        // lanterns & cargo
358                        for dir in NEIGHBORS {
359                            let lantern_pos = plot_center + (dir * (platform_length - 6));
360
361                            painter.sprite(
362                                lantern_pos.with_z(floor_level + 1),
363                                SpriteKind::StreetLamp,
364                            );
365                        }
366                        for dir in DIAGONALS {
367                            let cargo_pos = plot_center + (dir * (2 * length));
368
369                            for dir in CARDINALS {
370                                let sprite_pos = cargo_pos + dir;
371                                let rows =
372                                    (RandomField::new(0).get(sprite_pos.with_z(base)) % 3) as i32;
373                                for r in 0..rows {
374                                    painter
375                                        .aabb(Aabb {
376                                            min: (sprite_pos).with_z(floor_level + 1 + r),
377                                            max: (sprite_pos + 1).with_z(floor_level + 2 + r),
378                                        })
379                                        .fill(Fill::Block(Block::air(
380                                            match (RandomField::new(0)
381                                                .get(sprite_pos.with_z(base + r))
382                                                % 2)
383                                                as i32
384                                            {
385                                                0 => SpriteKind::Barrel,
386                                                _ => SpriteKind::CrateBlock,
387                                            },
388                                        )));
389                                    if r > 0 {
390                                        painter.owned_resource_sprite(
391                                            sprite_pos.with_z(floor_level + 2 + r),
392                                            SpriteKind::Crate,
393                                            0,
394                                        );
395                                    }
396                                }
397                            }
398                        }
399                        for dir in CARDINALS {
400                            // docks
401                            let dock_pos = plot_center + (dir * platform_length);
402
403                            painter
404                                .cylinder(Aabb {
405                                    min: (dock_pos - 8).with_z(floor_level),
406                                    max: (dock_pos + 8).with_z(floor_level + 1),
407                                })
408                                .fill(wood.clone());
409                            painter
410                                .cylinder(Aabb {
411                                    min: (dock_pos - 7).with_z(floor_level - 1),
412                                    max: (dock_pos + 7).with_z(floor_level),
413                                })
414                                .fill(wood.clone());
415                        }
416                        // campfire
417                        let campfire_pos =
418                            Vec2::new(plot_center.x - platform_length - 2, plot_center.y)
419                                .with_z(floor_level);
420                        painter.spawn(
421                            EntityInfo::at(campfire_pos.map(|e| e as f32 + 0.5))
422                                .into_special(SpecialEntity::Waypoint),
423                        );
424                    }
425
426                    // clear rooms and entries & decor
427                    if floor_level > (base - 6) {
428                        // decor
429                        painter
430                            .line(
431                                Vec2::new(plot_center.x, plot_center.y - length)
432                                    .with_z(floor_level + 5),
433                                Vec2::new(plot_center.x, plot_center.y + length)
434                                    .with_z(floor_level + 5),
435                                4.0,
436                            )
437                            .fill(color.clone());
438                        painter
439                            .line(
440                                Vec2::new(plot_center.x - length, plot_center.y)
441                                    .with_z(floor_level + 5),
442                                Vec2::new(plot_center.x + length, plot_center.y)
443                                    .with_z(floor_level + 5),
444                                4.0,
445                            )
446                            .fill(color.clone());
447                        // entries
448                        painter
449                            .line(
450                                Vec2::new(plot_center.x, plot_center.y - (2 * length) - 4)
451                                    .with_z(floor_level + 4),
452                                Vec2::new(plot_center.x, plot_center.y + (2 * length) + 4)
453                                    .with_z(floor_level + 4),
454                                4.0,
455                            )
456                            .clear();
457                        painter
458                            .line(
459                                Vec2::new(plot_center.x - (2 * length) - 4, plot_center.y)
460                                    .with_z(floor_level + 4),
461                                Vec2::new(plot_center.x + (2 * length) + 4, plot_center.y)
462                                    .with_z(floor_level + 4),
463                                4.0,
464                            )
465                            .clear();
466                        painter
467                            .superquadric(
468                                Aabb {
469                                    min: (plot_center - length - 1).with_z(floor_level),
470                                    max: (plot_center + length + 1)
471                                        .with_z(floor_level + height - 4),
472                                },
473                                sq_type,
474                            )
475                            .clear();
476                        // room floor
477                        painter
478                            .cylinder(Aabb {
479                                min: (plot_center - length - 3).with_z(floor_level),
480                                max: (plot_center + length + 3).with_z(floor_level + 1),
481                            })
482                            .fill(brick.clone());
483                        painter
484                            .cylinder(Aabb {
485                                min: (plot_center - length + 1).with_z(floor_level),
486                                max: (plot_center + length - 1).with_z(floor_level + 1),
487                            })
488                            .fill(color.clone());
489                        painter
490                            .cylinder(Aabb {
491                                min: (plot_center - length + 2).with_z(floor_level),
492                                max: (plot_center + length - 2).with_z(floor_level + 1),
493                            })
494                            .fill(brick.clone());
495                        // entry sprites
496                        painter
497                            .aabb(Aabb {
498                                min: Vec2::new(plot_center.x - 3, plot_center.y + length)
499                                    .with_z(floor_level + 2),
500                                max: Vec2::new(plot_center.x + 4, plot_center.y + length + 1)
501                                    .with_z(floor_level + 7),
502                            })
503                            .fill(window2.clone());
504                        painter
505                            .aabb(Aabb {
506                                min: Vec2::new(plot_center.x - 2, plot_center.y + length)
507                                    .with_z(floor_level + 2),
508                                max: Vec2::new(plot_center.x + 3, plot_center.y + length + 1)
509                                    .with_z(floor_level + 7),
510                            })
511                            .clear();
512
513                        painter
514                            .aabb(Aabb {
515                                min: Vec2::new(plot_center.x - 3, plot_center.y - length - 1)
516                                    .with_z(floor_level + 2),
517                                max: Vec2::new(plot_center.x + 4, plot_center.y - length)
518                                    .with_z(floor_level + 7),
519                            })
520                            .fill(window2.clone());
521                        painter
522                            .aabb(Aabb {
523                                min: Vec2::new(plot_center.x - 2, plot_center.y - length - 1)
524                                    .with_z(floor_level + 2),
525                                max: Vec2::new(plot_center.x + 3, plot_center.y - length)
526                                    .with_z(floor_level + 7),
527                            })
528                            .clear();
529                        painter
530                            .aabb(Aabb {
531                                min: Vec2::new(plot_center.x + length, plot_center.y - 3)
532                                    .with_z(floor_level + 2),
533                                max: Vec2::new(plot_center.x + length + 1, plot_center.y + 4)
534                                    .with_z(floor_level + 7),
535                            })
536                            .fill(window.clone());
537                        painter
538                            .aabb(Aabb {
539                                min: Vec2::new(plot_center.x + length, plot_center.y - 2)
540                                    .with_z(floor_level + 2),
541                                max: Vec2::new(plot_center.x + length + 1, plot_center.y + 3)
542                                    .with_z(floor_level + 7),
543                            })
544                            .clear();
545
546                        painter
547                            .aabb(Aabb {
548                                min: Vec2::new(plot_center.x - length - 1, plot_center.y - 3)
549                                    .with_z(floor_level + 2),
550                                max: Vec2::new(plot_center.x - length, plot_center.y + 4)
551                                    .with_z(floor_level + 7),
552                            })
553                            .fill(window.clone());
554                        painter
555                            .aabb(Aabb {
556                                min: Vec2::new(plot_center.x - length - 1, plot_center.y - 2)
557                                    .with_z(floor_level + 2),
558                                max: Vec2::new(plot_center.x - length, plot_center.y + 3)
559                                    .with_z(floor_level + 7),
560                            })
561                            .clear();
562                        // cargo in rooms
563                        for dir in DIAGONALS {
564                            let cargo_pos = plot_center + (dir * (length / 2));
565                            for dir in CARDINALS {
566                                let sprite_pos = cargo_pos + dir;
567                                let rows =
568                                    (RandomField::new(0).get(sprite_pos.with_z(base)) % 4) as i32;
569                                for r in 0..rows {
570                                    painter
571                                        .aabb(Aabb {
572                                            min: (sprite_pos).with_z(floor_level + 1 + r),
573                                            max: (sprite_pos + 1).with_z(floor_level + 2 + r),
574                                        })
575                                        .fill(Fill::Block(Block::air(
576                                            match (RandomField::new(0)
577                                                .get(sprite_pos.with_z(base + r))
578                                                % 2)
579                                                as i32
580                                            {
581                                                0 => SpriteKind::Barrel,
582                                                _ => SpriteKind::CrateBlock,
583                                            },
584                                        )));
585                                }
586                            }
587                        }
588
589                        // wall lamps
590                        let corner_pos_1 = Vec2::new(plot_center.x - length, plot_center.y - 5);
591                        let corner_pos_2 = Vec2::new(plot_center.x - 5, plot_center.y - length);
592                        for dir in SQUARE_4 {
593                            let lamp_pos_1 = Vec2::new(
594                                corner_pos_1.x + (dir.x * ((2 * length) - 1)),
595                                corner_pos_1.y + (dir.y * 10),
596                            )
597                            .with_z(floor_level + 7);
598                            painter.rotated_sprite(
599                                lamp_pos_1,
600                                SpriteKind::WallLampMesa,
601                                (2 + (4 * dir.x)) as u8,
602                            );
603                            let lamp_pos_2 = Vec2::new(
604                                corner_pos_2.x + (dir.x * 10),
605                                corner_pos_2.y + (dir.y * ((2 * length) - 1)),
606                            )
607                            .with_z(floor_level + 7);
608                            painter.rotated_sprite(
609                                lamp_pos_2,
610                                SpriteKind::WallLampMesa,
611                                (4 - (4 * dir.y)) as u8,
612                            );
613                        }
614                    }
615                    // stairs
616                    if floor_level > (base + 8) {
617                        let stairs_level = floor_level + 1;
618                        let stairs_start = plot_center + door_dir * ((2 * length) - 7);
619                        let mid_dir = if door_dir.x != 0 {
620                            door_dir.x
621                        } else {
622                            door_dir.y
623                        };
624                        let stairs_mid = Vec2::new(
625                            plot_center.x + mid_dir * (3 * (length / 2)),
626                            plot_center.y + mid_dir * (3 * (length / 2)),
627                        );
628                        let stairs_end = Vec2::new(
629                            plot_center.x + door_dir.y * ((2 * length) - 7),
630                            plot_center.y + door_dir.x * ((2 * length) - 7),
631                        );
632                        let rope_pos = Vec2::new(
633                            plot_center.x + mid_dir * ((3 * (length / 2)) + 2),
634                            plot_center.y + mid_dir * ((3 * (length / 2)) + 2),
635                        );
636
637                        painter
638                            .cylinder(Aabb {
639                                min: (stairs_start - 6).with_z(stairs_level - 1),
640                                max: (stairs_start + 6).with_z(stairs_level),
641                            })
642                            .fill(wood.clone());
643
644                        painter
645                            .cylinder(Aabb {
646                                min: (stairs_mid - 6).with_z(stairs_level - (height / 2) - 1),
647                                max: (stairs_mid + 6).with_z(stairs_level - (height / 2)),
648                            })
649                            .fill(wood.clone());
650
651                        painter
652                            .cylinder(Aabb {
653                                min: (stairs_end - 6).with_z(stairs_level - height - 1),
654                                max: (stairs_end + 6).with_z(stairs_level - height),
655                            })
656                            .fill(wood.clone());
657
658                        for n in 0..2 {
659                            let stairs = painter
660                                .line(
661                                    stairs_start.with_z(stairs_level + (n * 2)),
662                                    stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
663                                    4.0 + (n as f32 / 2.0),
664                                )
665                                .union(painter.line(
666                                    stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
667                                    stairs_end.with_z(stairs_level - height + (n * 2)),
668                                    4.0 + (n as f32 / 2.0),
669                                ));
670                            match n {
671                                0 => stairs.fill(wood.clone()),
672                                _ => stairs.clear(),
673                            };
674                        }
675                        painter
676                            .line(
677                                rope_pos.with_z(stairs_level + (height / 2) - 3),
678                                (plot_center - (length / 2))
679                                    .with_z(stairs_level + (height / 2) + 2),
680                                1.5,
681                            )
682                            .fill(wood.clone());
683
684                        painter
685                            .aabb(Aabb {
686                                min: rope_pos.with_z(stairs_level - (height / 2) - 1),
687                                max: (rope_pos + 1).with_z(stairs_level + (height / 2) - 3),
688                            })
689                            .fill(rope.clone());
690                    }
691                }
692                // vary next storey
693                length += -1;
694                width += -1;
695                height += -1;
696                floor_level += height;
697                mem::swap(&mut length, &mut width);
698            }
699        }
700    }
701}