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