veloren_world/site/plot/
bridge.rs

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 outset = 7;
146
147    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    /*
183    let t = 4;
184    b.union(
185        painter.aabb(aabb(
186            (bridge.start.xy() - side - forward * (top - bridge.start.z))
187                .with_z(bridge.start.z - t),
188            (bridge.end.xy() + side + forward * (top - bridge.end.z))
189                .with_z(bridge.start.z),
190        )),
191    )
192    .translate(Vec3::new(0, 0, t))
193    .without(b)
194    .clear();
195    */
196
197    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    /*
489    let place_lights = |center: Vec3<i32>| {
490        painter.sprite(
491            orth_dir
492                .select_aabr_with(bridge_aabr, center.xy())
493                .with_z(center.z),
494            SpriteKind::FireBowlGround,
495        );
496        painter.sprite(
497            (-orth_dir)
498                .select_aabr_with(bridge_aabr, center.xy())
499                .with_z(center.z),
500            SpriteKind::FireBowlGround,
501        );
502    };
503
504    place_lights(bridge_aabr.center().with_z(bridge_top + 1));
505
506    let light_spacing = 1;
507    let num_lights = (len - 1) / 2 / light_spacing;
508
509    let place_lights = |i: i32| {
510        let offset = i * light_spacing;
511        let z =
512            bridge_start_z + 1 + (offset + if len / 2 % 2 == 0 { 4 } else { 3 }) / (slope_inv - 1);
513
514        place_lights(
515            (bridge
516                .dir
517                .select_aabr_with(bridge_aabr, bridge_aabr.center())
518                - forward * offset)
519                .with_z(z),
520        );
521        place_lights(
522            ((-bridge.dir).select_aabr_with(bridge_aabr, bridge_aabr.center()) + forward * offset)
523                .with_z(z),
524        );
525    };
526    for i in 0..num_lights {
527        place_lights(i);
528    }
529    */
530
531    // Small chance to spawn a troll.
532    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    /// The original start position in world coords.
893    pub(crate) original_start: Vec2<i32>,
894    /// The original end position in world coords.
895    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}