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, Obj},
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
23bitflags::bitflags! {
24 #[derive(Debug, Clone, Copy)]
25 pub struct VertexFlags: u8 {
26 const INST_COLOR = 0b00000001;
28 const GLOW = 0b00000010;
30 }
31}
32
33struct ObjectGroup {
34 instances: Instances<LodObjectInstance>,
35 bounds: Option<Aabb<f32>>,
36 frustum_last_plane_index: u8,
37 visible: bool,
38}
39
40pub struct Lod {
41 model: Option<(u32, Model<LodTerrainVertex>)>,
42 data: LodData,
43
44 zone_objects: HashMap<Vec2<i32>, HashMap<lod::ObjectKind, ObjectGroup>>,
45 object_data: HashMap<lod::ObjectKind, (Model<LodObjectVertex>, f32, Range<f32>)>,
47}
48
49pub fn water_color() -> Rgba<f32> {
51 srgba_to_linear(Rgba::new(0.0, 0.25, 0.5, 0.0))
53}
54
55impl Lod {
56 pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self {
57 let data = LodData::new(
58 renderer,
59 client.world_data().chunk_size().as_(),
60 client.world_data().lod_base.raw(),
61 client.world_data().lod_alt.raw(),
62 client.world_data().lod_horizon.raw(),
63 (client.world_data().chunk_size().as_() / weather::CHUNKS_PER_CELL).map(|e| e.max(1)),
64 settings.graphics.lod_detail.clamp(100, 2500),
65 );
68 Self {
69 model: None,
70 data,
71 zone_objects: HashMap::new(),
72 object_data: [
73 (
74 lod::ObjectKind::GenericTree,
75 make_lod_object("oak", renderer),
76 ),
77 (lod::ObjectKind::Pine, make_lod_object("pine", renderer)),
78 (lod::ObjectKind::Dead, make_lod_object("dead", renderer)),
79 (lod::ObjectKind::House, make_lod_object("house", renderer)),
80 (
81 lod::ObjectKind::GiantTree,
82 make_lod_object("giant_tree", renderer),
83 ),
84 (
85 lod::ObjectKind::Mangrove,
86 make_lod_object("mangrove", renderer),
87 ),
88 (lod::ObjectKind::Acacia, make_lod_object("acacia", renderer)),
89 (lod::ObjectKind::Birch, make_lod_object("birch", renderer)),
90 (
91 lod::ObjectKind::Redwood,
92 make_lod_object("redwood", renderer),
93 ),
94 (lod::ObjectKind::Baobab, make_lod_object("baobab", renderer)),
95 (
96 lod::ObjectKind::Frostpine,
97 make_lod_object("frostpine", renderer),
98 ),
99 (lod::ObjectKind::Haniwa, make_lod_object("haniwa", renderer)),
100 (
101 lod::ObjectKind::Desert,
102 make_lod_object("desert_houses", renderer),
103 ),
104 (lod::ObjectKind::Palm, make_lod_object("palm", renderer)),
105 (lod::ObjectKind::Arena, make_lod_object("arena", renderer)),
106 (
107 lod::ObjectKind::SavannahHut,
108 make_lod_object("savannah_hut", renderer),
109 ),
110 (
111 lod::ObjectKind::SavannahAirshipDock,
112 make_lod_object("savannah_airship_dock", renderer),
113 ),
114 (
115 lod::ObjectKind::TerracottaPalace,
116 make_lod_object("terracotta_palace", renderer),
117 ),
118 (
119 lod::ObjectKind::TerracottaHouse,
120 make_lod_object("terracotta_house", renderer),
121 ),
122 (
123 lod::ObjectKind::TerracottaYard,
124 make_lod_object("terracotta_yard", renderer),
125 ),
126 (
127 lod::ObjectKind::AirshipDock,
128 make_lod_object("airship_dock", renderer),
129 ),
130 (
131 lod::ObjectKind::CoastalHouse,
132 make_lod_object("coastal_house", renderer),
133 ),
134 (
135 lod::ObjectKind::CoastalWorkshop,
136 make_lod_object("coastal_workshop", renderer),
137 ),
138 (
139 lod::ObjectKind::CoastalAirshipDock,
140 make_lod_object("coastal_airship_dock", renderer),
141 ),
142 (
143 lod::ObjectKind::DesertCityAirshipDock,
144 make_lod_object("desert_city_airship_dock", renderer),
145 ),
146 (
147 lod::ObjectKind::CliffTownAirshipDock,
148 make_lod_object("cliff_town_airship_dock", renderer),
149 ),
150 ]
151 .into(),
152 }
153 }
154
155 pub fn get_data(&self) -> &LodData { &self.data }
156
157 pub fn set_detail(&mut self, detail: u32) {
158 self.data.tgt_detail = (detail - detail % 2).clamp(100, 2500);
160 }
161
162 pub fn maintain(
163 &mut self,
164 renderer: &mut Renderer,
165 client: &Client,
166 focus_pos: Vec3<f32>,
167 camera: &Camera,
168 ) {
169 if self
171 .model
172 .as_ref()
173 .map(|(detail, _)| *detail != self.data.tgt_detail)
174 .unwrap_or(true)
175 {
176 self.model = Some((
177 self.data.tgt_detail,
178 renderer
179 .create_model(&create_lod_terrain_mesh(self.data.tgt_detail))
180 .unwrap(),
181 ));
182 }
183
184 for (p, zone) in client.lod_zones() {
186 self.zone_objects.entry(*p).or_insert_with(|| {
187 let mut objects = HashMap::<_, Vec<_>>::new();
188 let mut bounds = None;
189 for object in zone.objects.iter() {
190 let Some((_, radius, z_range)) = self.object_data.get(&object.kind) else {
191 continue;
192 };
193 let pos = p.map(|e| lod::to_wpos(e) as f32).with_z(0.0)
194 + object.pos.map(|e| e as f32)
195 + Vec2::broadcast(0.5).with_z(0.0);
196 let rad = Vec2::broadcast(*radius);
197 let obj_bounds = Aabb {
198 min: pos + (-rad).with_z(z_range.start),
199 max: pos + rad.with_z(z_range.end),
200 };
201 bounds = Some(bounds.map_or(obj_bounds, |b: Aabb<f32>| b.union(obj_bounds)));
202 objects
203 .entry(object.kind)
204 .or_default()
205 .push(LodObjectInstance::new(pos, object.color, object.flags));
206 }
207 objects
208 .into_iter()
209 .map(|(kind, instances)| {
210 (kind, ObjectGroup {
211 instances: renderer.create_instances(&instances),
212 bounds,
213 frustum_last_plane_index: 0,
214 visible: false,
215 })
216 })
217 .collect()
218 });
219 }
220
221 self.zone_objects
223 .retain(|p, _| client.lod_zones().contains_key(p));
224
225 let camera::Dependents {
227 view_mat,
228 proj_mat_treeculler,
229 ..
230 } = camera.dependents();
231 let focus_off = focus_pos.map(|e| e.trunc());
232 let frustum = Frustum::from_modelview_projection(
233 (proj_mat_treeculler * view_mat * Mat4::translation_3d(-focus_off)).into_col_arrays(),
234 );
235 for groups in &mut self.zone_objects.values_mut() {
236 for group in groups.values_mut() {
237 if let Some(bounds) = &group.bounds {
238 let (in_frustum, last_plane_index) =
239 AABB::new(bounds.min.into_array(), bounds.max.into_array())
240 .coherent_test_against_frustum(
241 &frustum,
242 group.frustum_last_plane_index,
243 );
244 group.visible = in_frustum;
245 group.frustum_last_plane_index = last_plane_index;
246 }
247 }
248 }
249 let weather = client.state().weather_grid();
253 let size = weather.size().as_::<u32>();
254 renderer.update_texture(
255 &self.data.weather,
256 [0, 0],
257 [size.x, size.y],
258 &weather
259 .iter()
260 .map(|(_, w)| [(w.cloud * 255.0) as u8, (w.rain * 255.0) as u8, 0, 0])
261 .collect::<Vec<_>>(),
262 );
263 }
264
265 pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>, culling_mode: CullingMode) {
266 if let Some((_, model)) = self.model.as_ref() {
267 drawer.draw_lod_terrain(model);
268 }
269
270 if !matches!(culling_mode, CullingMode::Underground) {
271 let mut drawer = drawer.draw_lod_objects();
273 for groups in self.zone_objects.values() {
274 for (kind, group) in groups.iter().filter(|(_, g)| g.visible) {
275 if let Some((model, _, _)) = self.object_data.get(kind) {
276 drawer.draw(model, &group.instances);
277 }
278 }
279 }
280 }
281 }
282}
283
284fn create_lod_terrain_mesh(detail: u32) -> Mesh<LodTerrainVertex> {
285 let detail = detail + 1;
288 Spiral2d::new()
289 .take((detail * detail) as usize)
290 .skip(1)
291 .map(|pos| {
292 let x = pos.x + detail as i32 / 2;
293 let y = pos.y + detail as i32 / 2;
294
295 let transform = |x| (2.0 * x as f32) / detail as f32 - 1.0;
296
297 Quad::new(
298 Vertex::new(Vec2::new(x, y).map(transform)),
299 Vertex::new(Vec2::new(x + 1, y).map(transform)),
300 Vertex::new(Vec2::new(x + 1, y + 1).map(transform)),
301 Vertex::new(Vec2::new(x, y + 1).map(transform)),
302 )
303 .rotated_by(usize::from(
304 !((x > detail as i32 / 2) ^ (y > detail as i32 / 2)),
305 ))
306 })
307 .collect()
308}
309
310fn make_lod_object(
364 name: &str,
365 renderer: &mut Renderer,
366) -> (Model<LodObjectVertex>, f32, Range<f32>) {
367 let model = Obj::load_expect(&format!("voxygen.lod.{}", name));
368 let mut radius = 0.0f32;
369 let mut z_range = None;
370 let mut mesh = Mesh::new();
371 for (objname, obj) in model.read().0.objects() {
372 let mut color = objname.split('_').filter_map(|x| x.parse::<u8>().ok());
373 let color = color
374 .next()
375 .and_then(|r| Some(Rgb::new(r, color.next()?, color.next()?)))
376 .unwrap_or(Rgb::broadcast(127));
377 let color = srgb_to_linear(color.map(|c| c as f32 / 255.0));
378 let flags = if objname.contains("InstCol") && objname.contains("Glow") {
379 VertexFlags::INST_COLOR | VertexFlags::GLOW
380 } else if objname.contains("Glow") {
381 VertexFlags::GLOW
382 } else if objname.contains("InstCol") {
383 VertexFlags::INST_COLOR
384 } else {
385 VertexFlags::empty()
386 };
387
388 for vs in obj.triangles() {
389 for v in vs {
390 radius = radius.max(Vec3::from(v.position()).xy().magnitude());
391 let z = v.position()[2];
392 let z_range = z_range.get_or_insert(z..z);
393 z_range.start = z_range.start.min(z);
394 z_range.end = z_range.end.max(z);
395 }
396 let [a, b, c] = vs.map(|v| {
397 LodObjectVertex::new(
398 v.position().into(),
399 v.normal().unwrap_or([0.0, 0.0, 1.0]).into(),
400 color,
401 flags,
402 )
403 });
404 mesh.push_tri(Tri::new(a, b, c));
405 }
406 }
407 (
408 renderer.create_model(&mesh).expect("Mesh was empty!"),
409 radius,
410 z_range.expect("no vertices"),
411 )
412}