1use crate::{
2 render::{
3 CullingMode, FirstPassDrawer, Instances, LodObjectInstance, LodObjectVertex,
4 LodTerrainVertex, Mesh, Model, Quad, Renderer, Tri,
5 pipelines::lod_terrain::{LodData, Vertex},
6 },
7 scene::{Camera, camera},
8 settings::Settings,
9};
10use client::Client;
11use common::{
12 assets::{AssetExt, ObjAsset},
13 lod,
14 spiral::Spiral2d,
15 util::{srgb_to_linear, srgba_to_linear},
16 weather,
17};
18use hashbrown::HashMap;
19use std::ops::Range;
20use treeculler::{AABB, BVol, Frustum};
21use vek::*;
22
23const MAX_OBJECT_RADIUS: i32 = 64;
25
26bitflags::bitflags! {
27 #[derive(Debug, Clone, Copy)]
28 pub struct VertexFlags: u8 {
29 const INST_COLOR = 0b00000001;
31 const GLOW = 0b00000010;
33 }
34}
35
36struct ObjectGroup {
37 instances: Instances<LodObjectInstance>,
38 z_range: Option<Range<i32>>,
40 frustum_last_plane_index: u8,
41 visible: bool,
42}
43
44pub struct Lod {
45 model: Option<(u32, Model<LodTerrainVertex>)>,
46 data: LodData,
47
48 zone_objects: HashMap<Vec2<i32>, HashMap<lod::ObjectKind, ObjectGroup>>,
49 object_data: HashMap<lod::ObjectKind, Model<LodObjectVertex>>,
50}
51
52pub fn water_color() -> Rgba<f32> {
54 srgba_to_linear(Rgba::new(0.0, 0.25, 0.5, 0.0))
56}
57
58impl Lod {
59 pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self {
60 let data = LodData::new(
61 renderer,
62 client.world_data().chunk_size().as_(),
63 client.world_data().lod_base.raw(),
64 client.world_data().lod_alt.raw(),
65 client.world_data().lod_horizon.raw(),
66 (client.world_data().chunk_size().as_() / weather::CHUNKS_PER_CELL).map(|e| e.max(1)),
67 settings.graphics.lod_detail.clamp(100, 2500),
68 );
71 Self {
72 model: None,
73 data,
74 zone_objects: HashMap::new(),
75 object_data: [
76 (
77 lod::ObjectKind::GenericTree,
78 make_lod_object("oak", renderer),
79 ),
80 (lod::ObjectKind::Pine, make_lod_object("pine", renderer)),
81 (lod::ObjectKind::Dead, make_lod_object("dead", renderer)),
82 (lod::ObjectKind::House, make_lod_object("house", renderer)),
83 (
84 lod::ObjectKind::GiantTree,
85 make_lod_object("giant_tree", renderer),
86 ),
87 (
88 lod::ObjectKind::Mangrove,
89 make_lod_object("mangrove", renderer),
90 ),
91 (lod::ObjectKind::Acacia, make_lod_object("acacia", renderer)),
92 (lod::ObjectKind::Birch, make_lod_object("birch", renderer)),
93 (
94 lod::ObjectKind::Redwood,
95 make_lod_object("redwood", renderer),
96 ),
97 (lod::ObjectKind::Baobab, make_lod_object("baobab", renderer)),
98 (
99 lod::ObjectKind::Frostpine,
100 make_lod_object("frostpine", renderer),
101 ),
102 (lod::ObjectKind::Haniwa, make_lod_object("haniwa", renderer)),
103 (
104 lod::ObjectKind::Desert,
105 make_lod_object("desert_houses", renderer),
106 ),
107 (lod::ObjectKind::Palm, make_lod_object("palm", renderer)),
108 (lod::ObjectKind::Arena, make_lod_object("arena", renderer)),
109 (
110 lod::ObjectKind::SavannahHut,
111 make_lod_object("savannah_hut", renderer),
112 ),
113 (
114 lod::ObjectKind::SavannahAirshipDock,
115 make_lod_object("savannah_airship_dock", renderer),
116 ),
117 (
118 lod::ObjectKind::TerracottaPalace,
119 make_lod_object("terracotta_palace", renderer),
120 ),
121 (
122 lod::ObjectKind::TerracottaHouse,
123 make_lod_object("terracotta_house", renderer),
124 ),
125 (
126 lod::ObjectKind::TerracottaYard,
127 make_lod_object("terracotta_yard", renderer),
128 ),
129 (
130 lod::ObjectKind::AirshipDock,
131 make_lod_object("airship_dock", renderer),
132 ),
133 (
134 lod::ObjectKind::CoastalHouse,
135 make_lod_object("coastal_house", renderer),
136 ),
137 (
138 lod::ObjectKind::CoastalWorkshop,
139 make_lod_object("coastal_workshop", renderer),
140 ),
141 ]
142 .into(),
143 }
144 }
145
146 pub fn get_data(&self) -> &LodData { &self.data }
147
148 pub fn set_detail(&mut self, detail: u32) {
149 self.data.tgt_detail = (detail - detail % 2).clamp(100, 2500);
151 }
152
153 pub fn maintain(
154 &mut self,
155 renderer: &mut Renderer,
156 client: &Client,
157 focus_pos: Vec3<f32>,
158 camera: &Camera,
159 ) {
160 if self
162 .model
163 .as_ref()
164 .map(|(detail, _)| *detail != self.data.tgt_detail)
165 .unwrap_or(true)
166 {
167 self.model = Some((
168 self.data.tgt_detail,
169 renderer
170 .create_model(&create_lod_terrain_mesh(self.data.tgt_detail))
171 .unwrap(),
172 ));
173 }
174
175 for (p, zone) in client.lod_zones() {
177 self.zone_objects.entry(*p).or_insert_with(|| {
178 let mut objects = HashMap::<_, Vec<_>>::new();
179 let mut z_range = None;
180 for object in zone.objects.iter() {
181 let pos = p.map(|e| lod::to_wpos(e) as f32).with_z(0.0)
182 + object.pos.map(|e| e as f32)
183 + Vec2::broadcast(0.5).with_z(0.0);
184 z_range = Some(z_range.map_or(
185 pos.z as i32..pos.z as i32,
186 |z_range: Range<i32>| {
187 z_range.start.min(pos.z as i32)..z_range.end.max(pos.z as i32)
188 },
189 ));
190 objects
191 .entry(object.kind)
192 .or_default()
193 .push(LodObjectInstance::new(pos, object.color, object.flags));
194 }
195 objects
196 .into_iter()
197 .map(|(kind, instances)| {
198 (kind, ObjectGroup {
199 instances: renderer.create_instances(&instances),
200 z_range: z_range.clone(),
201 frustum_last_plane_index: 0,
202 visible: false,
203 })
204 })
205 .collect()
206 });
207 }
208
209 self.zone_objects
211 .retain(|p, _| client.lod_zones().contains_key(p));
212
213 let camera::Dependents {
215 view_mat,
216 proj_mat_treeculler,
217 ..
218 } = camera.dependents();
219 let focus_off = focus_pos.map(|e| e.trunc());
220 let frustum = Frustum::from_modelview_projection(
221 (proj_mat_treeculler * view_mat * Mat4::translation_3d(-focus_off)).into_col_arrays(),
222 );
223 for (pos, groups) in &mut self.zone_objects {
224 for group in groups.values_mut() {
225 if let Some(z_range) = &group.z_range {
226 let group_min = (pos.map(lod::to_wpos).with_z(z_range.start)
227 - MAX_OBJECT_RADIUS)
228 .map(|e| e as f32);
229 let group_max = ((pos + 1).map(lod::to_wpos).with_z(z_range.end)
230 + MAX_OBJECT_RADIUS)
231 .map(|e| e as f32);
232 let (in_frustum, last_plane_index) =
233 AABB::new(group_min.into_array(), group_max.into_array())
234 .coherent_test_against_frustum(
235 &frustum,
236 group.frustum_last_plane_index,
237 );
238 group.visible = in_frustum;
239 group.frustum_last_plane_index = last_plane_index;
240 }
241 }
242 }
243 let weather = client.state().weather_grid();
247 let size = weather.size().as_::<u32>();
248 renderer.update_texture(
249 &self.data.weather,
250 [0, 0],
251 [size.x, size.y],
252 &weather
253 .iter()
254 .map(|(_, w)| [(w.cloud * 255.0) as u8, (w.rain * 255.0) as u8, 0, 0])
255 .collect::<Vec<_>>(),
256 );
257 }
258
259 pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>, culling_mode: CullingMode) {
260 if let Some((_, model)) = self.model.as_ref() {
261 drawer.draw_lod_terrain(model);
262 }
263
264 if !matches!(culling_mode, CullingMode::Underground) {
265 let mut drawer = drawer.draw_lod_objects();
267 for groups in self.zone_objects.values() {
268 for (kind, group) in groups.iter().filter(|(_, g)| g.visible) {
269 if let Some(model) = self.object_data.get(kind) {
270 drawer.draw(model, &group.instances);
271 }
272 }
273 }
274 }
275 }
276}
277
278fn create_lod_terrain_mesh(detail: u32) -> Mesh<LodTerrainVertex> {
279 let detail = detail + 1;
282 Spiral2d::new()
283 .take((detail * detail) as usize)
284 .skip(1)
285 .map(|pos| {
286 let x = pos.x + detail as i32 / 2;
287 let y = pos.y + detail as i32 / 2;
288
289 let transform = |x| (2.0 * x as f32) / detail as f32 - 1.0;
290
291 Quad::new(
292 Vertex::new(Vec2::new(x, y).map(transform)),
293 Vertex::new(Vec2::new(x + 1, y).map(transform)),
294 Vertex::new(Vec2::new(x + 1, y + 1).map(transform)),
295 Vertex::new(Vec2::new(x, y + 1).map(transform)),
296 )
297 .rotated_by(usize::from(
298 !((x > detail as i32 / 2) ^ (y > detail as i32 / 2)),
299 ))
300 })
301 .collect()
302}
303
304fn make_lod_object(name: &str, renderer: &mut Renderer) -> Model<LodObjectVertex> {
305 let model = ObjAsset::load_expect(&format!("voxygen.lod.{}", name));
306 let mesh = model
307 .read()
308 .0
309 .objects()
310 .flat_map(|(objname, obj)| {
311 let mut color = objname.split('_').filter_map(|x| x.parse::<u8>().ok());
312 let color = color
313 .next()
314 .and_then(|r| Some(Rgb::new(r, color.next()?, color.next()?)))
315 .unwrap_or(Rgb::broadcast(127));
316 let color = srgb_to_linear(color.map(|c| (c as f32 / 255.0)));
317 let flags = match objname {
318 "InstCol" => VertexFlags::INST_COLOR,
319 "Glow" => VertexFlags::GLOW,
320 _ => VertexFlags::empty(),
321 };
322 obj.triangles().map(move |vs| {
323 let [a, b, c] = vs.map(|v| {
324 LodObjectVertex::new(
325 v.position().into(),
326 v.normal().unwrap_or([0.0, 0.0, 1.0]).into(),
327 color,
328 flags,
329 )
330 });
331 Tri::new(a, b, c)
332 })
333 })
334 .collect();
335 renderer.create_model(&mesh).expect("Mesh was empty!")
336}