1use super::*;
2use crate::{Land, site::generation::PrimitiveTransform};
3use common::{
4 generation::EntityInfo,
5 terrain::{BiomeKind, Block, BlockKind},
6};
7use num::integer::Roots;
8use rand::prelude::*;
9use vek::*;
10
11enum RoofKind {
12 Crenelated,
13 Hipped,
14}
15
16struct HeightenedViaduct {
17 slope_inv: i32,
18 bridge_start_offset: i32,
19 vault_spacing: i32,
20 vault_size: (i32, i32),
21 side_vault_size: (i32, i32),
22 holes: bool,
23}
24
25impl HeightenedViaduct {
26 fn random(rng: &mut impl Rng, height: i32) -> Self {
27 let vault_spacing = *[3, 4, 5, 6].choose_mut(rng).unwrap();
28 Self {
29 slope_inv: rng.random_range(6..=8),
30 bridge_start_offset: rng.random_range({
31 let min = (5 - height / 3).max(0);
32 min..=(12 - height).max(min)
33 }),
34 vault_spacing,
35 vault_size: *[(3, 16), (1, 4), (1, 4), (1, 4), (5, 32), (5, 32)]
36 .choose_mut(rng)
37 .unwrap(),
38 side_vault_size: *[(4, 5), (7, 10), (7, 10), (13, 20)]
39 .choose_mut(rng)
40 .unwrap(),
41 holes: vault_spacing >= 4 && vault_spacing % 2 == 0 && rng.random_bool(0.8),
42 }
43 }
44}
45
46enum BridgeKind {
47 Flat,
48 Tower(RoofKind),
49 Short,
50 HeightenedViaduct(HeightenedViaduct),
51 HangBridge,
52}
53
54impl BridgeKind {
55 fn random(
56 rng: &mut impl Rng,
57 start: Vec3<i32>,
58 start_dist: i32,
59 end: Vec3<i32>,
60 end_dist: i32,
61 water_alt: i32,
62 ) -> BridgeKind {
63 let len = (start.xy() - end.xy()).map(|e| e.abs()).reduce_max();
64 let height = end.z - start.z;
65 let down = start.z - water_alt;
66 (0..=4)
67 .filter_map(|bridge| match bridge {
68 0 if height >= 16 => Some(BridgeKind::Tower(match rng.random_range(0..=2) {
69 0 => RoofKind::Crenelated,
70 _ => RoofKind::Hipped,
71 })),
72 1 if len < 60 => Some(BridgeKind::Short),
73 2 if len >= 50
74 && height < 13
75 && down < 20
76 && ((start_dist > 13 && end_dist > 13)
77 || (start_dist - end_dist).abs() < 6) =>
78 {
79 Some(BridgeKind::HeightenedViaduct(HeightenedViaduct::random(
80 rng, height,
81 )))
82 },
83 3 if height < 10 && down > 10 => Some(BridgeKind::HangBridge),
84 4 if down > 8 => Some(BridgeKind::Flat),
85 _ => None,
86 })
87 .collect::<Vec<_>>()
88 .into_iter()
89 .choose(rng)
90 .unwrap_or(BridgeKind::Flat)
91 }
92
93 fn width(&self) -> i32 {
94 match self {
95 BridgeKind::HangBridge => 2,
96 _ => 8,
97 }
98 }
99}
100
101fn aabb(min: Vec3<i32>, max: Vec3<i32>) -> Aabb<i32> {
102 let aabb = Aabb { min, max }.made_valid();
103 Aabb {
104 min: aabb.min,
105 max: aabb.max + 1,
106 }
107}
108
109fn render_short(bridge: &Bridge, painter: &Painter) {
110 let (bridge_fill, edge_fill) = match bridge.biome {
111 BiomeKind::Desert => (
112 Fill::Block(Block::new(BlockKind::Rock, Rgb::new(212, 191, 142))),
113 Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(190))),
114 ),
115 _ => (
116 Fill::Brick(BlockKind::Rock, Rgb::gray(70), 25),
117 Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(130))),
118 ),
119 };
120
121 let bridge_width = 3;
122
123 let orth_dir = bridge.dir.orthogonal();
124
125 let orthogonal = orth_dir.to_vec2();
126 let forward = bridge.dir.to_vec2();
127
128 let len = (bridge.start.xy() - bridge.end.xy())
129 .map(|e| e.abs())
130 .reduce_max();
131 let inset = 4;
132
133 let top = bridge.end.z + (len / 5).max(8) - inset;
134
135 let side = orthogonal * bridge_width;
136
137 let remove = painter.vault(
138 aabb(
139 (bridge.start.xy() - side + forward * inset).with_z(bridge.start.z),
140 (bridge.end.xy() + side - forward * inset).with_z(top - 2),
141 ),
142 orth_dir,
143 );
144
145 let up_ramp = |point: Vec3<i32>, dir: Dir, side_len: i32| {
148 let forward = dir.to_vec2();
149 let side = dir.orthogonal().to_vec2() * side_len;
150 let ramp_in = top - point.z;
151 painter
152 .ramp(
153 aabb(
154 point - side,
155 (point.xy() + side + forward * ramp_in).with_z(top),
156 ),
157 dir,
158 )
159 .union(painter.aabb(aabb(
160 (point - side).with_z(point.z - 4),
161 point + side + forward * ramp_in,
162 )))
163 };
164
165 let bridge_prim = |side_len: i32| {
166 let side = orthogonal * side_len;
167 painter
168 .aabb(aabb(
169 (bridge.start.xy() - side + forward * (top - bridge.start.z))
170 .with_z(bridge.start.z),
171 (bridge.end.xy() + side - forward * (top - bridge.end.z)).with_z(top),
172 ))
173 .union(up_ramp(bridge.start, bridge.dir, side_len).union(up_ramp(
174 bridge.end,
175 -bridge.dir,
176 side_len,
177 )))
178 };
179
180 let b = bridge_prim(bridge_width);
181
182 b.without(remove).fill(bridge_fill);
198
199 let prim = bridge_prim(bridge_width + 1);
200
201 prim.translate(Vec3::unit_z())
202 .without(prim)
203 .without(painter.aabb(aabb(
204 bridge.start - side - forward,
205 (bridge.end.xy() + side + forward).with_z(top + 1),
206 )))
207 .fill(edge_fill);
208}
209
210fn render_flat(bridge: &Bridge, painter: &Painter) {
211 let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50)));
212 let light_rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(130)));
213
214 let orth_dir = bridge.dir.orthogonal();
215
216 let orthogonal = orth_dir.to_vec2();
217 let forward = bridge.dir.to_vec2();
218
219 let height = bridge.end.z - bridge.start.z;
220
221 let bridge_width = bridge.width();
222 let side = orthogonal * bridge_width;
223
224 let aabr = Aabr {
225 min: bridge.start.xy() - side,
226 max: bridge.end.xy() + side,
227 }
228 .made_valid();
229
230 let [ramp_aabr, aabr] = bridge.dir.split_aabr_offset(aabr, height);
231
232 let ramp_prim = |ramp_aabr: Aabr<i32>, offset: i32| {
233 painter
234 .aabb(aabb(
235 ramp_aabr.min.with_z(bridge.start.z - 10 + offset),
236 ramp_aabr.max.with_z(bridge.start.z - 1 + offset),
237 ))
238 .union(painter.ramp(
239 aabb(
240 ramp_aabr.min.with_z(bridge.start.z + offset),
241 ramp_aabr.max.with_z(bridge.end.z + offset),
242 ),
243 bridge.dir,
244 ))
245 };
246
247 ramp_prim(ramp_aabr, 1).fill(light_rock.clone());
248
249 let ramp_aabr = orth_dir
250 .opposite()
251 .trim_aabr(orth_dir.trim_aabr(ramp_aabr, 1), 1);
252 ramp_prim(ramp_aabr, 5).clear();
253 ramp_prim(ramp_aabr, 0).fill(rock.clone());
254
255 let vault_width = 12;
256 let vault_offset = 5;
257 let bridge_thickness = 4;
258
259 let [vault, _] = bridge.dir.split_aabr_offset(aabr, vault_width);
260
261 let len = bridge.dir.select(aabr.size());
262 let true_offset = vault_width + vault_offset;
263 let n = (len / true_offset).max(1);
264 let p = len / n;
265
266 let holes = painter
267 .vault(
268 aabb(
269 vault.min.with_z(bridge.center.z - 20),
270 vault.max.with_z(bridge.end.z - bridge_thickness - 1),
271 ),
272 orth_dir,
273 )
274 .repeat((forward * p).with_z(0), n as u32);
275
276 painter
277 .aabb(aabb(
278 aabr.min.with_z(bridge.center.z - 10),
279 aabr.max.with_z(bridge.end.z + 1),
280 ))
281 .without(holes)
282 .fill(light_rock);
283
284 let aabr = orth_dir
285 .opposite()
286 .trim_aabr(orth_dir.trim_aabr(aabr, 1), 1);
287 painter
288 .aabb(aabb(
289 aabr.min.with_z(bridge.end.z + 1),
290 aabr.max.with_z(bridge.end.z + 8),
291 ))
292 .clear();
293
294 painter
295 .aabb(aabb(
296 aabr.min.with_z(bridge.end.z),
297 aabr.max.with_z(bridge.end.z),
298 ))
299 .fill(rock);
300}
301
302fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &HeightenedViaduct) {
303 let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50)));
304 let light_rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(130)));
305 let orth_dir = bridge.dir.orthogonal();
306
307 let orthogonal = orth_dir.to_vec2();
308 let forward = bridge.dir.to_vec2();
309
310 let slope_inv = data.slope_inv;
311
312 let len = (bridge.start.xy() - bridge.end.xy())
313 .map(|e| e.abs())
314 .reduce_max();
315
316 let bridge_start_z = bridge.end.z + data.bridge_start_offset;
317 let bridge_top = bridge_start_z + len / slope_inv / 2;
318
319 let bridge_width = bridge.width();
320 let side = orthogonal * bridge_width;
321
322 let aabr = Aabr {
323 min: bridge.start.xy() - side,
324 max: bridge.end.xy() + side,
325 }
326 .made_valid();
327
328 let [_start_aabr, rest] = bridge
329 .dir
330 .split_aabr_offset(aabr, bridge_start_z - bridge.start.z);
331 let [_end_aabr, bridge_aabr] =
332 (-bridge.dir).split_aabr_offset(rest, bridge_start_z - bridge.end.z);
333 let under = bridge.center.z - 15;
334
335 let bridge_prim = |bridge_width: i32| {
336 let side = orthogonal * bridge_width;
337
338 let aabr = Aabr {
339 min: bridge.start.xy() - side,
340 max: bridge.end.xy() + side,
341 }
342 .made_valid();
343
344 let [start_aabr, rest] = bridge
345 .dir
346 .split_aabr_offset(aabr, bridge_start_z - bridge.start.z);
347 let [end_aabr, bridge_aabr] =
348 (-bridge.dir).split_aabr_offset(rest, bridge_start_z - bridge.end.z);
349 let [bridge_start, bridge_end] = bridge
350 .dir
351 .split_aabr_offset(bridge_aabr, bridge.dir.select(bridge_aabr.size()) / 2);
352
353 let ramp_in_aabr = |aabr: Aabr<i32>, dir: Dir, zmin, zmax| {
354 let inset = dir.select(aabr.size());
355 painter.ramp_inset(
356 aabb(aabr.min.with_z(zmin), aabr.max.with_z(zmax)),
357 inset,
358 dir,
359 )
360 };
361
362 ramp_in_aabr(start_aabr, bridge.dir, bridge.start.z, bridge_start_z)
363 .union(
364 ramp_in_aabr(end_aabr, -bridge.dir, bridge.end.z, bridge_start_z)
365 .union(ramp_in_aabr(
366 bridge_start,
367 bridge.dir,
368 bridge_start_z + 1,
369 bridge_top,
370 ))
371 .union(ramp_in_aabr(
372 bridge_end,
373 -bridge.dir,
374 bridge_start_z + 1,
375 bridge_top,
376 )),
377 )
378 .union(
379 painter
380 .aabb(aabb(
381 start_aabr.min.with_z(under),
382 start_aabr.max.with_z(bridge.start.z - 1),
383 ))
384 .union(painter.aabb(aabb(
385 end_aabr.min.with_z(under),
386 end_aabr.max.with_z(bridge.end.z - 1),
387 ))),
388 )
389 .union(painter.aabb(aabb(
390 bridge_aabr.min.with_z(under),
391 bridge_aabr.max.with_z(bridge_start_z),
392 )))
393 };
394
395 let br = bridge_prim(bridge_width - 1);
396 let b = br.without(br.translate(-Vec3::unit_z()));
397
398 let c = bridge_aabr.center();
399 let len = bridge.dir.select(bridge_aabr.size());
400 let vault_size = data.vault_size.0 * len / data.vault_size.1;
401 let side_vault = data.side_vault_size.0 * vault_size / data.side_vault_size.1;
402 let vertical = 5;
403 let spacing = data.vault_spacing;
404 let vault_top = bridge_top - vertical;
405 let side_vault_top = vault_top - (vault_size + spacing + 1 + side_vault) / slope_inv;
406 let side_vault_offset = vault_size + spacing + 1;
407
408 let mut remove = painter.vault(
409 aabb(
410 (c - side - forward * vault_size).with_z(under),
411 (c + side + forward * vault_size).with_z(vault_top),
412 ),
413 orth_dir,
414 );
415
416 if side_vault * 2 + side_vault_offset < len / 2 + 5 {
417 remove = remove.union(
418 painter
419 .vault(
420 aabb(
421 (c - side + forward * side_vault_offset).with_z(under),
422 (c + side + forward * (side_vault * 2 + side_vault_offset))
423 .with_z(side_vault_top),
424 ),
425 orth_dir,
426 )
427 .union(
428 painter.vault(
429 aabb(
430 (c - side - forward * side_vault_offset).with_z(under),
431 (c + side - forward * (side_vault * 2 + side_vault_offset))
432 .with_z(side_vault_top),
433 ),
434 orth_dir,
435 ),
436 ),
437 );
438
439 if data.holes {
440 remove = remove.union(
441 painter
442 .vault(
443 aabb(
444 (c - side + forward * (vault_size + 1)).with_z(side_vault_top - 4),
445 (c + side + forward * (vault_size + spacing))
446 .with_z(side_vault_top + 2),
447 ),
448 orth_dir,
449 )
450 .union(
451 painter.vault(
452 aabb(
453 (c - side - forward * (vault_size + 1)).with_z(side_vault_top - 4),
454 (c + side - forward * (vault_size + spacing))
455 .with_z(side_vault_top + 2),
456 ),
457 orth_dir,
458 ),
459 ),
460 );
461 }
462 }
463
464 bridge_prim(bridge_width).without(remove).fill(rock);
465 b.translate(-Vec3::unit_z()).fill(light_rock);
466
467 br.translate(Vec3::unit_z() * 5)
468 .without(br.translate(-Vec3::unit_z()))
469 .clear();
470
471 let mut rng = rand::rng();
516 if rng.random_bool(0.1) {
517 painter.spawn(
518 EntityInfo::at(c.with_z(vault_top - 2).as_()).with_asset_expect(
519 "common.entity.wild.aggressive.swamp_troll",
520 &mut rng,
521 None,
522 ),
523 );
524 }
525}
526
527fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) {
528 let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50)));
529 let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(40, 28, 20)));
530
531 let tower_size = 5;
532
533 let bridge_width = tower_size - 2;
534
535 let orth_dir = bridge.dir.orthogonal();
536
537 let orthogonal = orth_dir.to_vec2();
538 let forward = bridge.dir.to_vec2();
539
540 let tower_height_extend = 10;
541
542 let tower_end = bridge.end.z + tower_height_extend;
543
544 let tower_center = bridge.start.xy() + forward * tower_size;
545 let tower_aabr = Aabr {
546 min: tower_center - tower_size,
547 max: tower_center + tower_size,
548 };
549
550 let len = (bridge.dir.select(bridge.end.xy()) - bridge.dir.select_aabr(tower_aabr)).abs() - 1;
551
552 painter
553 .aabb(aabb(
554 tower_aabr.min.with_z(bridge.start.z - 5),
555 tower_aabr.max.with_z(tower_end),
556 ))
557 .fill(rock.clone());
558
559 painter
560 .aabb(aabb(
561 (tower_aabr.min + 1).with_z(bridge.start.z),
562 (tower_aabr.max - 1).with_z(tower_end - 1),
563 ))
564 .clear();
565
566 let c = (-bridge.dir).select_aabr_with(tower_aabr, tower_aabr.center());
567 painter
568 .aabb(aabb(
569 (c - orthogonal).with_z(bridge.start.z),
570 (c + orthogonal).with_z(bridge.start.z + 2),
571 ))
572 .clear();
573
574 let ramp_height = 8;
575
576 let ramp_aabb = aabb(
577 (c - forward - orthogonal).with_z(bridge.start.z - 1),
578 (c - forward * ramp_height + orthogonal).with_z(bridge.start.z + ramp_height - 2),
579 );
580
581 painter
582 .aabb(ramp_aabb)
583 .without(painter.ramp(ramp_aabb, -bridge.dir))
584 .clear();
585
586 let c = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center());
587 painter
588 .aabb(aabb(
589 (c - orthogonal).with_z(bridge.end.z),
590 (c + orthogonal).with_z(bridge.end.z + 2),
591 ))
592 .clear();
593
594 let stair_thickness = 2;
595 painter
596 .staircase_in_aabb(
597 aabb(
598 (tower_aabr.min + 1).with_z(bridge.start.z),
599 (tower_aabr.max - 1).with_z(bridge.end.z - 1),
600 ),
601 stair_thickness,
602 bridge.dir.rotated_ccw(),
603 )
604 .fill(rock.clone());
605 let aabr = bridge
606 .dir
607 .rotated_cw()
608 .split_aabr_offset(tower_aabr, stair_thickness + 1)[1];
609
610 painter
611 .aabb(aabb(
612 aabr.min.with_z(bridge.end.z - 1),
613 aabr.max.with_z(bridge.end.z - 1),
614 ))
615 .fill(rock.clone());
616
617 painter
618 .aabb(aabb(
619 tower_aabr.center().with_z(bridge.start.z),
620 tower_aabr.center().with_z(bridge.end.z - 1),
621 ))
622 .fill(rock.clone());
623
624 let offset = tower_size * 2 - 2;
625 let d = 2;
626 let n = (bridge.end.z - bridge.start.z - d) / offset;
627 let p = (bridge.end.z - bridge.start.z - d) / n;
628
629 for i in 1..=n {
630 let c = tower_aabr.center().with_z(bridge.start.z + i * p);
631
632 for dir in Dir::ALL {
633 painter.rotated_sprite(
634 c + dir.to_vec2(),
635 SpriteKind::WallSconce,
636 dir.sprite_ori_legacy(),
637 );
638 }
639 }
640
641 painter.rotated_sprite(
642 (tower_aabr.center() + bridge.dir.to_vec2() * (tower_size - 1))
643 .with_z(bridge.end.z + tower_height_extend / 2),
644 SpriteKind::WallLamp,
645 (-bridge.dir).sprite_ori_legacy(),
646 );
647
648 match roof_kind {
649 RoofKind::Crenelated => {
650 painter
651 .aabb(aabb(
652 (tower_aabr.min - 1).with_z(tower_end + 1),
653 (tower_aabr.max + 1).with_z(tower_end + 2),
654 ))
655 .fill(rock.clone());
656
657 painter
658 .aabbs_around_aabb(
659 aabb(
660 tower_aabr.min.with_z(tower_end + 3),
661 tower_aabr.max.with_z(tower_end + 3),
662 ),
663 1,
664 1,
665 )
666 .fill(rock.clone());
667
668 painter
669 .aabb(aabb(
670 tower_aabr.min.with_z(tower_end + 2),
671 tower_aabr.max.with_z(tower_end + 2),
672 ))
673 .clear();
674
675 painter
676 .aabbs_around_aabb(
677 aabb(
678 (tower_aabr.min + 1).with_z(tower_end + 2),
679 (tower_aabr.max - 1).with_z(tower_end + 2),
680 ),
681 1,
682 4,
683 )
684 .fill(Fill::sprite(SpriteKind::FireBowlGround));
685 },
686 RoofKind::Hipped => {
687 painter
688 .pyramid(aabb(
689 (tower_aabr.min - 1).with_z(tower_end + 1),
690 (tower_aabr.max + 1).with_z(tower_end + 2 + tower_size),
691 ))
692 .fill(wood);
693 },
694 }
695
696 let offset = 15;
697 let thickness = 3;
698
699 let size = (offset - thickness) / 2;
700
701 let n = len / offset;
702 let p = len / n;
703
704 let offset = forward * p;
705
706 let size = bridge_width * orthogonal + forward * size;
707 let start = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center()) + forward;
708 painter
709 .aabb(aabb(
710 (start - orthogonal * bridge_width).with_z(bridge.center.z - 10),
711 (bridge.end + orthogonal * bridge_width).with_z(bridge.end.z - 1),
712 ))
713 .without(
714 painter
715 .vault(
716 aabb(
717 (start + offset / 2 - size).with_z(bridge.center.z - 10),
718 (start + offset / 2 + size).with_z(bridge.end.z - 3),
719 ),
720 orth_dir,
721 )
722 .repeat(offset.with_z(0), n as u32),
723 )
724 .fill(rock);
725
726 painter
727 .aabb(aabb(
728 (start - orthogonal * bridge_width).with_z(bridge.end.z),
729 (bridge.end + orthogonal * bridge_width).with_z(bridge.end.z + 5),
730 ))
731 .clear();
732
733 let light_spacing = 10;
734 let n = len / light_spacing;
735 let p = len / n;
736
737 let start = bridge.end;
738 let offset = -forward * p;
739 for i in 1..=n {
740 let c = start + i * offset;
741
742 painter.sprite(c + orthogonal * bridge_width, SpriteKind::StreetLamp);
743 painter.sprite(c - orthogonal * bridge_width, SpriteKind::StreetLamp);
744 }
745}
746
747fn render_hang(bridge: &Bridge, painter: &Painter) {
748 let orth_dir = bridge.dir.orthogonal();
749
750 let orthogonal = orth_dir.to_vec2();
751 let forward = bridge.dir.to_vec2();
752
753 let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50)));
754 let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(133, 94, 66)));
755
756 let bridge_width = bridge.width();
757 let side = orthogonal * bridge_width;
758
759 let aabr = Aabr {
760 min: bridge.start.xy() - side,
761 max: bridge.end.xy() + side,
762 }
763 .made_valid();
764
765 let top_offset = 4;
766 let top = bridge.end.z + top_offset;
767
768 let [ramp_f, aabr] = bridge.dir.split_aabr_offset(aabr, top - bridge.start.z + 1);
769
770 painter
771 .aabb(aabb(
772 ramp_f.min.with_z(bridge.start.z - 10),
773 ramp_f.max.with_z(bridge.start.z),
774 ))
775 .fill(rock.clone());
776 painter
777 .ramp_inset(
778 aabb(ramp_f.min.with_z(bridge.start.z), ramp_f.max.with_z(top)),
779 top - bridge.start.z + 1,
780 bridge.dir,
781 )
782 .fill(rock.clone());
783
784 let [ramp_b, aabr] = (-bridge.dir).split_aabr_offset(aabr, top_offset + 1);
785 painter
786 .aabb(aabb(
787 ramp_b.min.with_z(bridge.end.z - 10),
788 ramp_b.max.with_z(bridge.end.z),
789 ))
790 .fill(rock.clone());
791 painter
792 .ramp(
793 aabb(ramp_b.min.with_z(bridge.end.z), ramp_b.max.with_z(top)),
794 -bridge.dir,
795 )
796 .fill(rock.clone());
797
798 let len = bridge.dir.select(aabr.size());
799
800 let h = 3 * len.sqrt() / 4;
801
802 let x = len / 2;
803
804 let xsqr = (x * x) as f32;
805 let hsqr = (h * h) as f32;
806 let w = ((xsqr + (xsqr * (4.0 * hsqr + xsqr)).sqrt()) / 2.0)
807 .sqrt()
808 .ceil()
809 + 1.0;
810
811 let bottom = top - (h - (hsqr - hsqr * x as f32 / w).sqrt().ceil() as i32);
812
813 let w = w as i32;
814 let c = aabr.center();
815
816 let cylinder = painter
817 .horizontal_cylinder(
818 aabb(
819 (c - forward * w - side).with_z(bottom),
820 (c + forward * w + side).with_z(bottom + h * 2),
821 ),
822 orth_dir,
823 )
824 .intersect(painter.aabb(aabb(
825 aabr.min.with_z(bottom),
826 aabr.max.with_z(bottom + h * 2),
827 )));
828
829 cylinder.fill(wood.clone());
830
831 cylinder.translate(Vec3::unit_z()).clear();
832
833 let edges = cylinder
834 .without(cylinder.translate(Vec3::unit_z()))
835 .without(painter.aabb(aabb(
836 (c - forward * w - orthogonal * (bridge_width - 1)).with_z(bottom),
837 (c + forward * w + orthogonal * (bridge_width - 1)).with_z(bottom + h * 2),
838 )));
839
840 edges
841 .translate(Vec3::unit_z())
842 .fill(Fill::sprite(SpriteKind::Rope));
843
844 edges.translate(Vec3::unit_z() * 2).fill(wood);
845
846 let column_height = 3;
847 let column_range = top..=top + column_height;
848 painter
849 .column(
850 bridge.dir.select_aabr_with(ramp_f, ramp_f.min),
851 column_range.clone(),
852 )
853 .fill(rock.clone());
854 painter
855 .column(
856 bridge.dir.select_aabr_with(ramp_f, ramp_f.max),
857 column_range.clone(),
858 )
859 .fill(rock.clone());
860 painter
861 .column(
862 (-bridge.dir).select_aabr_with(ramp_b, ramp_b.min),
863 column_range.clone(),
864 )
865 .fill(rock.clone());
866 painter
867 .column(
868 (-bridge.dir).select_aabr_with(ramp_b, ramp_b.max),
869 column_range,
870 )
871 .fill(rock);
872}
873
874pub struct Bridge {
875 pub(crate) original_start: Vec2<i32>,
877 pub(crate) original_end: Vec2<i32>,
879
880 pub(crate) start: Vec3<i32>,
881 pub(crate) end: Vec3<i32>,
882 pub(crate) dir: Dir,
883 center: Vec3<i32>,
884 kind: BridgeKind,
885 biome: BiomeKind,
886}
887
888impl Bridge {
889 pub fn generate(
890 land: &Land,
891 index: IndexRef,
892 rng: &mut impl Rng,
893 site: &Site,
894 start: Vec2<i32>,
895 end: Vec2<i32>,
896 ) -> Self {
897 let original_start = site.tile_wpos(start);
898 let original_end = site.tile_wpos(end);
899
900 let min_water_dist = 5;
901 let find_edge = |start: Vec2<i32>, end: Vec2<i32>| {
902 let mut test_start = start;
903 let dir = Dir::from_vec2(end - start).to_vec2();
904 let mut last_alt = if let Some(col) = land.column_sample(start, index) {
905 col.alt as i32
906 } else {
907 return (
908 test_start.with_z(land.get_alt_approx(start) as i32),
909 i32::MAX,
910 );
911 };
912 let mut step = 0;
913 loop {
914 if let Some(sample) = land.column_sample(test_start + step * dir, index) {
915 let alt = sample.alt as i32;
916 let water_dist = sample.water_dist.unwrap_or(16.0) as i32;
917 if last_alt - alt > 1 + (step + 2) / 3
918 || sample.riverless_alt - sample.alt > 2.0
919 {
920 break (test_start.with_z(last_alt), water_dist);
921 } else {
922 test_start += step * dir;
923
924 if water_dist <= min_water_dist {
925 break (test_start.with_z(alt), water_dist);
926 }
927
928 step = water_dist - min_water_dist;
929
930 last_alt = alt;
931 }
932 } else {
933 break (test_start.with_z(last_alt), i32::MAX);
934 }
935 }
936 };
937
938 let (test_start, start_dist) = find_edge(original_start, original_end);
939
940 let (test_end, end_dist) = find_edge(original_end, original_start);
941
942 let (start, start_dist, end, end_dist) = if test_start.z < test_end.z {
943 (test_start, start_dist, test_end, end_dist)
944 } else {
945 (test_end, end_dist, test_start, start_dist)
946 };
947
948 let center = (start.xy() + end.xy()) / 2;
949 let col = land.column_sample(center, index).unwrap();
950 let center = center.with_z(col.alt as i32);
951 let water_alt = col.water_level as i32;
952 let bridge = BridgeKind::random(rng, start, start_dist, end, end_dist, water_alt);
953 Self {
954 original_start,
955 original_end,
956 start,
957 end,
958 center,
959 dir: Dir::from_vec2(end.xy() - start.xy()),
960 kind: bridge,
961 biome: land
962 .get_chunk_wpos(center.xy())
963 .map_or(BiomeKind::Void, |chunk| chunk.get_biome()),
964 }
965 }
966
967 pub fn width(&self) -> i32 { self.kind.width() }
968}
969
970impl Structure for Bridge {
971 #[cfg(feature = "use-dyn-lib")]
972 const UPDATE_FN: &'static [u8] = b"render_bridge\0";
973
974 #[cfg_attr(feature = "be-dyn-lib", unsafe(export_name = "render_bridge"))]
975 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
976 match &self.kind {
977 BridgeKind::Flat => render_flat(self, painter),
978 BridgeKind::Tower(roof) => render_tower(self, painter, roof),
979 BridgeKind::Short => render_short(self, painter),
980 BridgeKind::HeightenedViaduct(data) => render_heightened_viaduct(self, painter, data),
981 BridgeKind::HangBridge => render_hang(self, painter),
982 }
983 }
984}