veloren_world/site/plot/
gnarling.rs

1use super::*;
2use crate::{
3    Land,
4    assets::AssetHandle,
5    site::{generation::PrimitiveTransform, util::Dir},
6    util::{RandomField, attempt, sampler::Sampler, within_distance},
7};
8use common::{
9    generation::{ChunkSupplement, EntityInfo, EntitySpawn},
10    terrain::{Structure as PrefabStructure, StructuresGroup},
11};
12use kiddo::{SquaredEuclidean, float::kdtree::KdTree};
13use lazy_static::lazy_static;
14use rand::prelude::*;
15use std::collections::HashMap;
16use vek::*;
17
18pub struct GnarlingFortification {
19    name: String,
20    seed: u32,
21    origin: Vec2<i32>,
22    radius: i32,
23    wall_radius: i32,
24    wall_segments: Vec<(Vec2<i32>, Vec2<i32>)>,
25    wall_towers: Vec<Vec3<i32>>,
26    // Structure indicates the kind of structure it is, vec2 is relative position of a hut compared
27    // to origin, ori tells which way structure should face
28    structure_locations: Vec<(GnarlingStructure, Vec3<i32>, Dir)>,
29    tunnels: Tunnels,
30}
31
32enum GnarlingStructure {
33    Hut,
34    VeloriteHut,
35    Totem,
36    ChieftainHut,
37    WatchTower,
38    Banner,
39}
40
41impl GnarlingStructure {
42    fn required_separation(&self, other: &Self) -> i32 {
43        let radius = |structure: &Self| match structure {
44            Self::Hut => 7,
45            Self::VeloriteHut => 15,
46            Self::Banner => 6,
47            Self::Totem => 6,
48            // Generated in different pass that doesn't use distance check
49            Self::WatchTower => 0,
50            Self::ChieftainHut => 0,
51        };
52
53        let additional_padding = match (self, other) {
54            (Self::Banner, Self::Banner) => 50,
55            (Self::Totem, Self::Totem) => 50,
56            (Self::VeloriteHut, Self::VeloriteHut) => 50,
57            _ => 0,
58        };
59
60        radius(self) + radius(other) + additional_padding
61    }
62}
63
64impl GnarlingFortification {
65    pub fn generate(wpos: Vec2<i32>, land: &Land, rng: &mut impl Rng) -> Self {
66        let rpos_height = |rpos| land.get_alt_approx(rpos + wpos) as i32;
67
68        let name = NameGen::location(rng).generate_gnarling();
69        let seed = rng.random();
70        let origin = wpos;
71
72        let wall_radius = {
73            let unit_size = rng.random_range(10..20);
74            let num_units = rng.random_range(5..10);
75            let variation = rng.random_range(0..50);
76            unit_size * num_units + variation
77        };
78
79        let radius = wall_radius + 50;
80
81        // Tunnels
82        let alt = land.get_alt_approx(wpos) as i32;
83        let start = wpos.with_z(alt);
84        let boss_room_shift = rng.random_range(60..110);
85        let end_xy = match rng.random_range(0..4) {
86            0 => Vec2::new(start.x + boss_room_shift, start.y + boss_room_shift),
87            1 => Vec2::new(start.x - boss_room_shift, start.y + boss_room_shift),
88            2 => Vec2::new(start.x - boss_room_shift, start.y - boss_room_shift),
89            3 => Vec2::new(start.x + boss_room_shift, start.y - boss_room_shift),
90            // Unreachable
91            _ => unreachable!(),
92        };
93
94        let is_underground = |pos: Vec3<i32>| land.get_alt_approx(pos.xy()) as i32 - 9 > pos.z;
95        let is_valid_edge = |p1: Vec3<i32>, p2: Vec3<i32>| {
96            let diff = p1 - p2;
97            // Check that the new point is underground and the slope is mildish
98            is_underground(p2) && (diff.z.pow(2) / diff.xy().magnitude_squared().max(1)) < 3
99        };
100        let mut end = end_xy.with_z(start.z - 20);
101        for i in 1..31 {
102            let new_z = start.z - (i * 20);
103            if is_underground(end_xy.with_z(new_z + 35)) {
104                end = end_xy.with_z(new_z);
105                break;
106            }
107        }
108
109        let tunnel_length_range = (12.0, 27.0);
110        let tunnels =
111            Tunnels::new(start, end, is_valid_edge, tunnel_length_range, rng).unwrap_or_default();
112
113        let num_points = (wall_radius / 15).max(5);
114        let outer_wall_corners = (0..num_points)
115            .map(|a| {
116                let angle = a as f32 / num_points as f32 * core::f32::consts::TAU;
117                Vec2::new(angle.cos(), angle.sin()).map(|a| (a * wall_radius as f32) as i32)
118            })
119            .map(|point| {
120                point.map(|a| {
121                    let variation = wall_radius / 5;
122                    a + rng.random_range(-variation..=variation)
123                })
124            })
125            .collect::<Vec<_>>();
126
127        let gate_index = rng.random_range(0..outer_wall_corners.len());
128
129        let chieftain_indices = {
130            // Will not be adjacent to gate and needs two sections, so subtract 4 (3 to get
131            // rid of gate and adjacent, 1 to account for taking 2 sections)
132            let chosen = rng.random_range(0..(outer_wall_corners.len() - 4));
133            let index = if gate_index < 2 {
134                chosen + gate_index + 2
135            } else if chosen < (gate_index - 2) {
136                chosen
137            } else {
138                chosen + 4
139            };
140            [index, (index + 1) % outer_wall_corners.len()]
141        };
142
143        let outer_wall_segments = outer_wall_corners
144            .iter()
145            .enumerate()
146            .filter_map(|(i, point)| {
147                if i == gate_index {
148                    None
149                } else {
150                    let next_point = if let Some(point) = outer_wall_corners.get(i + 1) {
151                        *point
152                    } else {
153                        outer_wall_corners[0]
154                    };
155                    Some((*point, next_point))
156                }
157            })
158            .collect::<Vec<_>>();
159
160        // Structures will not spawn in wall corner triangles corresponding to these
161        // indices
162        let forbidden_indices = [gate_index, chieftain_indices[0], chieftain_indices[1]];
163        // Structures will be weighted to spawn further from these indices when
164        // selecting a point in the triangle
165        let restricted_indices = [
166            (chieftain_indices[0] + outer_wall_corners.len() - 1) % outer_wall_corners.len(),
167            (chieftain_indices[1] + 1) % outer_wall_corners.len(),
168        ];
169
170        let desired_structures = wall_radius.pow(2) / 100;
171        let mut structure_locations = Vec::<(GnarlingStructure, Vec3<i32>, Dir)>::new();
172        for _ in 0..desired_structures {
173            if let Some((hut_loc, kind)) = attempt(50, || {
174                // Choose structure kind
175                let structure_kind = match rng.random_range(0..10) {
176                    0 => GnarlingStructure::Totem,
177                    1..=3 => GnarlingStructure::VeloriteHut,
178                    4..=5 => GnarlingStructure::Banner,
179                    _ => GnarlingStructure::Hut,
180                };
181
182                // Choose triangle
183                let corner_1_index = rng.random_range(0..outer_wall_corners.len());
184
185                if forbidden_indices.contains(&corner_1_index) {
186                    return None;
187                }
188
189                let center = Vec2::zero();
190                let corner_1 = outer_wall_corners[corner_1_index];
191                let (corner_2, corner_2_index) =
192                    if let Some(corner) = outer_wall_corners.get(corner_1_index + 1) {
193                        (*corner, corner_1_index + 1)
194                    } else {
195                        (outer_wall_corners[0], 0)
196                    };
197
198                let center_weight: f32 = rng.random_range(0.15..0.7);
199
200                // Forbidden and restricted indices are near walls, so don't spawn structures
201                // too close to avoid overlap with wall
202                let corner_1_weight_range = if restricted_indices.contains(&corner_1_index) {
203                    let limit = 0.75;
204                    if chieftain_indices.contains(&corner_2_index) {
205                        ((1.0 - center_weight) * (1.0 - limit))..(1.0 - center_weight)
206                    } else {
207                        0.0..((1.0 - center_weight) * limit)
208                    }
209                } else {
210                    0.0..(1.0 - center_weight)
211                };
212
213                let corner_1_weight = rng.random_range(corner_1_weight_range);
214                let corner_2_weight = 1.0 - center_weight - corner_1_weight;
215
216                let structure_center: Vec2<i32> = (center * center_weight
217                    + corner_1.as_() * corner_1_weight
218                    + corner_2.as_() * corner_2_weight)
219                    .as_();
220
221                // Check that structure not in the water or too close to another structure
222                if land
223                    .get_chunk_wpos(structure_center + origin)
224                    .is_some_and(|c| c.is_underwater())
225                    || structure_locations.iter().any(|(kind, loc, _door_dir)| {
226                        structure_center.distance_squared(loc.xy())
227                            < structure_kind.required_separation(kind).pow(2)
228                    })
229                {
230                    None
231                } else {
232                    Some((
233                        structure_center.with_z(rpos_height(structure_center)),
234                        structure_kind,
235                    ))
236                }
237            }) {
238                let dir_to_center = Dir::from_vec2(hut_loc.xy()).opposite();
239                let door_rng: u32 = rng.random_range(0..9);
240                let door_dir = match door_rng {
241                    0..=3 => dir_to_center,
242                    4..=5 => dir_to_center.rotated_cw(),
243                    6..=7 => dir_to_center.rotated_ccw(),
244                    // Should only be 8
245                    _ => dir_to_center.opposite(),
246                };
247                structure_locations.push((kind, hut_loc, door_dir));
248            }
249        }
250
251        let wall_connections = [
252            outer_wall_corners[chieftain_indices[0]],
253            outer_wall_corners[(chieftain_indices[1] + 1) % outer_wall_corners.len()],
254        ];
255        let inner_tower_locs = wall_connections
256            .iter()
257            .map(|corner_pos| *corner_pos / 3)
258            .collect::<Vec<_>>();
259
260        let chieftain_hut_loc = ((inner_tower_locs[0] + inner_tower_locs[1])
261            + 2 * outer_wall_corners[chieftain_indices[1]])
262            / 4;
263        let chieftain_hut_ori = Dir::from_vec2(chieftain_hut_loc).opposite();
264        structure_locations.push((
265            GnarlingStructure::ChieftainHut,
266            chieftain_hut_loc.with_z(rpos_height(chieftain_hut_loc)),
267            chieftain_hut_ori,
268        ));
269
270        let watchtower_locs = {
271            let (corner_1, corner_2) = (
272                outer_wall_corners[gate_index],
273                outer_wall_corners[(gate_index + 1) % outer_wall_corners.len()],
274            );
275            [
276                corner_1 / 5 + corner_2 * 4 / 5,
277                corner_1 * 4 / 5 + corner_2 / 5,
278            ]
279        };
280        watchtower_locs.iter().for_each(|loc| {
281            structure_locations.push((
282                GnarlingStructure::WatchTower,
283                loc.with_z(rpos_height(*loc)),
284                Dir::Y,
285            ));
286        });
287
288        let wall_towers = outer_wall_corners
289            .into_iter()
290            .chain(inner_tower_locs.iter().copied())
291            .map(|pos_2d| pos_2d.with_z(rpos_height(pos_2d)))
292            .collect::<Vec<_>>();
293        let wall_segments = outer_wall_segments
294            .into_iter()
295            .chain(wall_connections.iter().copied().zip(inner_tower_locs))
296            .collect::<Vec<_>>();
297
298        Self {
299            name,
300            seed,
301            origin,
302            radius,
303            wall_radius,
304            wall_towers,
305            wall_segments,
306            structure_locations,
307            tunnels,
308        }
309    }
310
311    pub fn name(&self) -> &str { &self.name }
312
313    pub fn radius(&self) -> i32 { self.radius }
314
315    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
316        SpawnRules {
317            trees: !within_distance(wpos, self.origin, self.wall_radius),
318            waypoints: false,
319            ..SpawnRules::default()
320        }
321    }
322
323    // TODO: Find a better way of spawning entities in site
324    pub fn apply_supplement(
325        &self,
326        // NOTE: Used only for dynamic elements like chests and entities!
327        dynamic_rng: &mut impl Rng,
328        wpos2d: Vec2<i32>,
329        supplement: &mut ChunkSupplement,
330    ) {
331        let rpos = wpos2d - self.origin;
332        let area = Aabr {
333            min: rpos,
334            max: rpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
335        };
336
337        // tunnel junctions
338        for terminal in &self.tunnels.terminals {
339            if area.contains_point(terminal.xy() - self.origin) {
340                let chance = dynamic_rng.random_range(0..10);
341                let entities = match chance {
342                    0..=4 => vec![mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng)],
343                    5 => vec![
344                        mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng),
345                        mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng),
346                    ],
347                    6 => vec![deadwood(*terminal - 5 * Vec3::unit_z(), dynamic_rng)],
348                    7 => vec![
349                        mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng),
350                        deadwood(*terminal - 5 * Vec3::unit_z(), dynamic_rng),
351                    ],
352                    8 => vec![
353                        mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng),
354                        mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng),
355                        mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng),
356                    ],
357                    _ => Vec::new(),
358                };
359                for entity in entities {
360                    supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(entity)))
361                }
362            }
363        }
364
365        // harvester room
366        if area.contains_point(self.tunnels.end.xy() - self.origin) {
367            let boss_room_offset = (self.tunnels.end.xy() - self.tunnels.start.xy())
368                .map(|e| if e < 0 { -20 } else { 20 });
369            supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(harvester_boss(
370                self.tunnels.end + boss_room_offset - 2 * Vec3::unit_z(),
371                dynamic_rng,
372            ))));
373        }
374
375        // above-ground structures
376        for (loc, pos, _ori) in &self.structure_locations {
377            let wpos = *pos + self.origin;
378            if area.contains_point(pos.xy()) {
379                match loc {
380                    GnarlingStructure::Hut => {
381                        const NUM_HUT_GNARLINGS: [i32; 2] = [1, 2];
382                        let num =
383                            dynamic_rng.random_range(NUM_HUT_GNARLINGS[0]..=NUM_HUT_GNARLINGS[1]);
384                        for _ in 1..=num {
385                            supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(
386                                random_gnarling(wpos, dynamic_rng),
387                            )));
388                        }
389                    },
390                    GnarlingStructure::VeloriteHut => {
391                        const NUM_VELO_GNARLINGS: i32 = 4;
392                        const GOLEM_SPAWN_THRESHOLD: i32 = 2;
393                        const VELO_HEIGHT: i32 = 12;
394                        const GROUND_HEIGHT: i32 = 8;
395                        let num = dynamic_rng.random_range(1..=NUM_VELO_GNARLINGS);
396                        for _ in 1..=num {
397                            supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(
398                                random_gnarling(
399                                    wpos.xy().with_z(wpos.z + VELO_HEIGHT),
400                                    dynamic_rng,
401                                ),
402                            )));
403                        }
404                        if num <= GOLEM_SPAWN_THRESHOLD {
405                            // wooden golem (with oriented spawn)
406                            let x_offset;
407                            let y_offset;
408                            match _ori {
409                                Dir::X => {
410                                    x_offset = 8;
411                                    y_offset = 8;
412                                },
413                                Dir::NegX => {
414                                    x_offset = -8;
415                                    y_offset = 8;
416                                },
417                                Dir::Y => {
418                                    x_offset = 8;
419                                    y_offset = -8;
420                                },
421                                Dir::NegY => {
422                                    x_offset = -8;
423                                    y_offset = -8;
424                                },
425                            }
426                            let pos = Vec3::new(
427                                wpos.x + x_offset,
428                                wpos.y + y_offset,
429                                wpos.z + GROUND_HEIGHT,
430                            );
431                            supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(wood_golem(
432                                pos,
433                                dynamic_rng,
434                            ))));
435                        }
436                    },
437                    GnarlingStructure::Banner => {},
438                    GnarlingStructure::ChieftainHut => {
439                        // inside hut
440                        const FLOOR_HEIGHT: i32 = 8;
441                        let pos = wpos.xy().with_z(wpos.z + FLOOR_HEIGHT);
442                        supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(
443                            gnarling_chieftain(pos, dynamic_rng),
444                        )));
445                        supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(
446                            gnarling_logger(pos, dynamic_rng),
447                        )));
448                        supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(
449                            gnarling_mugger(pos, dynamic_rng),
450                        )));
451                        supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(
452                            gnarling_stalker(pos, dynamic_rng),
453                        )));
454                        // hut corner posts
455                        const CORNER_HEIGHT: i32 = 10;
456                        const CORNER_OFFSET: i32 = 18;
457                        let height = wpos.z + CORNER_HEIGHT;
458                        let plus_minus: [i32; 2] = [1, -1];
459                        for x in plus_minus {
460                            for y in plus_minus {
461                                let pos = Vec3::new(
462                                    wpos.x + x * CORNER_OFFSET,
463                                    wpos.y + y * CORNER_OFFSET,
464                                    height,
465                                );
466                                supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(
467                                    gnarling_stalker(pos, dynamic_rng),
468                                )));
469                            }
470                        }
471                        // hut sides on ground (using orientation)
472                        const NUM_SIDE_GNARLINGS: i32 = 2;
473                        const GROUND_HEIGHT: i32 = 4;
474                        const GROUND_OFFSET: i32 = 24;
475                        let height = wpos.z + GROUND_HEIGHT;
476                        let x_or_y = match _ori {
477                            Dir::X | Dir::NegX => true,
478                            Dir::Y | Dir::NegY => false,
479                        };
480                        for pm in plus_minus {
481                            let mut pos_ori =
482                                Vec3::new(wpos.x + pm * GROUND_OFFSET, wpos.y, height);
483                            let mut pos_xori =
484                                Vec3::new(wpos.x, wpos.y + pm * GROUND_OFFSET, height);
485                            if x_or_y {
486                                (pos_ori, pos_xori) = (pos_xori, pos_ori);
487                            }
488                            supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(wood_golem(
489                                pos_ori,
490                                dynamic_rng,
491                            ))));
492                            for _ in 1..=NUM_SIDE_GNARLINGS {
493                                supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(
494                                    melee_gnarling(pos_xori, dynamic_rng),
495                                )));
496                            }
497                        }
498                    },
499                    GnarlingStructure::WatchTower => {
500                        const NUM_WATCHTOWER_STALKERS: i32 = 2;
501                        const FLOOR_HEIGHT: i32 = 27;
502                        supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(wood_golem(
503                            wpos,
504                            dynamic_rng,
505                        ))));
506                        let spawn_pos = wpos.xy().with_z(wpos.z + FLOOR_HEIGHT);
507                        for _ in 1..=NUM_WATCHTOWER_STALKERS {
508                            supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(
509                                gnarling_stalker(spawn_pos + Vec2::broadcast(4), dynamic_rng),
510                            )));
511                        }
512                    },
513                    GnarlingStructure::Totem => {},
514                }
515            }
516        }
517
518        // wall towers
519        for pos in &self.wall_towers {
520            const NUM_WALLTOWER_STALKERS: [i32; 2] = [1, 3];
521            const FLOOR_HEIGHT: i32 = 27;
522            let wpos = *pos + self.origin;
523            if area.contains_point(pos.xy()) {
524                let num =
525                    dynamic_rng.random_range(NUM_WALLTOWER_STALKERS[0]..=NUM_WALLTOWER_STALKERS[1]);
526                for _ in 1..=num {
527                    supplement.add_entity_spawn(EntitySpawn::Entity(Box::new(gnarling_stalker(
528                        wpos.xy().with_z(wpos.z + FLOOR_HEIGHT),
529                        dynamic_rng,
530                    ))))
531                }
532            }
533        }
534    }
535}
536
537impl Structure for GnarlingFortification {
538    #[cfg(feature = "use-dyn-lib")]
539    const UPDATE_FN: &'static [u8] = b"render_gnarlingfortification\0";
540
541    #[cfg_attr(
542        feature = "be-dyn-lib",
543        unsafe(export_name = "render_gnarlingfortification")
544    )]
545    fn render_inner(&self, _site: &Site, land: &Land, painter: &Painter) {
546        // Create outer wall
547        for (point, next_point) in self.wall_segments.iter() {
548            // This adds additional points for the wall on the line between two points,
549            // allowing the wall to better handle slopes
550            const SECTIONS_PER_WALL_SEGMENT: usize = 8;
551
552            (0..(SECTIONS_PER_WALL_SEGMENT as i32))
553                .map(move |a| {
554                    let get_point =
555                        |a| point + (next_point - point) * a / (SECTIONS_PER_WALL_SEGMENT as i32);
556                    (get_point(a), get_point(a + 1))
557                })
558                .for_each(|(point, next_point)| {
559                    // 2d world positions of each point in wall segment
560                    let start_wpos = point + self.origin;
561                    let end_wpos = next_point + self.origin;
562
563                    let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12);
564                    let lightwood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12);
565                    let moss = Fill::Brick(BlockKind::Wood, Rgb::new(22, 36, 20), 24);
566
567                    let start = (start_wpos + 2)
568                        .as_()
569                        .with_z(land.get_alt_approx(start_wpos) + 0.0);
570                    let end = (end_wpos + 2)
571                        .as_()
572                        .with_z(land.get_alt_approx(end_wpos) + 0.0);
573                    let randstart = start % 10.0 - 5.;
574                    let randend = end % 10.0 - 5.0;
575                    let mid = (start + end) / 2.0;
576                    let startshift = Vec3::new(
577                        randstart.x * 5.0,
578                        randstart.y * 5.0,
579                        randstart.z * 1.0 + 5.0,
580                    );
581                    let endshift =
582                        Vec3::new(randend.x * 5.0, randend.y * 5.0, randend.z * 1.0 + 5.0);
583
584                    let mossroot =
585                        painter.cubic_bezier(start, mid + startshift, mid + endshift, end, 3.0);
586
587                    let start = start_wpos
588                        .as_()
589                        .with_z(land.get_alt_approx(start_wpos) - 2.0);
590                    let end = end_wpos.as_().with_z(land.get_alt_approx(end_wpos) - 2.0);
591                    let randstart = start % 10.0 - 5.;
592                    let randend = end % 10.0 - 5.0;
593                    let mid = (start + end) / 2.0;
594                    let startshift =
595                        Vec3::new(randstart.x * 2.0, randstart.y * 2.0, randstart.z * 0.5);
596                    let endshift = Vec3::new(randend.x * 2.0, randend.y * 2.0, randend.z * 0.5);
597
598                    let root1 =
599                        painter.cubic_bezier(start, mid + startshift, mid + endshift, end, 5.0);
600
601                    let mosstop1 = root1.translate(Vec3::new(0, 0, 1));
602
603                    root1.fill(darkwood.clone());
604
605                    let start = (start_wpos + 3)
606                        .as_()
607                        .with_z(land.get_alt_approx(start_wpos) + 0.0);
608                    let end = (end_wpos + 3)
609                        .as_()
610                        .with_z(land.get_alt_approx(end_wpos) + 0.0);
611                    let randstart = start % 10.0 - 5.;
612                    let randend = end % 10.0 - 5.0;
613                    let mid = (start + end) / 2.0;
614                    let startshift = Vec3::new(
615                        randstart.x * 3.0,
616                        randstart.y * 3.0,
617                        randstart.z * 2.0 + 5.0,
618                    );
619                    let endshift =
620                        Vec3::new(randend.x * 3.0, randend.y * 3.0, randend.z * 2.0 + 5.0);
621                    let root2 =
622                        painter.cubic_bezier(start, mid + startshift, mid + endshift, end, 2.0);
623
624                    let mosstop2 = root2.translate(Vec3::new(0, 0, 1));
625                    let start = start_wpos.as_().with_z(land.get_alt_approx(start_wpos));
626                    let end = end_wpos.as_().with_z(land.get_alt_approx(end_wpos));
627
628                    let wall_base_height = 3.0;
629                    let wall_mid_thickness = 1.0;
630                    let wall_mid_height = 7.0 + wall_base_height;
631
632                    painter
633                        .segment_prism(start, end, wall_mid_thickness, wall_mid_height)
634                        .fill(darkwood);
635
636                    let start = start_wpos
637                        .as_()
638                        .with_z(land.get_alt_approx(start_wpos) + wall_mid_height);
639                    let end = end_wpos
640                        .as_()
641                        .with_z(land.get_alt_approx(end_wpos) + wall_mid_height);
642
643                    let wall_top_thickness = 2.0;
644                    let wall_top_height = 1.0;
645
646                    let topwall =
647                        painter.segment_prism(start, end, wall_top_thickness, wall_top_height);
648                    let mosstopwall = topwall.translate(Vec3::new(0, 0, 1));
649
650                    topwall.fill(lightwood.clone());
651
652                    root2.fill(lightwood);
653
654                    mosstopwall
655                        .intersect(mossroot.translate(Vec3::new(0, 0, 8)))
656                        .fill(moss.clone());
657
658                    mosstop1.intersect(mossroot).fill(moss.clone());
659                    mosstop2.intersect(mossroot).fill(moss);
660                })
661        }
662
663        // Create towers
664        self.wall_towers.iter().for_each(|point| {
665            let wpos = point.xy() + self.origin;
666
667            // Tower base
668            let tower_depth = 3;
669            let tower_base_pos = wpos.with_z(land.get_alt_approx(wpos) as i32 - tower_depth);
670            let tower_radius = 5.0;
671            let tower_height = 30.0;
672
673            let randx = wpos.x.abs() % 10;
674            let randy = wpos.y.abs() % 10;
675            let randz = (land.get_alt_approx(wpos) as i32).abs() % 10;
676            //layers of rings, starting at exterior
677            let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12);
678            let lightwood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12);
679            let moss = Fill::Brick(BlockKind::Wood, Rgb::new(22, 36, 20), 24);
680            let red = Fill::Brick(BlockKind::Wood, Rgb::new(102, 31, 2), 12);
681
682            let twist = painter.cubic_bezier(
683                tower_base_pos + Vec3::new(4, 4, 8),
684                tower_base_pos + Vec3::new(-9, 9, 14),
685                tower_base_pos + Vec3::new(-11, -11, 16),
686                tower_base_pos + Vec3::new(4, -4, 21),
687                1.5,
688            );
689            let mosstwist = twist.translate(Vec3::new(0, 0, 1));
690            let mossarea = twist.translate(Vec3::new(1, 0, 1));
691
692            mossarea
693                .intersect(mosstwist)
694                .without(twist)
695                .fill(moss.clone());
696            twist.fill(darkwood.clone());
697
698            let outside = painter
699                .cylinder_with_radius(
700                    wpos.with_z(land.get_alt_approx(wpos) as i32),
701                    tower_radius,
702                    tower_height,
703                )
704                .without(painter.cylinder_with_radius(
705                    wpos.with_z(land.get_alt_approx(wpos) as i32),
706                    tower_radius - 1.0,
707                    tower_height,
708                ));
709            outside.fill(lightwood.clone());
710            painter
711                .cylinder_with_radius(
712                    wpos.with_z(land.get_alt_approx(wpos) as i32),
713                    tower_radius - 1.0,
714                    tower_height,
715                )
716                .fill(darkwood.clone());
717            painter
718                .cylinder_with_radius(
719                    wpos.with_z(land.get_alt_approx(wpos) as i32),
720                    tower_radius - 2.0,
721                    tower_height,
722                )
723                .fill(lightwood.clone());
724            painter
725                .cylinder_with_radius(
726                    wpos.with_z(land.get_alt_approx(wpos) as i32),
727                    tower_radius - 3.0,
728                    tower_height,
729                )
730                .fill(darkwood);
731            painter
732                .cylinder_with_radius(
733                    wpos.with_z(land.get_alt_approx(wpos) as i32),
734                    tower_radius - 4.0,
735                    tower_height,
736                )
737                .fill(lightwood);
738            //top layer, green above the tower
739            painter
740                .cylinder_with_radius(
741                    wpos.with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32),
742                    tower_radius,
743                    2.0,
744                )
745                .fill(moss);
746            //standing area one block deeper
747            painter
748                .cylinder_with_radius(
749                    wpos.with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 9),
750                    tower_radius - 1.0,
751                    1.0,
752                )
753                .clear();
754            //remove top sphere from trunk
755            painter
756                .sphere_with_radius(
757                    Vec2::new(wpos.x - (randy - 5) / 2, wpos.y - (randz - 5) / 2).with_z(
758                        land.get_alt_approx(wpos) as i32 + tower_height as i32 + 6 - randx / 3,
759                    ),
760                    5.5,
761                )
762                .clear();
763            //remove bark from exterior layer
764            painter
765                .sphere_with_radius(
766                    Vec2::new(wpos.x - (randy - 5) * 2, wpos.y - (randz - 5) * 2)
767                        .with_z(land.get_alt_approx(wpos) as i32 + randx * 2),
768                    7.5,
769                )
770                .intersect(outside)
771                .clear();
772
773            painter
774                .sphere_with_radius(
775                    Vec2::new(wpos.x - (randx - 5) * 2, wpos.y - (randy - 5) * 2)
776                        .with_z(land.get_alt_approx(wpos) as i32 + randz * 2),
777                    5.5,
778                )
779                .intersect(outside)
780                .clear();
781
782            //cut out standing room
783            painter
784                .aabb(Aabb {
785                    min: Vec2::new(wpos.x - 3, wpos.y - 10)
786                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 8),
787                    max: Vec2::new(wpos.x + 3, wpos.y + 10)
788                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 3),
789                })
790                .clear();
791            painter
792                .aabb(Aabb {
793                    min: Vec2::new(wpos.x - 10, wpos.y - 3)
794                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 8),
795                    max: Vec2::new(wpos.x + 10, wpos.y + 3)
796                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 3),
797                })
798                .clear();
799            painter
800                .aabb(Aabb {
801                    min: Vec2::new(wpos.x - 2, wpos.y - 10)
802                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 8),
803                    max: Vec2::new(wpos.x + 2, wpos.y + 10)
804                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 2),
805                })
806                .clear();
807            painter
808                .aabb(Aabb {
809                    min: Vec2::new(wpos.x - 10, wpos.y - 2)
810                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 8),
811                    max: Vec2::new(wpos.x + 10, wpos.y + 2)
812                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 2),
813                })
814                .clear();
815            //flags
816            painter
817                .aabb(Aabb {
818                    min: Vec2::new(wpos.x - 2, wpos.y - tower_radius as i32 - 1)
819                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 16),
820                    max: Vec2::new(wpos.x + 2, wpos.y + tower_radius as i32 + 1)
821                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 10),
822                })
823                .intersect(outside)
824                .fill(red.clone());
825            painter
826                .aabb(Aabb {
827                    min: Vec2::new(wpos.x - tower_radius as i32 - 1, wpos.y - 2)
828                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 16),
829                    max: Vec2::new(wpos.x + tower_radius as i32 + 1, wpos.y + 2)
830                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 10),
831                })
832                .intersect(outside)
833                .fill(red.clone());
834            painter
835                .aabb(Aabb {
836                    min: Vec2::new(wpos.x - 1, wpos.y - tower_radius as i32 - 1)
837                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 17),
838                    max: Vec2::new(wpos.x + 1, wpos.y + tower_radius as i32 + 1)
839                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 16),
840                })
841                .intersect(outside)
842                .fill(red.clone());
843            painter
844                .aabb(Aabb {
845                    min: Vec2::new(wpos.x - tower_radius as i32 - 1, wpos.y - 1)
846                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 17),
847                    max: Vec2::new(wpos.x + tower_radius as i32 + 1, wpos.y + 1)
848                        .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 16),
849                })
850                .intersect(outside)
851                .fill(red);
852        });
853
854        self.structure_locations
855            .iter()
856            .for_each(|(kind, loc, door_dir)| {
857                let wpos = self.origin + loc.xy();
858                let alt = land.get_alt_approx(wpos) as i32;
859
860                fn generate_hut(
861                    painter: &Painter,
862                    wpos: Vec2<i32>,
863                    alt: i32,
864                    door_dir: Dir,
865                    hut_radius: f32,
866                    hut_wall_height: f32,
867                    door_height: i32,
868                    roof_height: f32,
869                    roof_overhang: f32,
870                ) {
871                    let randx = wpos.x.abs() % 10;
872                    let randy = wpos.y.abs() % 10;
873                    let randz = alt.abs() % 10;
874                    let hut_wall_height = hut_wall_height + randy as f32 * 1.5;
875
876                    let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12);
877                    let lightwood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12);
878                    let moss = Fill::Brick(BlockKind::Leaves, Rgb::new(22, 36, 20), 24);
879
880                    // Floor
881                    let base = wpos.with_z(alt - 4);
882                    painter
883                        .cylinder_with_radius(base, hut_radius + 1.0, 6.0)
884                        .fill(darkwood.clone());
885
886                    // Wall
887                    let floor_pos = wpos.with_z(alt + 1);
888                    //alternating colors for ring pattern on ceiling
889                    painter
890                        .cylinder_with_radius(floor_pos, hut_radius, hut_wall_height + 3.0)
891                        .fill(lightwood.clone());
892                    painter
893                        .cylinder_with_radius(floor_pos, hut_radius - 1.0, hut_wall_height + 3.0)
894                        .fill(darkwood.clone());
895                    painter
896                        .cylinder_with_radius(floor_pos, hut_radius - 2.0, hut_wall_height + 3.0)
897                        .fill(lightwood);
898                    painter
899                        .cylinder_with_radius(floor_pos, hut_radius - 3.0, hut_wall_height + 3.0)
900                        .fill(darkwood);
901                    painter
902                        .cylinder_with_radius(floor_pos, hut_radius - 1.0, hut_wall_height)
903                        .clear();
904
905                    // Door
906                    let aabb_min = |dir| {
907                        match dir {
908                            Dir::X | Dir::Y => wpos - Vec2::one(),
909                            Dir::NegX | Dir::NegY => wpos + randx / 5 + 1,
910                        }
911                        .with_z(alt + 1)
912                    };
913                    let aabb_max = |dir| {
914                        (match dir {
915                            Dir::X | Dir::Y => wpos + randx / 5 + 1,
916                            Dir::NegX | Dir::NegY => wpos - Vec2::one(),
917                        } + dir.to_vec2() * hut_radius as i32)
918                            .with_z(alt + 1 + door_height)
919                    };
920
921                    painter
922                        .aabb(Aabb {
923                            min: aabb_min(door_dir),
924                            max: aabb_max(door_dir),
925                        })
926                        .clear();
927
928                    // Roof
929                    let roof_radius = hut_radius + roof_overhang;
930                    painter
931                        .cone_with_radius(
932                            wpos.with_z(alt + 3 + hut_wall_height as i32),
933                            roof_radius - 1.0,
934                            roof_height - 1.0,
935                        )
936                        .fill(moss.clone());
937
938                    //small bits hanging from huts
939                    let tendril1 = painter.line(
940                        Vec2::new(wpos.x - 3, wpos.y - 5)
941                            .with_z(alt + (hut_wall_height * 0.75) as i32),
942                        Vec2::new(wpos.x - 3, wpos.y - 5).with_z(alt + 3 + hut_wall_height as i32),
943                        1.0,
944                    );
945
946                    let tendril2 = painter.line(
947                        Vec2::new(wpos.x + 4, wpos.y + 2)
948                            .with_z(alt + 1 + (hut_wall_height * 0.75) as i32),
949                        Vec2::new(wpos.x + 4, wpos.y + 2).with_z(alt + 3 + hut_wall_height as i32),
950                        1.0,
951                    );
952
953                    let tendril3 = tendril2.translate(Vec3::new(-7, 2, 0));
954                    let tendril4 = tendril1.translate(Vec3::new(7, 4, 0));
955                    let tendrils = tendril1.union(tendril2).union(tendril3).union(tendril4);
956
957                    tendrils.fill(moss);
958
959                    //sphere to delete some hut
960                    painter
961                        .sphere_with_radius(
962                            Vec2::new(wpos.x - (randy - 5) / 2, wpos.y - (randz - 5) / 2)
963                                .with_z(alt + 12 + hut_wall_height as i32 - randx / 3),
964                            8.5,
965                        )
966                        .clear();
967                }
968
969                fn generate_chieftainhut(
970                    painter: &Painter,
971                    wpos: Vec2<i32>,
972                    alt: i32,
973                    roof_height: f32,
974                ) {
975                    let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12);
976                    let lightwood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12);
977                    let moss = Fill::Brick(BlockKind::Wood, Rgb::new(22, 36, 20), 24);
978                    let red = Fill::Brick(BlockKind::Wood, Rgb::new(102, 31, 2), 12);
979
980                    // Floor
981                    let raise = 5;
982
983                    let platform = painter.aabb(Aabb {
984                        min: (wpos - 20).with_z(alt + raise),
985                        max: (wpos + 20).with_z(alt + raise + 1),
986                    });
987
988                    painter.fill(platform, darkwood.clone());
989
990                    let supports = painter
991                        .line(
992                            (wpos - 19).with_z(alt - 3),
993                            (wpos - 19).with_z(alt + raise),
994                            2.0,
995                        )
996                        .repeat(Vec3::new(37, 0, 0), 2)
997                        .repeat(Vec3::new(0, 37, 0), 2);
998
999                    let supports_inner = painter
1000                        .aabb(Aabb {
1001                            min: (wpos - 19).with_z(alt - 10) + Vec3::unit_y() * 17,
1002                            max: (wpos - 15).with_z(alt + raise) + Vec3::unit_y() * 17,
1003                        })
1004                        .repeat(Vec3::new(17, 17, 0), 2)
1005                        .repeat(Vec3::new(17, -17, 0), 2);
1006                    // let support_inner_2 = support_inner_1.translate(Vec3::new(34, 0, 0));
1007                    // let support_inner_3 = support_inner_1.translate(Vec3::new(17, 17, 0));
1008                    // let support_inner_4 = support_inner_1.translate(Vec3::new(17, -17, 0));
1009                    let supports = supports.union(supports_inner);
1010
1011                    painter.fill(supports, darkwood.clone());
1012                    let height_1 = 10.0;
1013                    let height_2 = 12.0;
1014                    let rad_1 = 18.0;
1015                    let rad_2 = 15.0;
1016
1017                    let floor_pos = wpos.with_z(alt + 1 + raise);
1018                    painter
1019                        .cylinder_with_radius(floor_pos, rad_1, height_1)
1020                        .fill(lightwood.clone());
1021                    painter
1022                        .cylinder_with_radius(floor_pos, rad_1 - 1.0, height_1)
1023                        .clear();
1024
1025                    let floor2_pos = wpos.with_z(alt + 1 + raise + height_1 as i32);
1026                    painter
1027                        .cylinder_with_radius(floor2_pos, rad_2, height_2)
1028                        .fill(lightwood);
1029
1030                    // Roof
1031                    let roof_radius = rad_1 + 5.0;
1032                    let roof1 = painter.cone_with_radius(
1033                        wpos.with_z(alt + 1 + height_1 as i32 + raise),
1034                        roof_radius,
1035                        roof_height,
1036                    );
1037                    roof1.fill(moss.clone());
1038                    let roof_radius = rad_2 + 1.0;
1039                    painter
1040                        .cone_with_radius(
1041                            wpos.with_z(alt + 1 + height_1 as i32 + height_2 as i32 + raise),
1042                            roof_radius,
1043                            roof_height,
1044                        )
1045                        .fill(moss);
1046                    let centerspot = painter.line(
1047                        (wpos + 1).with_z(alt + raise + height_1 as i32 + 2),
1048                        (wpos + 1).with_z(alt + raise + height_1 as i32 + 2),
1049                        1.0,
1050                    );
1051                    let roof_support_1 = painter.line(
1052                        (wpos + rad_1 as i32 - 7).with_z(alt + raise + height_1 as i32 - 2),
1053                        (wpos + rad_1 as i32 - 2).with_z(alt + raise + height_1 as i32),
1054                        1.5,
1055                    );
1056                    let roof_strut = painter.line(
1057                        (wpos + rad_1 as i32 - 7).with_z(alt + raise + height_1 as i32 + 2),
1058                        (wpos + rad_1 as i32 - 2).with_z(alt + raise + height_1 as i32 + 2),
1059                        1.0,
1060                    );
1061                    let wall2support = painter.line(
1062                        (wpos + rad_2 as i32 - 5).with_z(alt + raise + height_1 as i32 + 7),
1063                        (wpos + rad_2 as i32 - 5).with_z(alt + raise + height_1 as i32 + 12),
1064                        1.5,
1065                    );
1066                    let wall2roof = painter.line(
1067                        (wpos + rad_2 as i32 - 7).with_z(alt + raise + height_2 as i32 + 12),
1068                        (wpos + rad_2 as i32 - 4).with_z(alt + raise + height_2 as i32 + 10),
1069                        2.0,
1070                    );
1071
1072                    let roof_support_1 = centerspot
1073                        .union(roof_support_1)
1074                        .union(roof_strut)
1075                        .union(wall2support)
1076                        .union(wall2roof);
1077
1078                    let roof_support_2 =
1079                        roof_support_1.rotate_about_min(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1));
1080                    let roof_support_3 =
1081                        roof_support_1.rotate_about_min(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1));
1082                    let roof_support_4 =
1083                        roof_support_1.rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1));
1084                    let roof_support = roof_support_1
1085                        .union(roof_support_2)
1086                        .union(roof_support_3)
1087                        .union(roof_support_4);
1088
1089                    painter.fill(roof_support, red.clone());
1090
1091                    let spike_high = painter.cubic_bezier(
1092                        (wpos + rad_2 as i32 - 5).with_z(alt + raise + height_1 as i32 + 2),
1093                        (wpos + rad_2 as i32 - 2).with_z(alt + raise + height_1 as i32 + 2),
1094                        (wpos + rad_2 as i32 - 1).with_z(alt + raise + height_1 as i32 + 5),
1095                        (wpos + rad_2 as i32).with_z(alt + raise + height_1 as i32 + 8),
1096                        1.3,
1097                    );
1098                    let spike_low =
1099                        spike_high.rotate_about_min(Mat3::new(1, 0, 0, 0, 1, 0, 0, 0, -1));
1100                    let spike_1 = centerspot.union(spike_low).union(spike_high);
1101
1102                    let spike_2 = spike_1.rotate_about_min(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1));
1103                    let spike_3 = spike_1.rotate_about_min(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1));
1104                    let spike_4 = spike_1.rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1));
1105                    let spikes = spike_1.union(spike_2).union(spike_3).union(spike_4);
1106
1107                    painter.fill(
1108                        spikes,
1109                        Fill::Brick(BlockKind::Wood, Rgb::new(112, 110, 99), 24),
1110                    );
1111                    //Open doorways (top floor)
1112                    painter
1113                        .aabb(Aabb {
1114                            min: Vec2::new(wpos.x - 2, wpos.y - 15)
1115                                .with_z(alt + raise + height_1 as i32 + 3),
1116                            max: Vec2::new(wpos.x + 2, wpos.y + 15)
1117                                .with_z(alt + raise + height_1 as i32 + height_2 as i32),
1118                        })
1119                        .clear();
1120                    painter
1121                        .aabb(Aabb {
1122                            min: Vec2::new(wpos.x - 3, wpos.y - 15)
1123                                .with_z(alt + raise + height_1 as i32 + 4),
1124                            max: Vec2::new(wpos.x + 3, wpos.y + 15)
1125                                .with_z(alt + raise + height_1 as i32 + 10),
1126                        })
1127                        .clear();
1128                    painter
1129                        .aabb(Aabb {
1130                            min: Vec2::new(wpos.x - 15, wpos.y - 2)
1131                                .with_z(alt + raise + height_1 as i32 + 3),
1132                            max: Vec2::new(wpos.x + 15, wpos.y + 2)
1133                                .with_z(alt + raise + height_1 as i32 + height_2 as i32),
1134                        })
1135                        .clear();
1136                    painter
1137                        .aabb(Aabb {
1138                            min: Vec2::new(wpos.x - 15, wpos.y - 3)
1139                                .with_z(alt + raise + height_1 as i32 + 4),
1140                            max: Vec2::new(wpos.x + 15, wpos.y + 3)
1141                                .with_z(alt + raise + height_1 as i32 + 10),
1142                        })
1143                        .clear();
1144
1145                    //
1146                    painter
1147                        .aabb(Aabb {
1148                            min: Vec2::new(wpos.x - 18, wpos.y - 2)
1149                                .with_z(alt + raise + height_1 as i32 - 9),
1150                            max: Vec2::new(wpos.x + 18, wpos.y + 2)
1151                                .with_z(alt + raise + height_1 as i32 - 1),
1152                        })
1153                        .clear();
1154                    painter
1155                        .aabb(Aabb {
1156                            min: Vec2::new(wpos.x - 2, wpos.y - 18)
1157                                .with_z(alt + raise + height_1 as i32 - 9),
1158                            max: Vec2::new(wpos.x + 2, wpos.y + 18)
1159                                .with_z(alt + raise + height_1 as i32 - 1),
1160                        })
1161                        .clear();
1162                    painter
1163                        .aabb(Aabb {
1164                            min: Vec2::new(wpos.x - 18, wpos.y - 3)
1165                                .with_z(alt + raise + height_1 as i32 - 9),
1166                            max: Vec2::new(wpos.x + 18, wpos.y + 3)
1167                                .with_z(alt + raise + height_1 as i32 - 3),
1168                        })
1169                        .clear();
1170                    painter
1171                        .aabb(Aabb {
1172                            min: Vec2::new(wpos.x - 3, wpos.y - 18)
1173                                .with_z(alt + raise + height_1 as i32 - 9),
1174                            max: Vec2::new(wpos.x + 3, wpos.y + 18)
1175                                .with_z(alt + raise + height_1 as i32 - 3),
1176                        })
1177                        .clear();
1178                    //Roofing details
1179
1180                    painter
1181                        .aabb(Aabb {
1182                            min: Vec2::new(wpos.x - 23, wpos.y - 2)
1183                                .with_z(alt + raise + height_1 as i32 - 3),
1184                            max: Vec2::new(wpos.x + 23, wpos.y + 2)
1185                                .with_z(alt + raise + height_1 as i32 + 7),
1186                        })
1187                        .intersect(roof1)
1188                        .translate(Vec3::new(0, 0, -1))
1189                        .fill(darkwood.clone());
1190                    painter
1191                        .aabb(Aabb {
1192                            min: Vec2::new(wpos.x - 23, wpos.y - 2)
1193                                .with_z(alt + raise + height_1 as i32 - 3),
1194                            max: Vec2::new(wpos.x + 23, wpos.y + 2)
1195                                .with_z(alt + raise + height_1 as i32 + 7),
1196                        })
1197                        .intersect(roof1)
1198                        .fill(red.clone());
1199                    painter
1200                        .aabb(Aabb {
1201                            min: Vec2::new(wpos.x - 2, wpos.y - 23)
1202                                .with_z(alt + raise + height_1 as i32 - 3),
1203                            max: Vec2::new(wpos.x + 2, wpos.y + 23)
1204                                .with_z(alt + raise + height_1 as i32 + 7),
1205                        })
1206                        .intersect(roof1)
1207                        .translate(Vec3::new(0, 0, -1))
1208                        .fill(darkwood);
1209                    painter
1210                        .aabb(Aabb {
1211                            min: Vec2::new(wpos.x - 2, wpos.y - 23)
1212                                .with_z(alt + raise + height_1 as i32 - 3),
1213                            max: Vec2::new(wpos.x + 2, wpos.y + 23)
1214                                .with_z(alt + raise + height_1 as i32 + 7),
1215                        })
1216                        .intersect(roof1)
1217                        .fill(red);
1218                    painter
1219                        .cylinder_with_radius(floor2_pos, rad_2 - 1.0, height_2)
1220                        .clear();
1221                }
1222
1223                match kind {
1224                    GnarlingStructure::Hut => {
1225                        let hut_radius = 5.0;
1226                        let hut_wall_height = 4.0;
1227                        let door_height = 4;
1228                        let roof_height = 3.0;
1229                        let roof_overhang = 1.0;
1230
1231                        generate_hut(
1232                            painter,
1233                            wpos,
1234                            alt,
1235                            *door_dir,
1236                            hut_radius,
1237                            hut_wall_height,
1238                            door_height,
1239                            roof_height,
1240                            roof_overhang,
1241                        );
1242                    },
1243                    GnarlingStructure::VeloriteHut => {
1244                        let rand = Vec3::new(
1245                            wpos.x.abs() % 10,
1246                            wpos.y.abs() % 10,
1247                            (land.get_alt_approx(wpos) as i32).abs() % 10,
1248                        );
1249
1250                        let length = 14;
1251                        let width = 6;
1252                        let height = alt + 12;
1253                        let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12);
1254                        let lightwood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12);
1255                        let moss = Fill::Brick(BlockKind::Wood, Rgb::new(22, 36, 20), 24);
1256                        let red = Fill::Brick(BlockKind::Wood, Rgb::new(102, 31, 2), 12);
1257
1258                        let low1 = painter.aabb(Aabb {
1259                            min: Vec2::new(wpos.x - width, wpos.y - length).with_z(height + 1),
1260                            max: Vec2::new(wpos.x + width, wpos.y + length).with_z(height + 2),
1261                        });
1262
1263                        let low2 = painter.aabb(Aabb {
1264                            min: Vec2::new(wpos.x - length, wpos.y - width).with_z(height + 1),
1265                            max: Vec2::new(wpos.x + length, wpos.y + width).with_z(height + 2),
1266                        });
1267                        let top1 = painter.aabb(Aabb {
1268                            min: Vec2::new(wpos.x - width + 1, wpos.y - length + 1)
1269                                .with_z(height + 2),
1270                            max: Vec2::new(wpos.x + width - 1, wpos.y + length - 1)
1271                                .with_z(height + 2 + 1),
1272                        });
1273
1274                        let top2 = painter.aabb(Aabb {
1275                            min: Vec2::new(wpos.x - length + 1, wpos.y - width + 1)
1276                                .with_z(height + 2),
1277                            max: Vec2::new(wpos.x + length - 1, wpos.y + width - 1)
1278                                .with_z(height + 2 + 1),
1279                        });
1280                        let low = low1.union(low2);
1281                        let top = top1.union(top2);
1282
1283                        let roof = low1.union(low2).union(top1).union(top2);
1284                        let roofmoss = roof.translate(Vec3::new(0, 0, 1)).without(top).without(low);
1285                        top.fill(darkwood.clone());
1286                        low.fill(lightwood);
1287                        roofmoss.fill(moss);
1288                        painter
1289                            .sphere_with_radius(
1290                                Vec2::new(wpos.x + rand.y - 5, wpos.y + rand.z - 5)
1291                                    .with_z(height + 4),
1292                                7.0,
1293                            )
1294                            .intersect(roofmoss)
1295                            .clear();
1296                        painter
1297                            .sphere_with_radius(
1298                                Vec2::new(wpos.x + rand.x - 5, wpos.y + rand.y - 5)
1299                                    .with_z(height + 4),
1300                                4.0,
1301                            )
1302                            .intersect(roofmoss)
1303                            .clear();
1304                        painter
1305                            .sphere_with_radius(
1306                                Vec2::new(wpos.x + 2 * (rand.z - 5), wpos.y + 2 * (rand.x - 5))
1307                                    .with_z(height + 4),
1308                                4.0,
1309                            )
1310                            .intersect(roofmoss)
1311                            .clear();
1312
1313                        //inside leg
1314                        let leg1 = painter.aabb(Aabb {
1315                            min: Vec2::new(wpos.x - width, wpos.y - width).with_z(height - 12),
1316                            max: Vec2::new(wpos.x - width + 2, wpos.y - width + 2)
1317                                .with_z(height + 2),
1318                        });
1319                        let legsupport1 = painter.line(
1320                            Vec2::new(wpos.x - width, wpos.y - width).with_z(height - 6),
1321                            Vec2::new(wpos.x - width + 3, wpos.y - width + 3).with_z(height),
1322                            1.0,
1323                        );
1324
1325                        let leg2 = leg1.translate(Vec3::new(0, width * 2 - 2, 0));
1326                        let leg3 = leg1.translate(Vec3::new(width * 2 - 2, 0, 0));
1327                        let leg4 = leg1.translate(Vec3::new(width * 2 - 2, width * 2 - 2, 0));
1328                        let legsupport2 = legsupport1
1329                            .rotate_about_min(Mat3::new(0, 1, 0, -1, 0, 0, 0, 0, 1))
1330                            .translate(Vec3::new(1, width * 2 + 1, 0));
1331                        let legsupport3 = legsupport1
1332                            .rotate_about_min(Mat3::new(0, -1, 0, 1, 0, 0, 0, 0, 1))
1333                            .translate(Vec3::new(width * 2 + 1, 1, 0));
1334                        let legsupport4 = legsupport1
1335                            .rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1))
1336                            .translate(Vec3::new(width * 2 + 2, width * 2 + 2, 0));
1337
1338                        let legsupports = legsupport1
1339                            .union(legsupport2)
1340                            .union(legsupport3)
1341                            .union(legsupport4);
1342
1343                        let legs = leg1.union(leg2).union(leg3).union(leg4);
1344                        legs.fill(darkwood);
1345                        legsupports.without(legs).fill(red);
1346
1347                        let spike1 = painter.line(
1348                            Vec2::new(wpos.x - 12, wpos.y + 2).with_z(height + 3),
1349                            Vec2::new(wpos.x - 7, wpos.y + 2).with_z(height + 5),
1350                            1.0,
1351                        );
1352                        let spike2 = painter.line(
1353                            Vec2::new(wpos.x - 12, wpos.y - 3).with_z(height + 3),
1354                            Vec2::new(wpos.x - 7, wpos.y - 3).with_z(height + 5),
1355                            1.0,
1356                        );
1357                        let spikes = spike1.union(spike2);
1358                        let spikesalt = spikes
1359                            .rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1))
1360                            .translate(Vec3::new(26, 8, 0));
1361                        let spikeshalf = spikes.union(spikesalt);
1362                        let spikesotherhalf = spikeshalf
1363                            .rotate_about_min(Mat3::new(0, -1, 0, 1, 0, 0, 0, 0, 1))
1364                            .translate(Vec3::new(16, -9, 0));
1365                        let spikesall = spikeshalf.union(spikesotherhalf);
1366
1367                        spikesall.fill(Fill::Brick(BlockKind::Wood, Rgb::new(112, 110, 99), 24));
1368                        let crystal1 = painter.aabb(Aabb {
1369                            min: Vec2::new(wpos.x - 2, wpos.y - 3).with_z(alt),
1370                            max: Vec2::new(wpos.x + 2, wpos.y + 1).with_z(alt + 7),
1371                        });
1372                        let crystal2 = painter.aabb(Aabb {
1373                            min: Vec2::new(wpos.x - 3, wpos.y - 3).with_z(alt),
1374                            max: Vec2::new(wpos.x + 3, wpos.y + 1).with_z(alt + 6),
1375                        });
1376                        let crystal3 = painter.aabb(Aabb {
1377                            min: Vec2::new(wpos.x - 2, wpos.y - 2).with_z(alt),
1378                            max: Vec2::new(wpos.x + 4, wpos.y + 3).with_z(alt + 4),
1379                        });
1380                        let crystal4 = painter.aabb(Aabb {
1381                            min: Vec2::new(wpos.x - 1, wpos.y - 4).with_z(alt),
1382                            max: Vec2::new(wpos.x + 2, wpos.y + 2).with_z(alt + 8),
1383                        });
1384                        let crystalp1 = crystal1.union(crystal3);
1385                        let crystalp2 = crystal2.union(crystal4);
1386
1387                        crystalp1.fill(Fill::Brick(
1388                            BlockKind::GlowingRock,
1389                            Rgb::new(50, 225, 210),
1390                            24,
1391                        ));
1392                        crystalp2.fill(Fill::Brick(
1393                            BlockKind::GlowingRock,
1394                            Rgb::new(36, 187, 151),
1395                            24,
1396                        ));
1397                    },
1398                    GnarlingStructure::Totem => {
1399                        let totem_pos = wpos.with_z(alt);
1400
1401                        lazy_static! {
1402                            pub static ref TOTEM: AssetHandle<StructuresGroup> =
1403                                PrefabStructure::load_group("site_structures.gnarling.totem");
1404                        }
1405
1406                        let totem = TOTEM.read();
1407                        let totem = totem[self.seed as usize % totem.len()].clone();
1408
1409                        painter
1410                            .prim(Primitive::Prefab(Box::new(totem.clone())))
1411                            .translate(totem_pos)
1412                            .fill(Fill::Prefab(Box::new(totem), totem_pos, self.seed));
1413                    },
1414                    GnarlingStructure::ChieftainHut => {
1415                        let roof_height = 3.0;
1416
1417                        generate_chieftainhut(painter, wpos, alt, roof_height);
1418                    },
1419
1420                    GnarlingStructure::Banner => {
1421                        let rand = Vec3::new(
1422                            wpos.x.abs() % 10,
1423                            wpos.y.abs() % 10,
1424                            (land.get_alt_approx(wpos) as i32).abs() % 10,
1425                        );
1426
1427                        let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12);
1428                        let moss = Fill::Brick(BlockKind::Leaves, Rgb::new(22, 36, 20), 24);
1429                        let red = Fill::Brick(BlockKind::Wood, Rgb::new(102, 31, 2), 12);
1430                        let flag = painter.aabb(Aabb {
1431                            min: Vec2::new(wpos.x + 1, wpos.y - 1).with_z(alt + 8),
1432                            max: Vec2::new(wpos.x + 8, wpos.y).with_z(alt + 38),
1433                        });
1434                        flag.fill(red);
1435                        //add green streaks
1436                        let streak1 = painter
1437                            .line(
1438                                Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 20),
1439                                Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 33),
1440                                4.0,
1441                            )
1442                            .intersect(flag);
1443
1444                        let streak2 = painter
1445                            .line(
1446                                Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 12),
1447                                Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 25),
1448                                1.5,
1449                            )
1450                            .intersect(flag);
1451                        let streak3 = painter
1452                            .line(
1453                                Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 8),
1454                                Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 21),
1455                                1.0,
1456                            )
1457                            .intersect(flag);
1458                        let streaks = streak1.union(streak2).union(streak3);
1459                        streaks.fill(moss);
1460                        //erase from top and bottom of rectangle flag for shape
1461                        painter
1462                            .line(
1463                                Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 31),
1464                                Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 44),
1465                                5.0,
1466                            )
1467                            .intersect(flag)
1468                            .clear();
1469                        painter
1470                            .sphere_with_radius(Vec2::new(wpos.x + 8, wpos.y).with_z(alt + 8), 6.0)
1471                            .intersect(flag)
1472                            .clear();
1473                        //tatters
1474                        painter
1475                            .line(
1476                                Vec2::new(wpos.x + 3 + rand.x / 5, wpos.y - 1)
1477                                    .with_z(alt + 15 + rand.y),
1478                                Vec2::new(wpos.x + 3 + rand.y / 5, wpos.y - 1).with_z(alt + 5),
1479                                0.9 * rand.z as f32 / 4.0,
1480                            )
1481                            .clear();
1482                        painter
1483                            .line(
1484                                Vec2::new(wpos.x + 4 + rand.z / 2, wpos.y - 1)
1485                                    .with_z(alt + 20 + rand.x),
1486                                Vec2::new(wpos.x + 4 + rand.z / 2, wpos.y - 1)
1487                                    .with_z(alt + 17 + rand.y),
1488                                0.9 * rand.z as f32 / 6.0,
1489                            )
1490                            .clear();
1491
1492                        //flagpole
1493                        let column = painter.aabb(Aabb {
1494                            min: (wpos - 1).with_z(alt - 3),
1495                            max: (wpos).with_z(alt + 30),
1496                        });
1497
1498                        let arm = painter.line(
1499                            Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 26),
1500                            Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 39),
1501                            0.9,
1502                        );
1503                        let flagpole = column.union(arm);
1504                        flagpole.fill(darkwood);
1505                    },
1506                    GnarlingStructure::WatchTower => {
1507                        let platform_1_height = 14;
1508                        let platform_2_height = 20;
1509                        let platform_3_height = 26;
1510                        let roof_height = 30;
1511
1512                        let platform_1 = painter.aabb(Aabb {
1513                            min: (wpos + 1).with_z(alt + platform_1_height),
1514                            max: (wpos + 10).with_z(alt + platform_1_height + 1),
1515                        });
1516
1517                        let platform_2 = painter.aabb(Aabb {
1518                            min: (wpos + 1).with_z(alt + platform_2_height),
1519                            max: (wpos + 10).with_z(alt + platform_2_height + 1),
1520                        });
1521
1522                        let platform_3 = painter.aabb(Aabb {
1523                            min: (wpos + 2).with_z(alt + platform_3_height),
1524                            max: (wpos + 9).with_z(alt + platform_3_height + 1),
1525                        });
1526
1527                        let support_1 = painter.line(
1528                            wpos.with_z(alt),
1529                            (wpos + 2).with_z(alt + platform_1_height),
1530                            1.0,
1531                        );
1532                        let support_2 = support_1
1533                            .rotate_about_min(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1))
1534                            .translate(Vec3::new(0, 13, 0));
1535                        let support_3 = support_1
1536                            .rotate_about_min(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1))
1537                            .translate(Vec3::new(13, 0, 0));
1538                        let support_4 = support_1
1539                            .rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1))
1540                            .translate(Vec3::new(13, 13, 0));
1541
1542                        let supports = support_1.union(support_2).union(support_3).union(support_4);
1543
1544                        let platform_1_supports = painter
1545                            .plane(
1546                                Aabr {
1547                                    min: (wpos + 2),
1548                                    max: (wpos + 9),
1549                                },
1550                                (wpos + 3).with_z(alt + platform_1_height + 1),
1551                                Vec2::new(0.0, 1.0),
1552                            )
1553                            .union(painter.plane(
1554                                Aabr {
1555                                    min: (wpos + 2),
1556                                    max: (wpos + 9),
1557                                },
1558                                (wpos + 3).with_z(alt + platform_2_height),
1559                                Vec2::new(0.0, -1.0),
1560                            ))
1561                            .without(
1562                                painter.aabb(Aabb {
1563                                    min: Vec2::new(wpos.x + 3, wpos.y + 2)
1564                                        .with_z(alt + platform_1_height),
1565                                    max: Vec2::new(wpos.x + 8, wpos.y + 9)
1566                                        .with_z(alt + platform_2_height),
1567                                }),
1568                            )
1569                            .without(
1570                                painter.aabb(Aabb {
1571                                    min: Vec2::new(wpos.x + 2, wpos.y + 2)
1572                                        .with_z(alt + platform_2_height),
1573                                    max: Vec2::new(wpos.x + 9, wpos.y + 9)
1574                                        .with_z(alt + platform_2_height + 2),
1575                                }),
1576                            )
1577                            .union(
1578                                painter.aabb(Aabb {
1579                                    min: Vec2::new(wpos.x + 2, wpos.y + 2)
1580                                        .with_z(alt + platform_1_height),
1581                                    max: Vec2::new(wpos.x + 9, wpos.y + 9)
1582                                        .with_z(alt + platform_2_height),
1583                                }),
1584                            )
1585                            .without(
1586                                painter.aabb(Aabb {
1587                                    min: Vec2::new(wpos.x + 3, wpos.y + 2)
1588                                        .with_z(alt + platform_1_height),
1589                                    max: Vec2::new(wpos.x + 8, wpos.y + 9)
1590                                        .with_z(alt + platform_2_height),
1591                                }),
1592                            )
1593                            .without(
1594                                painter.aabb(Aabb {
1595                                    min: Vec2::new(wpos.x + 2, wpos.y + 3)
1596                                        .with_z(alt + platform_1_height),
1597                                    max: Vec2::new(wpos.x + 9, wpos.y + 8)
1598                                        .with_z(alt + platform_2_height),
1599                                }),
1600                            );
1601
1602                        let platform_2_supports = painter
1603                            .plane(
1604                                Aabr {
1605                                    min: (wpos + 3),
1606                                    max: (wpos + 8),
1607                                },
1608                                (wpos + 3).with_z(alt + platform_2_height + 1),
1609                                Vec2::new(1.0, 0.0),
1610                            )
1611                            .union(painter.plane(
1612                                Aabr {
1613                                    min: (wpos + 3),
1614                                    max: (wpos + 8),
1615                                },
1616                                (wpos + 3).with_z(alt + platform_3_height),
1617                                Vec2::new(-1.0, 0.0),
1618                            ))
1619                            .without(
1620                                painter.aabb(Aabb {
1621                                    min: Vec2::new(wpos.x + 3, wpos.y + 4)
1622                                        .with_z(alt + platform_2_height),
1623                                    max: Vec2::new(wpos.x + 8, wpos.y + 7)
1624                                        .with_z(alt + platform_3_height),
1625                                }),
1626                            )
1627                            .union(
1628                                painter.aabb(Aabb {
1629                                    min: Vec2::new(wpos.x + 3, wpos.y + 3)
1630                                        .with_z(alt + platform_2_height),
1631                                    max: Vec2::new(wpos.x + 8, wpos.y + 8)
1632                                        .with_z(alt + platform_3_height),
1633                                }),
1634                            )
1635                            .without(
1636                                painter.aabb(Aabb {
1637                                    min: Vec2::new(wpos.x + 4, wpos.y + 3)
1638                                        .with_z(alt + platform_2_height),
1639                                    max: Vec2::new(wpos.x + 7, wpos.y + 8)
1640                                        .with_z(alt + platform_3_height),
1641                                }),
1642                            )
1643                            .without(
1644                                painter.aabb(Aabb {
1645                                    min: Vec2::new(wpos.x + 3, wpos.y + 4)
1646                                        .with_z(alt + platform_2_height),
1647                                    max: Vec2::new(wpos.x + 8, wpos.y + 7)
1648                                        .with_z(alt + platform_3_height),
1649                                }),
1650                            );
1651
1652                        let roof = painter
1653                            .gable(
1654                                Aabb {
1655                                    min: (wpos + 2).with_z(alt + roof_height),
1656                                    max: (wpos + 9).with_z(alt + roof_height + 4),
1657                                },
1658                                0,
1659                                Dir::Y,
1660                            )
1661                            .without(
1662                                painter.gable(
1663                                    Aabb {
1664                                        min: Vec2::new(wpos.x + 3, wpos.y + 2)
1665                                            .with_z(alt + roof_height),
1666                                        max: Vec2::new(wpos.x + 8, wpos.y + 9)
1667                                            .with_z(alt + roof_height + 3),
1668                                    },
1669                                    0,
1670                                    Dir::Y,
1671                                ),
1672                            );
1673
1674                        let roof_pillars = painter
1675                            .aabb(Aabb {
1676                                min: Vec2::new(wpos.x + 3, wpos.y + 3)
1677                                    .with_z(alt + platform_3_height),
1678                                max: Vec2::new(wpos.x + 8, wpos.y + 8)
1679                                    .with_z(alt + roof_height + 1),
1680                            })
1681                            .without(
1682                                painter.aabb(Aabb {
1683                                    min: Vec2::new(wpos.x + 4, wpos.y + 3)
1684                                        .with_z(alt + platform_3_height),
1685                                    max: Vec2::new(wpos.x + 7, wpos.y + 8)
1686                                        .with_z(alt + roof_height),
1687                                }),
1688                            )
1689                            .without(
1690                                painter.aabb(Aabb {
1691                                    min: Vec2::new(wpos.x + 3, wpos.y + 4)
1692                                        .with_z(alt + platform_3_height),
1693                                    max: Vec2::new(wpos.x + 8, wpos.y + 7)
1694                                        .with_z(alt + roof_height),
1695                                }),
1696                            );
1697                        //skirt
1698                        let skirt1 = painter
1699                            .aabb(Aabb {
1700                                min: Vec2::new(wpos.x + 1, wpos.y)
1701                                    .with_z(alt + platform_1_height - 3),
1702                                max: Vec2::new(wpos.x + 4, wpos.y + 1)
1703                                    .with_z(alt + platform_1_height + 1),
1704                            })
1705                            .without(painter.line(
1706                                Vec2::new(wpos.x + 1, wpos.y).with_z(alt + platform_1_height - 1),
1707                                Vec2::new(wpos.x + 1, wpos.y).with_z(alt + platform_1_height - 3),
1708                                1.0,
1709                            ))
1710                            .without(painter.line(
1711                                Vec2::new(wpos.x + 3, wpos.y).with_z(alt + platform_1_height - 2),
1712                                Vec2::new(wpos.x + 3, wpos.y).with_z(alt + platform_1_height - 3),
1713                                1.0,
1714                            ));
1715                        let skirt2 = skirt1
1716                            .translate(Vec3::new(6, 0, 0))
1717                            .rotate_about_min(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1));
1718                        let skirt3 = skirt2.translate(Vec3::new(3, 0, 0));
1719
1720                        let skirtside1 = skirt1.union(skirt2).union(skirt3);
1721                        let skirtside2 = skirtside1
1722                            .rotate_about_min(Mat3::new(0, -1, 0, 1, 0, 0, 0, 0, 1))
1723                            .translate(Vec3::new(0, 1, 0));
1724
1725                        let skirtcorner1 = skirtside1.union(skirtside2);
1726                        let skirtcorner2 = skirtcorner1
1727                            .rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1))
1728                            .translate(Vec3::new(11, 11, 0));
1729
1730                        let skirt1 = skirtcorner1.union(skirtcorner2);
1731                        let skirt2 = skirt1
1732                            .rotate_about_min(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1))
1733                            .translate(Vec3::new(0, 11, 6));
1734
1735                        let skirt = skirt1.union(skirt2).union(roof);
1736                        painter.fill(
1737                            skirt,
1738                            Fill::Brick(BlockKind::Leaves, Rgb::new(22, 36, 20), 24),
1739                        );
1740
1741                        let towerplatform = platform_1.union(platform_2).union(platform_3);
1742
1743                        painter.fill(
1744                            towerplatform,
1745                            Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 24),
1746                        );
1747                        let towervertical = supports
1748                            .union(platform_1_supports)
1749                            .union(platform_2_supports)
1750                            .union(roof_pillars);
1751
1752                        painter.fill(
1753                            towervertical,
1754                            Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 24),
1755                        );
1756                    },
1757                }
1758            });
1759
1760        // Create tunnels beneath the fortification
1761        let wood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 24);
1762        let dirt = Fill::Brick(BlockKind::Earth, Rgb::new(55, 25, 8), 24);
1763        let alt = land.get_alt_approx(self.origin) as i32;
1764        let stump = painter
1765            .cylinder(Aabb {
1766                min: (self.tunnels.start.xy() - 10).with_z(alt - 15),
1767                max: (self.tunnels.start.xy() + 11).with_z(alt + 10),
1768            })
1769            .union(painter.cylinder(Aabb {
1770                min: (self.tunnels.start.xy() - 11).with_z(alt),
1771                max: (self.tunnels.start.xy() + 12).with_z(alt + 2),
1772            }))
1773            .union(painter.line(
1774                self.tunnels.start.xy().with_z(alt + 10),
1775                (self.tunnels.start.xy() + 15).with_z(alt - 8),
1776                5.0,
1777            ))
1778            .union(painter.line(
1779                self.tunnels.start.xy().with_z(alt + 10),
1780                Vec2::new(self.tunnels.start.x - 15, self.tunnels.start.y + 15).with_z(alt - 8),
1781                5.0,
1782            ))
1783            .union(painter.line(
1784                self.tunnels.start.xy().with_z(alt + 10),
1785                Vec2::new(self.tunnels.start.x + 15, self.tunnels.start.y - 15).with_z(alt - 8),
1786                5.0,
1787            ))
1788            .union(painter.line(
1789                self.tunnels.start.xy().with_z(alt + 10),
1790                (self.tunnels.start.xy() - 15).with_z(alt - 8),
1791                5.0,
1792            ))
1793            .without(
1794                painter.sphere_with_radius((self.tunnels.start.xy() + 10).with_z(alt + 26), 18.0),
1795            )
1796            .without(
1797                painter.sphere_with_radius((self.tunnels.start.xy() - 10).with_z(alt + 26), 18.0),
1798            );
1799        let entrance_hollow = painter.line(
1800            self.tunnels.start,
1801            self.tunnels.start.xy().with_z(alt + 10),
1802            9.0,
1803        );
1804
1805        let boss_room_offset =
1806            (self.tunnels.end.xy() - self.tunnels.start.xy()).map(|e| if e < 0 { -20 } else { 20 });
1807
1808        let boss_room = painter.ellipsoid(Aabb {
1809            min: (self.tunnels.end.xy() + boss_room_offset - 30).with_z(self.tunnels.end.z - 10),
1810            max: (self.tunnels.end.xy() + boss_room_offset + 30).with_z(self.tunnels.end.z + 10),
1811        });
1812
1813        let boss_room_clear = painter.ellipsoid(Aabb {
1814            min: (self.tunnels.end.xy() + boss_room_offset - 29).with_z(self.tunnels.end.z - 9),
1815            max: (self.tunnels.end.xy() + boss_room_offset + 29).with_z(self.tunnels.end.z + 9),
1816        });
1817
1818        let random_field = RandomField::new(self.seed);
1819
1820        let mut tunnels = Vec::new();
1821        let mut path_tunnels = Vec::new();
1822        let mut tunnels_clear = Vec::new();
1823        let mut ferns = Vec::new();
1824        let mut velorite_ores = Vec::new();
1825        let mut fire_bowls = Vec::new();
1826        for branch in self.tunnels.branches.iter() {
1827            let tunnel_radius_i32 = 4 + branch.0.x % 4;
1828            let in_path =
1829                self.tunnels.path.contains(&branch.0) && self.tunnels.path.contains(&branch.1);
1830            let tunnel_radius = tunnel_radius_i32 as f32;
1831            let start = branch.0;
1832            let end = branch.1;
1833            let ctrl0_offset = start.x % 6 * if start.y % 2 == 0 { -1 } else { 1 };
1834            let ctrl1_offset = end.x % 6 * if end.y % 2 == 0 { -1 } else { 1 };
1835            let ctrl0 = (((start + end) / 2) + start) / 2 + ctrl0_offset;
1836            let ctrl1 = (((start + end) / 2) + end) / 2 + ctrl1_offset;
1837            let tunnel = painter.cubic_bezier(start, ctrl0, ctrl1, end, tunnel_radius);
1838            let tunnel_clear = painter.cubic_bezier(start, ctrl0, ctrl1, end, tunnel_radius - 1.0);
1839            let mut fern_scatter = painter.empty();
1840            let mut velorite_scatter = painter.empty();
1841            let mut fire_bowl_scatter = painter.empty();
1842
1843            let min_z = branch.0.z.min(branch.1.z);
1844            let max_z = branch.0.z.max(branch.1.z);
1845            for i in branch.0.x - tunnel_radius_i32..branch.1.x + tunnel_radius_i32 {
1846                for j in branch.0.y - tunnel_radius_i32..branch.1.y + tunnel_radius_i32 {
1847                    if random_field.get(Vec3::new(i, j, min_z)) % 6 == 0 {
1848                        fern_scatter = fern_scatter.union(painter.aabb(Aabb {
1849                            min: Vec3::new(i, j, min_z),
1850                            max: Vec3::new(i + 1, j + 1, max_z),
1851                        }));
1852                    }
1853                    if random_field.get(Vec3::new(i, j, min_z)) % 30 == 0 {
1854                        velorite_scatter = velorite_scatter.union(painter.aabb(Aabb {
1855                            min: Vec3::new(i, j, min_z),
1856                            max: Vec3::new(i + 1, j + 1, max_z),
1857                        }));
1858                    }
1859                    if random_field.get(Vec3::new(i, j, min_z)) % 30 == 0 {
1860                        fire_bowl_scatter = fire_bowl_scatter.union(painter.aabb(Aabb {
1861                            min: Vec3::new(i, j, min_z),
1862                            max: Vec3::new(i + 1, j + 1, max_z),
1863                        }));
1864                    }
1865                }
1866            }
1867            let fern = tunnel_clear.intersect(fern_scatter);
1868            let velorite = tunnel_clear.intersect(velorite_scatter);
1869            let fire_bowl = tunnel_clear.intersect(fire_bowl_scatter);
1870            if in_path {
1871                path_tunnels.push(tunnel);
1872            } else {
1873                tunnels.push(tunnel);
1874            }
1875            tunnels_clear.push(tunnel_clear);
1876            ferns.push(fern);
1877            velorite_ores.push(velorite);
1878            fire_bowls.push(fire_bowl);
1879        }
1880
1881        let mut rooms = Vec::new();
1882        let mut rooms_clear = Vec::new();
1883        let mut chests_ori_0 = Vec::new();
1884        let mut chests_ori_2 = Vec::new();
1885        let mut chests_ori_4 = Vec::new();
1886        for terminal in self.tunnels.terminals.iter() {
1887            let room = painter.sphere(Aabb {
1888                min: terminal - 8,
1889                max: terminal + 8 + 1,
1890            });
1891            let room_clear = painter.sphere(Aabb {
1892                min: terminal - 7,
1893                max: terminal + 7 + 1,
1894            });
1895            rooms.push(room);
1896            rooms_clear.push(room_clear);
1897
1898            // FIRE!!!!!
1899            let fire_bowl = painter.aabb(Aabb {
1900                min: terminal.with_z(terminal.z - 7),
1901                max: terminal.with_z(terminal.z - 7) + 1,
1902            });
1903            fire_bowls.push(fire_bowl);
1904
1905            // Chest
1906            let chest_seed = random_field.get(*terminal) % 5;
1907            if chest_seed < 4 {
1908                let chest_pos = Vec3::new(terminal.x, terminal.y - 4, terminal.z - 6);
1909                let chest = painter.aabb(Aabb {
1910                    min: chest_pos,
1911                    max: chest_pos + 1,
1912                });
1913                chests_ori_4.push(chest);
1914                if chest_seed < 2 {
1915                    let chest_pos = Vec3::new(terminal.x, terminal.y + 4, terminal.z - 6);
1916                    let chest = painter.aabb(Aabb {
1917                        min: chest_pos,
1918                        max: chest_pos + 1,
1919                    });
1920                    chests_ori_0.push(chest);
1921                    if chest_seed < 1 {
1922                        let chest_pos = Vec3::new(terminal.x - 4, terminal.y, terminal.z - 6);
1923                        let chest = painter.aabb(Aabb {
1924                            min: chest_pos,
1925                            max: chest_pos + 1,
1926                        });
1927                        chests_ori_2.push(chest);
1928                    }
1929                }
1930            }
1931        }
1932        tunnels
1933            .into_iter()
1934            .chain(rooms)
1935            .chain(core::iter::once(boss_room))
1936            .chain(core::iter::once(stump))
1937            .for_each(|prim| prim.fill(wood.clone()));
1938        path_tunnels.into_iter().for_each(|t| t.fill(dirt.clone()));
1939
1940        // Clear out insides after filling the walls in
1941        let mut sprite_clear = Vec::new();
1942        tunnels_clear
1943            .into_iter()
1944            .chain(rooms_clear)
1945            .chain(core::iter::once(boss_room_clear))
1946            .for_each(|prim| {
1947                sprite_clear.push(prim.translate(Vec3::new(0, 0, 1)).intersect(prim));
1948
1949                prim.clear();
1950            });
1951
1952        // Place sprites
1953        ferns
1954            .into_iter()
1955            .for_each(|prim| prim.fill(Fill::Block(Block::air(SpriteKind::JungleFern))));
1956        velorite_ores
1957            .into_iter()
1958            .for_each(|prim| prim.fill(Fill::Block(Block::air(SpriteKind::Velorite))));
1959        fire_bowls
1960            .into_iter()
1961            .for_each(|prim| prim.fill(Fill::Block(Block::air(SpriteKind::FireBowlGround))));
1962
1963        chests_ori_0.into_iter().for_each(|prim| {
1964            prim.fill(Fill::Block(
1965                Block::air(SpriteKind::DungeonChest0).with_ori(0).unwrap(),
1966            ))
1967        });
1968        chests_ori_2.into_iter().for_each(|prim| {
1969            prim.fill(Fill::Block(
1970                Block::air(SpriteKind::DungeonChest0).with_ori(2).unwrap(),
1971            ))
1972        });
1973        chests_ori_4.into_iter().for_each(|prim| {
1974            prim.fill(Fill::Block(
1975                Block::air(SpriteKind::DungeonChest0).with_ori(4).unwrap(),
1976            ))
1977        });
1978
1979        entrance_hollow.clear();
1980        sprite_clear.into_iter().for_each(|prim| prim.clear());
1981    }
1982}
1983
1984fn gnarling_mugger<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
1985    EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
1986        "common.entity.dungeon.gnarling.mugger",
1987        rng,
1988        None,
1989    )
1990}
1991
1992fn gnarling_stalker<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
1993    EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
1994        "common.entity.dungeon.gnarling.stalker",
1995        rng,
1996        None,
1997    )
1998}
1999
2000fn gnarling_logger<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2001    EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2002        "common.entity.dungeon.gnarling.logger",
2003        rng,
2004        None,
2005    )
2006}
2007
2008fn random_gnarling<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2009    match rng.random_range(0..4) {
2010        0 => gnarling_stalker(pos, rng),
2011        1 => gnarling_mugger(pos, rng),
2012        _ => gnarling_logger(pos, rng),
2013    }
2014}
2015
2016fn melee_gnarling<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2017    match rng.random_range(0..2) {
2018        0 => gnarling_mugger(pos, rng),
2019        _ => gnarling_logger(pos, rng),
2020    }
2021}
2022
2023fn gnarling_chieftain<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2024    EntityInfo::at(pos.map(|x| x as f32))
2025        .with_asset_expect("common.entity.dungeon.gnarling.chieftain", rng, None)
2026        .with_no_flee()
2027}
2028
2029fn deadwood<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2030    EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2031        "common.entity.wild.aggressive.deadwood",
2032        rng,
2033        None,
2034    )
2035}
2036
2037fn mandragora<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2038    EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2039        "common.entity.dungeon.gnarling.mandragora",
2040        rng,
2041        None,
2042    )
2043}
2044
2045fn wood_golem<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2046    EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2047        "common.entity.dungeon.gnarling.woodgolem",
2048        rng,
2049        None,
2050    )
2051}
2052
2053fn harvester_boss<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2054    EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2055        "common.entity.dungeon.gnarling.harvester",
2056        rng,
2057        None,
2058    )
2059}
2060
2061#[derive(Default)]
2062struct Tunnels {
2063    start: Vec3<i32>,
2064    end: Vec3<i32>,
2065    branches: Vec<(Vec3<i32>, Vec3<i32>)>,
2066    path: Vec<Vec3<i32>>,
2067    terminals: Vec<Vec3<i32>>,
2068}
2069
2070impl Tunnels {
2071    /// Attempts to find a path from a `start` to the `end` using a rapidly
2072    /// exploring random tree (RRT). A point is sampled from an AABB extending
2073    /// slightly beyond the start and end in the x and y axes and below the
2074    /// start in the z axis to slightly below the end. Nodes are stored in a
2075    /// k-d tree for quicker nearest node calculations. A maximum of 7000
2076    /// points are sampled until the tree connects the start to the end. A
2077    /// final path is then reconstructed from the nodes. Returns a `Tunnels`
2078    /// struct of the RRT branches, the complete path, and the location of
2079    /// dead ends. Each branch is a tuple of the start and end locations of
2080    /// each segment. The path is a vector of all the nodes along the
2081    /// complete path from the `start` to the `end`.
2082    fn new<F>(
2083        start: Vec3<i32>,
2084        end: Vec3<i32>,
2085        is_valid_edge: F,
2086        radius_range: (f32, f32),
2087        rng: &mut impl Rng,
2088    ) -> Option<Self>
2089    where
2090        F: Fn(Vec3<i32>, Vec3<i32>) -> bool,
2091    {
2092        const MAX_POINTS: usize = 7000;
2093        let mut nodes = Vec::new();
2094        let mut node_index: usize = 0;
2095
2096        // HashMap<ChildNode, ParentNode>
2097        let mut parents = HashMap::new();
2098
2099        let mut kdtree: KdTree<f32, usize, 3, 32, u32> = KdTree::with_capacity(MAX_POINTS);
2100        let startf = start.map(|a| (a + 1) as f32);
2101        let endf = end.map(|a| (a + 1) as f32);
2102
2103        let min = Vec3::new(
2104            startf.x.min(endf.x),
2105            startf.y.min(endf.y),
2106            startf.z.min(endf.z),
2107        );
2108        let max = Vec3::new(
2109            startf.x.max(endf.x),
2110            startf.y.max(endf.y),
2111            startf.z.max(endf.z),
2112        );
2113
2114        kdtree.add(&[startf.x, startf.y, startf.z], node_index);
2115        nodes.push(startf);
2116        node_index += 1;
2117        let mut connect = false;
2118
2119        for _i in 0..MAX_POINTS {
2120            let radius: f32 = rng.random_range(radius_range.0..radius_range.1);
2121            let radius_sqrd = radius.powi(2);
2122            if connect {
2123                break;
2124            }
2125            let sampled_point = Vec3::new(
2126                rng.random_range(min.x - 20.0..max.x + 20.0),
2127                rng.random_range(min.y - 20.0..max.y + 20.0),
2128                rng.random_range(min.z - 20.0..max.z - 7.0),
2129            );
2130            let nearest_index = kdtree
2131                .nearest_one::<SquaredEuclidean>(&[
2132                    sampled_point.x,
2133                    sampled_point.y,
2134                    sampled_point.z,
2135                ])
2136                .item;
2137            let nearest = nodes[nearest_index];
2138            let dist_sqrd = sampled_point.distance_squared(nearest);
2139            let new_point = if dist_sqrd > radius_sqrd {
2140                nearest + (sampled_point - nearest).normalized().map(|a| a * radius)
2141            } else {
2142                sampled_point
2143            };
2144            if is_valid_edge(
2145                nearest.map(|e| e.floor() as i32),
2146                new_point.map(|e| e.floor() as i32),
2147            ) {
2148                kdtree.add(&[new_point.x, new_point.y, new_point.z], node_index);
2149                nodes.push(new_point);
2150                parents.insert(node_index, nearest_index);
2151                node_index += 1;
2152            }
2153            if new_point.distance_squared(endf) < radius_sqrd {
2154                connect = true;
2155            }
2156        }
2157
2158        let mut path = Vec::new();
2159        let nearest_index = kdtree
2160            .nearest_one::<SquaredEuclidean>(&[endf.x, endf.y, endf.z])
2161            .item;
2162        kdtree.add(&[endf.x, endf.y, endf.z], node_index);
2163        nodes.push(endf);
2164        parents.insert(node_index, nearest_index);
2165        path.push(endf);
2166        let mut current_node_index = node_index;
2167        while current_node_index > 0 {
2168            current_node_index = *parents.get(&current_node_index).unwrap();
2169            path.push(nodes[current_node_index]);
2170        }
2171
2172        let mut terminals = Vec::new();
2173        let last = nodes.len() - 1;
2174        for (node_id, node_pos) in nodes.iter().enumerate() {
2175            if !parents.values().any(|e| e == &node_id) && node_id != 0 && node_id != last {
2176                terminals.push(node_pos.map(|e| e.floor() as i32));
2177            }
2178        }
2179
2180        let branches = parents
2181            .iter()
2182            .map(|(a, b)| {
2183                (
2184                    nodes[*a].map(|e| e.floor() as i32),
2185                    nodes[*b].map(|e| e.floor() as i32),
2186                )
2187            })
2188            .collect::<Vec<(Vec3<i32>, Vec3<i32>)>>();
2189        let path = path
2190            .iter()
2191            .map(|a| a.map(|e| e.floor() as i32))
2192            .collect::<Vec<Vec3<i32>>>();
2193
2194        Some(Self {
2195            start,
2196            end,
2197            branches,
2198            path,
2199            terminals,
2200        })
2201    }
2202}
2203
2204#[cfg(test)]
2205mod tests {
2206    use super::*;
2207
2208    #[test]
2209    fn test_creating_entities() {
2210        let pos = Vec3::zero();
2211        let mut rng = rand::rng();
2212
2213        gnarling_mugger(pos, &mut rng);
2214        gnarling_stalker(pos, &mut rng);
2215        gnarling_logger(pos, &mut rng);
2216        gnarling_chieftain(pos, &mut rng);
2217        deadwood(pos, &mut rng);
2218        mandragora(pos, &mut rng);
2219        wood_golem(pos, &mut rng);
2220        harvester_boss(pos, &mut rng);
2221    }
2222}