veloren_voxygen/scene/
trail.rs

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