veloren_world/site2/plot/
road.rs

1use super::*;
2use crate::{
3    ColumnSample, Land,
4    util::{LOCALITY, RandomField, Sampler},
5};
6use common::terrain::{Block, BlockKind};
7use enumset::EnumSet;
8use rand::prelude::*;
9use strum::IntoEnumIterator;
10use util::sprites::PainterSpriteExt;
11use vek::*;
12
13#[derive(Clone, Copy, PartialEq, Eq)]
14pub enum RoadLights {
15    Default,
16    Terracotta,
17}
18
19#[derive(Clone, Copy, PartialEq, Eq)]
20pub enum RoadMaterial {
21    Dirt,
22    Cobblestone,
23    Sandstone,
24    Marble,
25}
26
27#[derive(Clone, Copy, PartialEq, Eq)]
28pub struct RoadKind {
29    pub lights: RoadLights,
30    pub material: RoadMaterial,
31}
32
33impl RoadKind {
34    /// Intended to be placed at `riverless_alt`.
35    pub fn place_light(&self, pos: Vec3<i32>, dir: Dir, painter: &Painter) {
36        let wood_corner = Fill::Brick(BlockKind::Wood, Rgb::new(86, 50, 50), 10);
37        painter
38            .column(pos.xy(), pos.z - 4..pos.z)
39            .sample_with_column(|p, col| p.z > col.riverless_alt as i32)
40            .fill(wood_corner);
41        match self.lights {
42            RoadLights::Default => painter.lanternpost_wood(pos, dir),
43            RoadLights::Terracotta => painter.sprite(pos, SpriteKind::LampTerracotta),
44        }
45    }
46
47    pub fn block(&self, col: &ColumnSample, wpos: Vec3<i32>, dir: Dir) -> Block {
48        match self.material {
49            RoadMaterial::Dirt => Block::new(
50                BlockKind::Earth,
51                crate::sim::Path::default()
52                    .surface_color((col.sub_surface_color * 255.0).as_(), wpos),
53            ),
54            RoadMaterial::Cobblestone => Block::new(
55                BlockKind::Rock,
56                (col.stone_col.as_() * 0.4
57                    + (col.sub_surface_color * 0.4 * 255.0)
58                    + (RandomField::new(15).get(wpos / (dir.orthogonal().to_vec2() + 1).with_z(1))
59                        % 20) as f32)
60                    .as_(),
61            ),
62            RoadMaterial::Sandstone => Block::new(
63                BlockKind::Rock,
64                (col.stone_col.as_() * 0.2
65                    + (col.surface_color * 0.5 * 255.0)
66                    + (RandomField::new(15).get(wpos / (dir.orthogonal().to_vec2() + 1).with_z(1))
67                        % 20) as f32)
68                    .as_(),
69            ),
70            RoadMaterial::Marble => Block::new(
71                BlockKind::Rock,
72                Rgb::broadcast((col.marble_small * 0.5 + 0.4) * 255.0).as_(),
73            ),
74        }
75    }
76}
77
78/// Represents house data generated by the `generate()` method
79pub struct Road {
80    pub path: Path<Vec2<i32>>,
81    pub kind: RoadKind,
82}
83
84impl Structure for Road {
85    #[cfg(feature = "use-dyn-lib")]
86    const UPDATE_FN: &'static [u8] = b"render_road\0";
87
88    #[cfg_attr(feature = "be-dyn-lib", unsafe(export_name = "render_road"))]
89    fn render_inner(&self, site: &Site, land: &Land, painter: &Painter) {
90        let field = RandomField::new(76237);
91
92        for p in self.path.iter() {
93            if (p.y + p.x) % 3 != 0 {
94                continue;
95            }
96
97            let current_tile = site.tiles.get(*p);
98            let TileKind::Road {
99                w,
100                a: this_a,
101                b: this_b,
102                ..
103            } = current_tile.kind
104            else {
105                continue;
106            };
107
108            let center = site.tile_center_wpos(*p);
109
110            let light_wpos = |dir: Dir| {
111                let width = w as i32 * 2 - 1 - (dir.signum() + 1) / 2;
112                center + dir.to_vec2() * width
113            };
114            let available_dirs: EnumSet<Dir> = Dir::iter()
115                .filter(|dir| {
116                    let light_wpos = light_wpos(*dir);
117                    let tpos = site.wpos_tile_pos(light_wpos);
118                    [tpos, tpos + dir.to_vec2()].into_iter().all(|tpos| {
119                        let tile = site.tiles.get(tpos);
120                        tile.is_natural()
121                            || if let TileKind::Road { a, b, .. } = tile.kind {
122                                current_tile.plot == tile.plot && this_a == a && this_b == b
123                            } else {
124                                false
125                            }
126                    })
127                })
128                .collect();
129
130            if available_dirs.is_empty() {
131                continue;
132            }
133
134            let i = field.get(p.with_z(11)) as usize % available_dirs.len();
135            let Some(dir) = available_dirs.iter().nth(i) else {
136                continue;
137            };
138
139            let wpos = light_wpos(dir);
140
141            // TODO: Not sure if this is always correct
142            let alt =
143                land.get_alt_approx(wpos)
144                    .max(land.get_interpolated(wpos, |c| c.water_alt) + 1.0) as i32
145                    + 1;
146            let wpos = wpos.with_z(alt);
147            self.kind.place_light(wpos, -dir, painter);
148        }
149    }
150
151    fn rel_terrain_offset(&self, col: &ColumnSample) -> i32 {
152        (col.riverless_alt as i32).max(col.water_level as i32 + 1)
153    }
154
155    fn terrain_surface_at<R: Rng>(
156        &self,
157        wpos: Vec2<i32>,
158        old: Block,
159        _rng: &mut R,
160        col: &ColumnSample,
161        z_off: i32,
162        site: &Site,
163    ) -> Option<Block> {
164        let z = self.rel_terrain_offset(col) + z_off;
165        if col.alt < col.water_level && z < 0 {
166            return None;
167        }
168
169        if z_off <= 0 {
170            let tpos = site.wpos_tile_pos(wpos);
171            let mut near_roads = LOCALITY.iter().filter_map(|rpos| {
172                let tile = site.tiles.get(tpos + rpos);
173                if let TileKind::Road { a, b, w } = &tile.kind {
174                    if let Some(PlotKind::Road(Road { path, .. })) =
175                        tile.plot.map(|p| &site.plot(p).kind)
176                    {
177                        let is_start = *a == 0;
178                        let is_end = *b == path.len() as u16 - 1;
179                        let a = path.nodes()[*a as usize];
180                        let b = path.nodes()[*b as usize];
181                        let path_dir = Dir::from_vec2(b - a);
182                        Some((
183                            LineSegment2 {
184                                start: site.tile_center_wpos(a)
185                                    - if is_start {
186                                        path_dir.to_vec2() * TILE_SIZE as i32 / 2
187                                    } else {
188                                        Vec2::zero()
189                                    },
190                                end: site.tile_center_wpos(b)
191                                    + if is_end {
192                                        path_dir.to_vec2() * TILE_SIZE as i32 / 2
193                                    } else {
194                                        Vec2::zero()
195                                    },
196                            }
197                            .as_(),
198                            *w,
199                        ))
200                    } else {
201                        None
202                    }
203                } else {
204                    None
205                }
206            });
207
208            let wposf = wpos.map(|e| e as f32);
209            if let Some((line, _)) =
210                near_roads.find(|(line, w)| line.distance_to_point(wposf) < *w as f32 * 2.0)
211            {
212                let dir = Dir::from_vec2((line.start - line.end).as_());
213                Some(self.kind.block(col, wpos.with_z(z), dir))
214            } else {
215                None
216            }
217        } else if old.is_fluid() || old.kind() == BlockKind::Snow || old.is_terrain() {
218            Some(old.into_vacant())
219        } else {
220            None
221        }
222    }
223}