veloren_voxygen/scene/
trail.rs

1use super::SceneData;
2use crate::render::{DynamicModel, Mesh, Quad, Renderer, TrailDrawer, TrailVertex};
3use common::comp::{Body, Pos, Vel, object};
4use common_base::span;
5use specs::{Entity as EcsEntity, Join, WorldExt};
6use std::collections::HashMap;
7use vek::*;
8
9#[derive(Clone, Copy, Eq, PartialEq, Hash)]
10struct MeshKey {
11    entity: EcsEntity,
12    is_main_weapon: bool,
13}
14
15#[derive(Default)]
16pub struct TrailMgr {
17    /// Meshes for each entity, usize is the last offset tick it was updated
18    entity_meshes: HashMap<MeshKey, (Mesh<TrailVertex>, usize)>,
19
20    /// Position cache for things like projectiles
21    pos_cache: HashMap<EcsEntity, Pos>,
22
23    /// Offset
24    offset: usize,
25
26    /// Dynamic model to upload to GPU
27    dynamic_model: Option<DynamicModel<TrailVertex>>,
28
29    /// Used to create sub model from dynamic model
30    model_len: u32,
31}
32
33const TRAIL_DYNAMIC_MODEL_SIZE: usize = 15;
34const TRAIL_SHRINKAGE: f32 = 0.8;
35
36impl TrailMgr {
37    pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData) {
38        span!(_guard, "maintain", "TrailMgr::maintain");
39
40        if scene_data.weapon_trails_enabled {
41            // Hack to shove trails in for projectiles
42            let ecs = scene_data.state.ecs();
43            for (entity, body, vel, pos) in (
44                &ecs.entities(),
45                &ecs.read_storage::<Body>(),
46                &ecs.read_storage::<Vel>(),
47                &ecs.read_storage::<Pos>(),
48            )
49                .join()
50            {
51                const MIN_SPEED: f32 = 15.0;
52                if vel.0.magnitude_squared() > MIN_SPEED.powi(2)
53                    && matches!(
54                        body,
55                        Body::Object(
56                            object::Body::Arrow
57                                | object::Body::MultiArrow
58                                | object::Body::ArrowSnake
59                                | object::Body::ArrowTurret
60                                | object::Body::ArrowClay
61                                | object::Body::BoltBesieger,
62                        )
63                    )
64                {
65                    let last_pos = *self.pos_cache.entry(entity).or_insert(*pos);
66                    let offset = self.offset;
67                    let quad_mesh = self.entity_mesh_or_insert(entity, true);
68                    const THICKNESS: f32 = 0.05;
69                    let p1 = pos.0;
70                    let p2 = p1 + Vec3::unit_z() * THICKNESS;
71                    let p4 = last_pos.0;
72                    let p3 = p4 + Vec3::unit_z() * THICKNESS;
73                    let vertex = |p: Vec3<f32>| TrailVertex {
74                        pos: p.into_array(),
75                    };
76                    let quad = Quad::new(vertex(p1), vertex(p2), vertex(p3), vertex(p4));
77                    quad_mesh.replace_quad(offset * 4, quad);
78                    self.pos_cache.insert(entity, *pos);
79                }
80            }
81
82            // Update offset
83            self.offset = (self.offset + 1) % TRAIL_DYNAMIC_MODEL_SIZE;
84
85            self.entity_meshes.values_mut().for_each(|(mesh, _)| {
86                // TODO: Figure out how to do this in shader files instead
87                // Shrink size of each quad over time
88                let vertices = mesh.vertices_mut_vec();
89                let last_offset =
90                    (self.offset + TRAIL_DYNAMIC_MODEL_SIZE - 1) % TRAIL_DYNAMIC_MODEL_SIZE;
91                let next_offset = (self.offset + 1) % TRAIL_DYNAMIC_MODEL_SIZE;
92                for i in 0..TRAIL_DYNAMIC_MODEL_SIZE {
93                    // Verts per quad are in b, c, a, d order
94                    let [b, c, a, d] = [0, 1, 2, 3].map(|offset| i * 4 + offset);
95                    vertices[a] = if i == next_offset {
96                        vertices[b]
97                    } else {
98                        vertices[a] * TRAIL_SHRINKAGE + vertices[b] * (1.0 - TRAIL_SHRINKAGE)
99                    };
100                    if i != last_offset {
101                        // Avoid shrinking edge of most recent quad so that edges of quads align
102                        vertices[d] =
103                            vertices[d] * TRAIL_SHRINKAGE + vertices[c] * (1.0 - TRAIL_SHRINKAGE);
104                    }
105                }
106
107                // Reset quad for each entity mesh at new offset
108                let zero = TrailVertex::zero();
109                mesh.replace_quad(self.offset * 4, Quad::new(zero, zero, zero, zero));
110            });
111
112            // Clear meshes for entities that only have zero quads in mesh
113            self.entity_meshes
114                .retain(|_, (_mesh, last_updated)| *last_updated != self.offset);
115
116            // TODO: as an optimization we can keep this big mesh around between frames and
117            // write directly to it for each entity.
118            // Create big mesh from currently existing meshes that is used to update dynamic
119            // model
120            let mut big_mesh = Mesh::new();
121            self.entity_meshes
122                .values()
123                // If any of the vertices in a mesh are non-zero, upload the entire mesh for the entity
124                .filter(|(mesh, _)| mesh.iter().any(|vert| *vert != TrailVertex::zero()))
125                .for_each(|(mesh, _)| big_mesh.push_mesh(mesh));
126
127            // To avoid empty mesh
128            if big_mesh.is_empty() {
129                let zero = TrailVertex::zero();
130                big_mesh.push_quad(Quad::new(zero, zero, zero, zero));
131            }
132
133            // If dynamic model too small, resize, with room for 10 additional entities to
134            // avoid needing to resize frequently
135            if self.dynamic_model.as_ref().map_or(0, |model| model.len()) < big_mesh.len() {
136                self.dynamic_model = Some(
137                    renderer
138                        .create_dynamic_model(big_mesh.len() + TRAIL_DYNAMIC_MODEL_SIZE * 4 * 10),
139                );
140            }
141            if let Some(dynamic_model) = &self.dynamic_model {
142                renderer.update_model(dynamic_model, &big_mesh, 0);
143            }
144            self.model_len = big_mesh.len() as u32;
145        } else {
146            self.entity_meshes.clear();
147            // Clear dynamic model to free memory
148            self.dynamic_model = None;
149        }
150    }
151
152    pub fn render<'a>(&'a self, drawer: &mut TrailDrawer<'_, 'a>, scene_data: &SceneData) {
153        span!(_guard, "render", "TrailMgr::render");
154        if scene_data.weapon_trails_enabled {
155            if let Some(dynamic_model) = &self.dynamic_model {
156                drawer.draw(dynamic_model.submodel(0..self.model_len))
157            }
158        }
159    }
160
161    pub fn entity_mesh_or_insert(
162        &mut self,
163        entity: EcsEntity,
164        is_main_weapon: bool,
165    ) -> &mut Mesh<TrailVertex> {
166        let key = MeshKey {
167            entity,
168            is_main_weapon,
169        };
170        &mut self
171            .entity_meshes
172            .entry(key)
173            .and_modify(|(_mesh, offset)| *offset = self.offset)
174            .or_insert((Self::default_trail_mesh(), self.offset))
175            .0
176    }
177
178    fn default_trail_mesh() -> Mesh<TrailVertex> {
179        let mut mesh = Mesh::new();
180        let zero = TrailVertex::zero();
181        for _ in 0..TRAIL_DYNAMIC_MODEL_SIZE {
182            mesh.push_quad(Quad::new(zero, zero, zero, zero));
183        }
184        mesh
185    }
186
187    pub fn offset(&self) -> usize { self.offset }
188}