veloren_world/site2/plot/
gnarling.rs

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