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 lod::ObjectKind::CoastalAirshipDock,
143 make_lod_object("coastal_airship_dock", renderer),
144 ),
145 (
146 lod::ObjectKind::DesertCityAirshipDock,
147 make_lod_object("desert_city_airship_dock", renderer),
148 ),
149 (
150 lod::ObjectKind::CliffTownAirshipDock,
151 make_lod_object("cliff_town_airship_dock", renderer),
152 ),
153 ]
154 .into(),
155 }
156 }
157
158 pub fn get_data(&self) -> &LodData { &self.data }
159
160 pub fn set_detail(&mut self, detail: u32) {
161 self.data.tgt_detail = (detail - detail % 2).clamp(100, 2500);
163 }
164
165 pub fn maintain(
166 &mut self,
167 renderer: &mut Renderer,
168 client: &Client,
169 focus_pos: Vec3<f32>,
170 camera: &Camera,
171 ) {
172 if self
174 .model
175 .as_ref()
176 .map(|(detail, _)| *detail != self.data.tgt_detail)
177 .unwrap_or(true)
178 {
179 self.model = Some((
180 self.data.tgt_detail,
181 renderer
182 .create_model(&create_lod_terrain_mesh(self.data.tgt_detail))
183 .unwrap(),
184 ));
185 }
186
187 for (p, zone) in client.lod_zones() {
189 self.zone_objects.entry(*p).or_insert_with(|| {
190 let mut objects = HashMap::<_, Vec<_>>::new();
191 let mut z_range = None;
192 for object in zone.objects.iter() {
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 z_range = Some(z_range.map_or(
197 pos.z as i32..pos.z as i32,
198 |z_range: Range<i32>| {
199 z_range.start.min(pos.z as i32)..z_range.end.max(pos.z as i32)
200 },
201 ));
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 z_range: z_range.clone(),
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 (pos, groups) in &mut self.zone_objects {
236 for group in groups.values_mut() {
237 if let Some(z_range) = &group.z_range {
238 let group_min = (pos.map(lod::to_wpos).with_z(z_range.start)
239 - MAX_OBJECT_RADIUS)
240 .map(|e| e as f32);
241 let group_max = ((pos + 1).map(lod::to_wpos).with_z(z_range.end)
242 + MAX_OBJECT_RADIUS)
243 .map(|e| e as f32);
244 let (in_frustum, last_plane_index) =
245 AABB::new(group_min.into_array(), group_max.into_array())
246 .coherent_test_against_frustum(
247 &frustum,
248 group.frustum_last_plane_index,
249 );
250 group.visible = in_frustum;
251 group.frustum_last_plane_index = last_plane_index;
252 }
253 }
254 }
255 let weather = client.state().weather_grid();
259 let size = weather.size().as_::<u32>();
260 renderer.update_texture(
261 &self.data.weather,
262 [0, 0],
263 [size.x, size.y],
264 &weather
265 .iter()
266 .map(|(_, w)| [(w.cloud * 255.0) as u8, (w.rain * 255.0) as u8, 0, 0])
267 .collect::<Vec<_>>(),
268 );
269 }
270
271 pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>, culling_mode: CullingMode) {
272 if let Some((_, model)) = self.model.as_ref() {
273 drawer.draw_lod_terrain(model);
274 }
275
276 if !matches!(culling_mode, CullingMode::Underground) {
277 let mut drawer = drawer.draw_lod_objects();
279 for groups in self.zone_objects.values() {
280 for (kind, group) in groups.iter().filter(|(_, g)| g.visible) {
281 if let Some(model) = self.object_data.get(kind) {
282 drawer.draw(model, &group.instances);
283 }
284 }
285 }
286 }
287 }
288}
289
290fn create_lod_terrain_mesh(detail: u32) -> Mesh<LodTerrainVertex> {
291 let detail = detail + 1;
294 Spiral2d::new()
295 .take((detail * detail) as usize)
296 .skip(1)
297 .map(|pos| {
298 let x = pos.x + detail as i32 / 2;
299 let y = pos.y + detail as i32 / 2;
300
301 let transform = |x| (2.0 * x as f32) / detail as f32 - 1.0;
302
303 Quad::new(
304 Vertex::new(Vec2::new(x, y).map(transform)),
305 Vertex::new(Vec2::new(x + 1, y).map(transform)),
306 Vertex::new(Vec2::new(x + 1, y + 1).map(transform)),
307 Vertex::new(Vec2::new(x, y + 1).map(transform)),
308 )
309 .rotated_by(usize::from(
310 !((x > detail as i32 / 2) ^ (y > detail as i32 / 2)),
311 ))
312 })
313 .collect()
314}
315
316fn make_lod_object(name: &str, renderer: &mut Renderer) -> Model<LodObjectVertex> {
368 let model = ObjAsset::load_expect(&format!("voxygen.lod.{}", name));
369 let mesh = model
370 .read()
371 .0
372 .objects()
373 .flat_map(|(objname, obj)| {
374 let mut color = objname.split('_').filter_map(|x| x.parse::<u8>().ok());
375 let color = color
376 .next()
377 .and_then(|r| Some(Rgb::new(r, color.next()?, color.next()?)))
378 .unwrap_or(Rgb::broadcast(127));
379 let color = srgb_to_linear(color.map(|c| (c as f32 / 255.0)));
380 let flags = if objname.contains("InstCol") && objname.contains("Glow") {
381 VertexFlags::INST_COLOR | VertexFlags::GLOW
382 } else if objname.contains("Glow") {
383 VertexFlags::GLOW
384 } else if objname.contains("InstCol") {
385 VertexFlags::INST_COLOR
386 } else {
387 VertexFlags::empty()
388 };
389 obj.triangles().map(move |vs| {
390 let [a, b, c] = vs.map(|v| {
391 LodObjectVertex::new(
392 v.position().into(),
393 v.normal().unwrap_or([0.0, 0.0, 1.0]).into(),
394 color,
395 flags,
396 )
397 });
398 Tri::new(a, b, c)
399 })
400 })
401 .collect();
402 renderer.create_model(&mesh).expect("Mesh was empty!")
403}