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 light_rock_color = Rgb::gray(130);
212 let surface_color = bridge.surface_color.map(|e| (e * 255.0) as u8);
213 let gradient_center = Vec3::new(
214 bridge.center.x as f32,
215 bridge.center.y as f32,
216 (bridge.center.z + 1) as f32,
217 );
218
219 let light_rock = Fill::GradientBrick(
220 util::gradient::Gradient::new(
221 gradient_center,
222 8.0,
223 util::gradient::Shape::plane(Vec3::unit_z()),
224 (surface_color, light_rock_color),
225 ),
226 BlockKind::Rock,
227 25,
228 );
229 let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50)));
230
231 let orth_dir = bridge.dir.orthogonal();
232
233 let orthogonal = orth_dir.to_vec2();
234 let forward = bridge.dir.to_vec2();
235
236 let height = bridge.end.z - bridge.start.z;
237
238 let bridge_width = bridge.width();
239 let side = orthogonal * bridge_width;
240
241 let aabr = Aabr {
242 min: bridge.start.xy() - side,
243 max: bridge.end.xy() + side,
244 }
245 .made_valid();
246
247 let [ramp_aabr, aabr] = bridge.dir.split_aabr_offset(aabr, height);
248
249 let ramp_prim = |ramp_aabr: Aabr<i32>, offset: i32| {
250 painter
251 .aabb(aabb(
252 ramp_aabr.min.with_z(bridge.start.z - 10 + offset),
253 ramp_aabr.max.with_z(bridge.start.z - 1 + offset),
254 ))
255 .union(painter.ramp(
256 aabb(
257 ramp_aabr.min.with_z(bridge.start.z + offset),
258 ramp_aabr.max.with_z(bridge.end.z + offset),
259 ),
260 bridge.dir,
261 ))
262 };
263
264 ramp_prim(ramp_aabr, 1).fill(light_rock.clone());
265
266 let ramp_aabr = orth_dir
267 .opposite()
268 .trim_aabr(orth_dir.trim_aabr(ramp_aabr, 1), 1);
269 ramp_prim(ramp_aabr, 5).clear();
270 ramp_prim(ramp_aabr, 0).fill(rock.clone());
271
272 let vault_width = 12;
273 let vault_offset = 5;
274 let bridge_thickness = 4;
275
276 let [vault, _] = bridge.dir.split_aabr_offset(aabr, vault_width);
277
278 let len = bridge.dir.select(aabr.size());
279 let true_offset = vault_width + vault_offset;
280 let n = (len / true_offset).max(1);
281 let p = len / n;
282
283 let holes = painter
284 .vault(
285 aabb(
286 vault.min.with_z(bridge.center.z - 20),
287 vault.max.with_z(bridge.end.z - bridge_thickness - 1),
288 ),
289 orth_dir,
290 )
291 .repeat((forward * p).with_z(0), n as u32);
292
293 painter
294 .aabb(aabb(
295 aabr.min.with_z(bridge.center.z - 10),
296 aabr.max.with_z(bridge.end.z + 1),
297 ))
298 .without(holes)
299 .fill(light_rock);
300
301 let aabr = orth_dir
302 .opposite()
303 .trim_aabr(orth_dir.trim_aabr(aabr, 1), 1);
304 painter
305 .aabb(aabb(
306 aabr.min.with_z(bridge.end.z + 1),
307 aabr.max.with_z(bridge.end.z + 8),
308 ))
309 .clear();
310
311 painter
312 .aabb(aabb(
313 aabr.min.with_z(bridge.end.z),
314 aabr.max.with_z(bridge.end.z),
315 ))
316 .fill(rock);
317}
318
319fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &HeightenedViaduct) {
320 let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50)));
321 let light_rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(130)));
322 let orth_dir = bridge.dir.orthogonal();
323
324 let orthogonal = orth_dir.to_vec2();
325 let forward = bridge.dir.to_vec2();
326
327 let slope_inv = data.slope_inv;
328
329 let len = (bridge.start.xy() - bridge.end.xy())
330 .map(|e| e.abs())
331 .reduce_max();
332
333 let bridge_start_z = bridge.end.z + data.bridge_start_offset;
334 let bridge_top = bridge_start_z + len / slope_inv / 2;
335
336 let bridge_width = bridge.width();
337 let side = orthogonal * bridge_width;
338
339 let aabr = Aabr {
340 min: bridge.start.xy() - side,
341 max: bridge.end.xy() + side,
342 }
343 .made_valid();
344
345 let [_start_aabr, rest] = bridge
346 .dir
347 .split_aabr_offset(aabr, bridge_start_z - bridge.start.z);
348 let [_end_aabr, bridge_aabr] =
349 (-bridge.dir).split_aabr_offset(rest, bridge_start_z - bridge.end.z);
350 let under = bridge.center.z - 15;
351
352 let bridge_prim = |bridge_width: i32| {
353 let side = orthogonal * bridge_width;
354
355 let aabr = Aabr {
356 min: bridge.start.xy() - side,
357 max: bridge.end.xy() + side,
358 }
359 .made_valid();
360
361 let [start_aabr, rest] = bridge
362 .dir
363 .split_aabr_offset(aabr, bridge_start_z - bridge.start.z);
364 let [end_aabr, bridge_aabr] =
365 (-bridge.dir).split_aabr_offset(rest, bridge_start_z - bridge.end.z);
366 let [bridge_start, bridge_end] = bridge
367 .dir
368 .split_aabr_offset(bridge_aabr, bridge.dir.select(bridge_aabr.size()) / 2);
369
370 let ramp_in_aabr = |aabr: Aabr<i32>, dir: Dir, zmin, zmax| {
371 let inset = dir.select(aabr.size());
372 painter.ramp_inset(
373 aabb(aabr.min.with_z(zmin), aabr.max.with_z(zmax)),
374 inset,
375 dir,
376 )
377 };
378
379 ramp_in_aabr(start_aabr, bridge.dir, bridge.start.z, bridge_start_z)
380 .union(
381 ramp_in_aabr(end_aabr, -bridge.dir, bridge.end.z, bridge_start_z)
382 .union(ramp_in_aabr(
383 bridge_start,
384 bridge.dir,
385 bridge_start_z + 1,
386 bridge_top,
387 ))
388 .union(ramp_in_aabr(
389 bridge_end,
390 -bridge.dir,
391 bridge_start_z + 1,
392 bridge_top,
393 )),
394 )
395 .union(
396 painter
397 .aabb(aabb(
398 start_aabr.min.with_z(under),
399 start_aabr.max.with_z(bridge.start.z - 1),
400 ))
401 .union(painter.aabb(aabb(
402 end_aabr.min.with_z(under),
403 end_aabr.max.with_z(bridge.end.z - 1),
404 ))),
405 )
406 .union(painter.aabb(aabb(
407 bridge_aabr.min.with_z(under),
408 bridge_aabr.max.with_z(bridge_start_z),
409 )))
410 };
411
412 let br = bridge_prim(bridge_width - 1);
413 let b = br.without(br.translate(-Vec3::unit_z()));
414
415 let c = bridge_aabr.center();
416 let len = bridge.dir.select(bridge_aabr.size());
417 let vault_size = data.vault_size.0 * len / data.vault_size.1;
418 let side_vault = data.side_vault_size.0 * vault_size / data.side_vault_size.1;
419 let vertical = 5;
420 let spacing = data.vault_spacing;
421 let vault_top = bridge_top - vertical;
422 let side_vault_top = vault_top - (vault_size + spacing + 1 + side_vault) / slope_inv;
423 let side_vault_offset = vault_size + spacing + 1;
424
425 let mut remove = painter.vault(
426 aabb(
427 (c - side - forward * vault_size).with_z(under),
428 (c + side + forward * vault_size).with_z(vault_top),
429 ),
430 orth_dir,
431 );
432
433 if side_vault * 2 + side_vault_offset < len / 2 + 5 {
434 remove = remove.union(
435 painter
436 .vault(
437 aabb(
438 (c - side + forward * side_vault_offset).with_z(under),
439 (c + side + forward * (side_vault * 2 + side_vault_offset))
440 .with_z(side_vault_top),
441 ),
442 orth_dir,
443 )
444 .union(
445 painter.vault(
446 aabb(
447 (c - side - forward * side_vault_offset).with_z(under),
448 (c + side - forward * (side_vault * 2 + side_vault_offset))
449 .with_z(side_vault_top),
450 ),
451 orth_dir,
452 ),
453 ),
454 );
455
456 if data.holes {
457 remove = remove.union(
458 painter
459 .vault(
460 aabb(
461 (c - side + forward * (vault_size + 1)).with_z(side_vault_top - 4),
462 (c + side + forward * (vault_size + spacing))
463 .with_z(side_vault_top + 2),
464 ),
465 orth_dir,
466 )
467 .union(
468 painter.vault(
469 aabb(
470 (c - side - forward * (vault_size + 1)).with_z(side_vault_top - 4),
471 (c + side - forward * (vault_size + spacing))
472 .with_z(side_vault_top + 2),
473 ),
474 orth_dir,
475 ),
476 ),
477 );
478 }
479 }
480
481 bridge_prim(bridge_width).without(remove).fill(rock);
482 b.translate(-Vec3::unit_z()).fill(light_rock);
483
484 br.translate(Vec3::unit_z() * 5)
485 .without(br.translate(-Vec3::unit_z()))
486 .clear();
487
488 let mut rng = rand::rng();
533 if rng.random_bool(0.1) {
534 painter.spawn(
535 EntityInfo::at(c.with_z(vault_top - 2).as_()).with_asset_expect(
536 "common.entity.wild.aggressive.swamp_troll",
537 &mut rng,
538 None,
539 ),
540 );
541 }
542}
543
544fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) {
545 let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50)));
546 let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(40, 28, 20)));
547
548 let tower_size = 5;
549
550 let bridge_width = tower_size - 2;
551
552 let orth_dir = bridge.dir.orthogonal();
553
554 let orthogonal = orth_dir.to_vec2();
555 let forward = bridge.dir.to_vec2();
556
557 let tower_height_extend = 10;
558
559 let tower_end = bridge.end.z + tower_height_extend;
560
561 let tower_center = bridge.start.xy() + forward * tower_size;
562 let tower_aabr = Aabr {
563 min: tower_center - tower_size,
564 max: tower_center + tower_size,
565 };
566
567 let len = (bridge.dir.select(bridge.end.xy()) - bridge.dir.select_aabr(tower_aabr)).abs() - 1;
568
569 painter
570 .aabb(aabb(
571 tower_aabr.min.with_z(bridge.start.z - 5),
572 tower_aabr.max.with_z(tower_end),
573 ))
574 .fill(rock.clone());
575
576 painter
577 .aabb(aabb(
578 (tower_aabr.min + 1).with_z(bridge.start.z),
579 (tower_aabr.max - 1).with_z(tower_end - 1),
580 ))
581 .clear();
582
583 let c = (-bridge.dir).select_aabr_with(tower_aabr, tower_aabr.center());
584 painter
585 .aabb(aabb(
586 (c - orthogonal).with_z(bridge.start.z),
587 (c + orthogonal).with_z(bridge.start.z + 2),
588 ))
589 .clear();
590
591 let ramp_height = 8;
592
593 let ramp_aabb = aabb(
594 (c - forward - orthogonal).with_z(bridge.start.z - 1),
595 (c - forward * ramp_height + orthogonal).with_z(bridge.start.z + ramp_height - 2),
596 );
597
598 painter
599 .aabb(ramp_aabb)
600 .without(painter.ramp(ramp_aabb, -bridge.dir))
601 .clear();
602
603 let c = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center());
604 painter
605 .aabb(aabb(
606 (c - orthogonal).with_z(bridge.end.z),
607 (c + orthogonal).with_z(bridge.end.z + 2),
608 ))
609 .clear();
610
611 let stair_thickness = 2;
612 painter
613 .staircase_in_aabb(
614 aabb(
615 (tower_aabr.min + 1).with_z(bridge.start.z),
616 (tower_aabr.max - 1).with_z(bridge.end.z - 1),
617 ),
618 stair_thickness,
619 bridge.dir.rotated_ccw(),
620 )
621 .fill(rock.clone());
622 let aabr = bridge
623 .dir
624 .rotated_cw()
625 .split_aabr_offset(tower_aabr, stair_thickness + 1)[1];
626
627 painter
628 .aabb(aabb(
629 aabr.min.with_z(bridge.end.z - 1),
630 aabr.max.with_z(bridge.end.z - 1),
631 ))
632 .fill(rock.clone());
633
634 painter
635 .aabb(aabb(
636 tower_aabr.center().with_z(bridge.start.z),
637 tower_aabr.center().with_z(bridge.end.z - 1),
638 ))
639 .fill(rock.clone());
640
641 let offset = tower_size * 2 - 2;
642 let d = 2;
643 let n = (bridge.end.z - bridge.start.z - d) / offset;
644 let p = (bridge.end.z - bridge.start.z - d) / n;
645
646 for i in 1..=n {
647 let c = tower_aabr.center().with_z(bridge.start.z + i * p);
648
649 for dir in Dir::ALL {
650 painter.rotated_sprite(
651 c + dir.to_vec2(),
652 SpriteKind::WallSconce,
653 dir.sprite_ori_legacy(),
654 );
655 }
656 }
657
658 painter.rotated_sprite(
659 (tower_aabr.center() + bridge.dir.to_vec2() * (tower_size - 1))
660 .with_z(bridge.end.z + tower_height_extend / 2),
661 SpriteKind::WallLamp,
662 (-bridge.dir).sprite_ori_legacy(),
663 );
664
665 match roof_kind {
666 RoofKind::Crenelated => {
667 painter
668 .aabb(aabb(
669 (tower_aabr.min - 1).with_z(tower_end + 1),
670 (tower_aabr.max + 1).with_z(tower_end + 2),
671 ))
672 .fill(rock.clone());
673
674 painter
675 .aabbs_around_aabb(
676 aabb(
677 tower_aabr.min.with_z(tower_end + 3),
678 tower_aabr.max.with_z(tower_end + 3),
679 ),
680 1,
681 1,
682 )
683 .fill(rock.clone());
684
685 painter
686 .aabb(aabb(
687 tower_aabr.min.with_z(tower_end + 2),
688 tower_aabr.max.with_z(tower_end + 2),
689 ))
690 .clear();
691
692 painter
693 .aabbs_around_aabb(
694 aabb(
695 (tower_aabr.min + 1).with_z(tower_end + 2),
696 (tower_aabr.max - 1).with_z(tower_end + 2),
697 ),
698 1,
699 4,
700 )
701 .fill(Fill::sprite(SpriteKind::FireBowlGround));
702 },
703 RoofKind::Hipped => {
704 painter
705 .pyramid(aabb(
706 (tower_aabr.min - 1).with_z(tower_end + 1),
707 (tower_aabr.max + 1).with_z(tower_end + 2 + tower_size),
708 ))
709 .fill(wood);
710 },
711 }
712
713 let offset = 15;
714 let thickness = 3;
715
716 let size = (offset - thickness) / 2;
717
718 let n = len / offset;
719 let p = len / n;
720
721 let offset = forward * p;
722
723 let size = bridge_width * orthogonal + forward * size;
724 let start = bridge.dir.select_aabr_with(tower_aabr, tower_aabr.center()) + forward;
725 painter
726 .aabb(aabb(
727 (start - orthogonal * bridge_width).with_z(bridge.center.z - 10),
728 (bridge.end + orthogonal * bridge_width).with_z(bridge.end.z - 1),
729 ))
730 .without(
731 painter
732 .vault(
733 aabb(
734 (start + offset / 2 - size).with_z(bridge.center.z - 10),
735 (start + offset / 2 + size).with_z(bridge.end.z - 3),
736 ),
737 orth_dir,
738 )
739 .repeat(offset.with_z(0), n as u32),
740 )
741 .fill(rock);
742
743 painter
744 .aabb(aabb(
745 (start - orthogonal * bridge_width).with_z(bridge.end.z),
746 (bridge.end + orthogonal * bridge_width).with_z(bridge.end.z + 5),
747 ))
748 .clear();
749
750 let light_spacing = 10;
751 let n = len / light_spacing;
752 let p = len / n;
753
754 let start = bridge.end;
755 let offset = -forward * p;
756 for i in 1..=n {
757 let c = start + i * offset;
758
759 painter.sprite(c + orthogonal * bridge_width, SpriteKind::StreetLamp);
760 painter.sprite(c - orthogonal * bridge_width, SpriteKind::StreetLamp);
761 }
762}
763
764fn render_hang(bridge: &Bridge, painter: &Painter) {
765 let orth_dir = bridge.dir.orthogonal();
766
767 let orthogonal = orth_dir.to_vec2();
768 let forward = bridge.dir.to_vec2();
769
770 let rock = Fill::Block(Block::new(BlockKind::Rock, Rgb::gray(50)));
771 let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(133, 94, 66)));
772
773 let bridge_width = bridge.width();
774 let side = orthogonal * bridge_width;
775
776 let aabr = Aabr {
777 min: bridge.start.xy() - side,
778 max: bridge.end.xy() + side,
779 }
780 .made_valid();
781
782 let top_offset = 4;
783 let top = bridge.end.z + top_offset;
784
785 let [ramp_f, aabr] = bridge.dir.split_aabr_offset(aabr, top - bridge.start.z + 1);
786
787 painter
788 .aabb(aabb(
789 ramp_f.min.with_z(bridge.start.z - 10),
790 ramp_f.max.with_z(bridge.start.z),
791 ))
792 .fill(rock.clone());
793 painter
794 .ramp_inset(
795 aabb(ramp_f.min.with_z(bridge.start.z), ramp_f.max.with_z(top)),
796 top - bridge.start.z + 1,
797 bridge.dir,
798 )
799 .fill(rock.clone());
800
801 let [ramp_b, aabr] = (-bridge.dir).split_aabr_offset(aabr, top_offset + 1);
802 painter
803 .aabb(aabb(
804 ramp_b.min.with_z(bridge.end.z - 10),
805 ramp_b.max.with_z(bridge.end.z),
806 ))
807 .fill(rock.clone());
808 painter
809 .ramp(
810 aabb(ramp_b.min.with_z(bridge.end.z), ramp_b.max.with_z(top)),
811 -bridge.dir,
812 )
813 .fill(rock.clone());
814
815 let len = bridge.dir.select(aabr.size());
816
817 let h = 3 * len.sqrt() / 4;
818
819 let x = len / 2;
820
821 let xsqr = (x * x) as f32;
822 let hsqr = (h * h) as f32;
823 let w = ((xsqr + (xsqr * (4.0 * hsqr + xsqr)).sqrt()) / 2.0)
824 .sqrt()
825 .ceil()
826 + 1.0;
827
828 let bottom = top - (h - (hsqr - hsqr * x as f32 / w).sqrt().ceil() as i32);
829
830 let w = w as i32;
831 let c = aabr.center();
832
833 let cylinder = painter
834 .horizontal_cylinder(
835 aabb(
836 (c - forward * w - side).with_z(bottom),
837 (c + forward * w + side).with_z(bottom + h * 2),
838 ),
839 orth_dir,
840 )
841 .intersect(painter.aabb(aabb(
842 aabr.min.with_z(bottom),
843 aabr.max.with_z(bottom + h * 2),
844 )));
845
846 cylinder.fill(wood.clone());
847
848 cylinder.translate(Vec3::unit_z()).clear();
849
850 let edges = cylinder
851 .without(cylinder.translate(Vec3::unit_z()))
852 .without(painter.aabb(aabb(
853 (c - forward * w - orthogonal * (bridge_width - 1)).with_z(bottom),
854 (c + forward * w + orthogonal * (bridge_width - 1)).with_z(bottom + h * 2),
855 )));
856
857 edges
858 .translate(Vec3::unit_z())
859 .fill(Fill::sprite(SpriteKind::Rope));
860
861 edges.translate(Vec3::unit_z() * 2).fill(wood);
862
863 let column_height = 3;
864 let column_range = top..=top + column_height;
865 painter
866 .column(
867 bridge.dir.select_aabr_with(ramp_f, ramp_f.min),
868 column_range.clone(),
869 )
870 .fill(rock.clone());
871 painter
872 .column(
873 bridge.dir.select_aabr_with(ramp_f, ramp_f.max),
874 column_range.clone(),
875 )
876 .fill(rock.clone());
877 painter
878 .column(
879 (-bridge.dir).select_aabr_with(ramp_b, ramp_b.min),
880 column_range.clone(),
881 )
882 .fill(rock.clone());
883 painter
884 .column(
885 (-bridge.dir).select_aabr_with(ramp_b, ramp_b.max),
886 column_range,
887 )
888 .fill(rock);
889}
890
891pub struct Bridge {
892 pub(crate) original_start: Vec2<i32>,
894 pub(crate) original_end: Vec2<i32>,
896
897 pub(crate) start: Vec3<i32>,
898 pub(crate) end: Vec3<i32>,
899 pub(crate) dir: Dir,
900 center: Vec3<i32>,
901 kind: BridgeKind,
902 biome: BiomeKind,
903 surface_color: Rgb<f32>,
904}
905
906impl Bridge {
907 pub fn generate(
908 land: &Land,
909 index: IndexRef,
910 rng: &mut impl Rng,
911 site: &Site,
912 start: Vec2<i32>,
913 end: Vec2<i32>,
914 ) -> Self {
915 let original_start = site.tile_wpos(start);
916 let original_end = site.tile_wpos(end);
917
918 let min_water_dist = 5;
919 let find_edge = |start: Vec2<i32>, end: Vec2<i32>| {
920 let mut test_start = start;
921 let dir = Dir::from_vec2(end - start).to_vec2();
922 let mut last_alt = if let Some(col) = land.column_sample(start, index) {
923 col.alt as i32
924 } else {
925 return (
926 test_start.with_z(land.get_alt_approx(start) as i32),
927 i32::MAX,
928 );
929 };
930 let mut step = 0;
931 loop {
932 if let Some(sample) = land.column_sample(test_start + step * dir, index) {
933 let alt = sample.alt as i32;
934 let water_dist = sample.water_dist.unwrap_or(16.0) as i32;
935 if last_alt - alt > 1 + (step + 2) / 3
936 || sample.riverless_alt - sample.alt > 2.0
937 {
938 break (test_start.with_z(last_alt), water_dist);
939 } else {
940 test_start += step * dir;
941
942 if water_dist <= min_water_dist {
943 break (test_start.with_z(alt), water_dist);
944 }
945
946 step = water_dist - min_water_dist;
947
948 last_alt = alt;
949 }
950 } else {
951 break (test_start.with_z(last_alt), i32::MAX);
952 }
953 }
954 };
955
956 let (test_start, start_dist) = find_edge(original_start, original_end);
957
958 let (test_end, end_dist) = find_edge(original_end, original_start);
959
960 let (start, start_dist, end, end_dist) = if test_start.z < test_end.z {
961 (test_start, start_dist, test_end, end_dist)
962 } else {
963 (test_end, end_dist, test_start, start_dist)
964 };
965
966 let center = (start.xy() + end.xy()) / 2;
967 let col = land.column_sample(center, index).unwrap();
968 let center = center.with_z(col.alt as i32);
969 let surface_color = col.surface_color;
970 let water_alt = col.water_level as i32;
971 let bridge = BridgeKind::random(rng, start, start_dist, end, end_dist, water_alt);
972 Self {
973 original_start,
974 original_end,
975 start,
976 end,
977 center,
978 dir: Dir::from_vec2(end.xy() - start.xy()),
979 kind: bridge,
980 biome: land
981 .get_chunk_wpos(center.xy())
982 .map_or(BiomeKind::Void, |chunk| chunk.get_biome()),
983 surface_color,
984 }
985 }
986
987 pub fn width(&self) -> i32 { self.kind.width() }
988}
989
990impl Structure for Bridge {
991 #[cfg(feature = "use-dyn-lib")]
992 const UPDATE_FN: &'static [u8] = b"render_bridge\0";
993
994 #[cfg_attr(feature = "be-dyn-lib", unsafe(export_name = "render_bridge"))]
995 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
996 match &self.kind {
997 BridgeKind::Flat => render_flat(self, painter),
998 BridgeKind::Tower(roof) => render_tower(self, painter, roof),
999 BridgeKind::Short => render_short(self, painter),
1000 BridgeKind::HeightenedViaduct(data) => render_heightened_viaduct(self, painter, data),
1001 BridgeKind::HangBridge => render_hang(self, painter),
1002 }
1003 }
1004}