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