Skip to main content

veloren_world/site/plot/
gnarling.rs

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