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_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 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 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!(),
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 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 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 let forbidden_indices = [gate_index, chieftain_indices[0], chieftain_indices[1]];
163 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 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 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 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 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 _ => 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 pub fn apply_supplement(
317 &self,
318 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 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 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 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 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 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 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 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 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 for (point, next_point) in self.wall_segments.iter() {
553 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 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 self.wall_towers.iter().for_each(|point| {
670 let wpos = point.xy() + self.origin;
671
672 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 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 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 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 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 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 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 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 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 let floor_pos = wpos.with_z(alt + 1);
893 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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(¤t_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}