Skip to main content

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