veloren_world/site2/plot/
road.rs1use 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 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
78pub 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 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}