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 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    /*
472    let place_lights = |center: Vec3<i32>| {
473        painter.sprite(
474            orth_dir
475                .select_aabr_with(bridge_aabr, center.xy())
476                .with_z(center.z),
477            SpriteKind::FireBowlGround,
478        );
479        painter.sprite(
480            (-orth_dir)
481                .select_aabr_with(bridge_aabr, center.xy())
482                .with_z(center.z),
483            SpriteKind::FireBowlGround,
484        );
485    };
486
487    place_lights(bridge_aabr.center().with_z(bridge_top + 1));
488
489    let light_spacing = 1;
490    let num_lights = (len - 1) / 2 / light_spacing;
491
492    let place_lights = |i: i32| {
493        let offset = i * light_spacing;
494        let z =
495            bridge_start_z + 1 + (offset + if len / 2 % 2 == 0 { 4 } else { 3 }) / (slope_inv - 1);
496
497        place_lights(
498            (bridge
499                .dir
500                .select_aabr_with(bridge_aabr, bridge_aabr.center())
501                - forward * offset)
502                .with_z(z),
503        );
504        place_lights(
505            ((-bridge.dir).select_aabr_with(bridge_aabr, bridge_aabr.center()) + forward * offset)
506                .with_z(z),
507        );
508    };
509    for i in 0..num_lights {
510        place_lights(i);
511    }
512    */
513
514    // Small chance to spawn a troll.
515    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    /// The original start position in world coords.
876    pub(crate) original_start: Vec2<i32>,
877    /// The original end position in world coords.
878    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}