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