veloren_voxygen/scene/
lod.rs

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
23// For culling
24const MAX_OBJECT_RADIUS: i32 = 64;
25
26bitflags::bitflags! {
27    #[derive(Debug, Clone, Copy)]
28    pub struct VertexFlags: u8 {
29        /// Use instance not vertex colour
30        const INST_COLOR    = 0b00000001;
31        /// Glow!
32        const GLOW          = 0b00000010;
33    }
34}
35
36struct ObjectGroup {
37    instances: Instances<LodObjectInstance>,
38    // None implies no instances
39    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
52// TODO: Make constant when possible.
53pub fn water_color() -> Rgba<f32> {
54    /* Rgba::new(0.2, 0.5, 1.0, 0.0) */
55    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            /* TODO: figure out how we want to do this without color borders?
69             * water_color().into_array().into(), */
70        );
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        // Make sure the recorded detail is even.
162        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        // Update LoD terrain mesh according to detail
173        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        // Create new LoD groups when a new zone has loaded
188        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        // Remove zones that are unloaded
222        self.zone_objects
223            .retain(|p, _| client.lod_zones().contains_key(p));
224
225        // Determine visibility of zones based on view frustum
226        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        // Update weather texture
256        // NOTE: consider moving the lerping to a shader if the overhead of uploading to
257        // the gpu each frame becomes an issue.
258        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            // Draw LoD objects
278            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    // detail is even, so we choose odd detail (detail + 1) to create two even
292    // halves with an empty hole.
293    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
316/// Create LoD objects from an .obj asset file.
317///
318/// ### Object Colors
319///
320/// In the .obj LoD files, objects can have a specific color by setting the name
321/// of the object to the format "r_g_b" where the values are u8 integers. E.g.
322/// the line "o 1_0_0" would set the color of that object to red. Note that the
323/// leading 'o' is part of the Wavefront .obj file format (o means 'object').
324/// The object name follows the 'o'.
325///
326/// If the name of the object is 'InstCol', that means to use the instance
327/// color, i.e., the color from the worldgen lod::Object. The get_lod_zone()
328/// function creates lod::Object instances with a color field (usually black)
329/// that is the 'instance' color.  For example, City plots have houses
330/// with varying roof colors, and the house lod::Object that is the roof uses
331/// the instance color because it's name is set to InstCol.
332///
333/// These two choices, setting a specific color for an object by using the
334/// "r_g_b" format, or setting the name to 'InstCol' and using the instance
335/// color are mutually exclusive.
336///
337/// ### Glow
338///
339/// A glow effect can also be applied to the LoD object by using the "Glow" flag
340/// in the object name. If the name of an object includes 'Glow', the object
341/// will have a glowing effect. For example, the name 240_0_0_Glow would set the
342/// color to red and apply a glow effect. 'Glow' by itself means to apply a glow
343/// effect to the object using the default color seen below (127, 127, 127). The
344/// lod-object-frag shader applies a glow color that is tinted by the fragment
345/// surface color.
346///
347/// > **Note:** The keyword Glow is case-sensitive.
348///
349/// ### Backwards Compatibility
350///
351/// Previous versions of this function expected the object name to either be
352/// InstCol, Glow, or a color format. So, Glow, InstCol, and 255_0_0 were valid
353/// object names. This scheme continues to work, with the added functionality of
354/// being able to specify a color and the glow effect together.
355///
356/// ### Examples (without the leading "o"):
357///
358/// - `Glow` Apply a glow effect with the default glow color (light orange).
359/// - `255_0_0_Glow` The object will be a glowing red.
360/// - `255_255_0` The object will be yellow.
361/// - `This is an object` The object color will be the default light gray.
362/// - `The cone on top of the spire, uses Glow` The object will glow with a
363///   light orange color.
364/// - `InstCol` The object will use the instance color that is set in the
365///   worldgen lod::Object.
366/// - `InstCol Glow` The object will use the instance color. Glow is ignored.
367fn 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}