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