veloren_voxygen/scene/figure/
mod.rs

1pub mod cache;
2pub mod load;
3mod volume;
4
5pub(super) use cache::FigureModelCache;
6use common_net::synced_components::Heads;
7pub use load::load_mesh; // TODO: Don't make this public.
8pub use volume::VolumeKey;
9
10use crate::{
11    ecs::comp::Interpolated,
12    render::{
13        AltIndices, CullingMode, FigureBoneData, FigureDrawer, FigureLocals, FigureModel,
14        FigureShadowDrawer, Instances, Mesh, Quad, RenderError, Renderer, SpriteDrawer,
15        SpriteInstance, SubModel, TerrainVertex,
16        pipelines::{
17            self, AtlasData, AtlasTextures, FigureSpriteAtlasData,
18            terrain::{BoundLocals as BoundTerrainLocals, Locals as TerrainLocals},
19            trail,
20        },
21    },
22    scene::{
23        RAIN_THRESHOLD, SceneData, TrailMgr,
24        camera::{Camera, CameraMode, Dependents},
25        math,
26        terrain::Terrain,
27    },
28};
29#[cfg(feature = "plugins")]
30use anim::plugin::PluginSkeleton;
31use anim::{
32    Animation, Skeleton,
33    arthropod::{self, ArthropodSkeleton},
34    biped_large::{self, BipedLargeSkeleton},
35    biped_small::{self, BipedSmallSkeleton},
36    bird_large::{self, BirdLargeSkeleton},
37    bird_medium::{self, BirdMediumSkeleton},
38    character::{self, CharacterSkeleton},
39    crustacean::{self, CrustaceanSkeleton},
40    dragon::{self, DragonSkeleton},
41    fish_medium::{self, FishMediumSkeleton},
42    fish_small::{self, FishSmallSkeleton},
43    golem::{self, GolemSkeleton},
44    item::ItemSkeleton,
45    object::ObjectSkeleton,
46    quadruped_low::{self, QuadrupedLowSkeleton},
47    quadruped_medium::{self, QuadrupedMediumSkeleton},
48    quadruped_small::{self, QuadrupedSmallSkeleton},
49    ship::ShipSkeleton,
50    theropod::{self, TheropodSkeleton},
51};
52use common::{
53    comp::{
54        Body, CharacterActivity, CharacterState, Collider, Controller, Health, Inventory, ItemKey,
55        Last, LightAnimation, LightEmitter, Object, Ori, PhysicsState, PickupItem, PoiseState, Pos,
56        Scale, ThrownItem, Vel,
57        body::{self, parts::HeadState},
58        inventory::slot::EquipSlot,
59        item::{Hands, ItemKind, ToolKind, armor::ArmorKind},
60        ship::{self, figuredata::VOXEL_COLLIDER_MANIFEST},
61        slot::ArmorSlot,
62    },
63    interaction::InteractionKind,
64    link::Is,
65    mounting::{Mount, Rider, Volume, VolumeRider, VolumeRiders},
66    resources::{DeltaTime, Time},
67    slowjob::SlowJobPool,
68    states::{equipping, idle, interact, utils::StageSection, wielding},
69    terrain::{SpriteKind, TerrainChunk, TerrainGrid},
70    uid::IdMaps,
71    util::Dir,
72    vol::RectRasterableVol,
73};
74use common_base::span;
75use common_state::State;
76use core::{
77    borrow::Borrow,
78    convert::TryFrom,
79    hash::Hash,
80    ops::{Deref, DerefMut, Range},
81};
82use guillotiere::AtlasAllocator;
83use hashbrown::HashMap;
84use specs::{
85    Entities, Entity as EcsEntity, Join, LazyUpdate, LendJoin, ReadExpect, ReadStorage, SystemData,
86    WorldExt, shred,
87};
88use std::sync::Arc;
89use treeculler::{BVol, BoundingSphere};
90use vek::*;
91
92use super::terrain::{BlocksOfInterest, SPRITE_LOD_LEVELS};
93
94const DAMAGE_FADE_COEFFICIENT: f64 = 15.0;
95const MOVING_THRESHOLD: f32 = 0.2;
96const MOVING_THRESHOLD_SQR: f32 = MOVING_THRESHOLD * MOVING_THRESHOLD;
97
98/// camera data, figure LOD render distance.
99pub type CameraData<'a> = (&'a Camera, f32);
100
101/// Enough data to render a figure model.
102pub type FigureModelRef<'a> = (
103    &'a pipelines::figure::BoundLocals,
104    SubModel<'a, TerrainVertex>,
105    &'a AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData>,
106);
107
108pub trait ModelEntry {
109    fn allocation(&self) -> &guillotiere::Allocation;
110
111    fn lod_model(&self, lod: usize) -> Option<SubModel<'_, TerrainVertex>>;
112
113    fn atlas_textures(&self) -> &AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData>;
114}
115
116/// An entry holding enough information to draw or destroy a figure in a
117/// particular cache.
118pub struct FigureModelEntry<const N: usize> {
119    /// The estimated bounds of this figure, in voxels.  This may not be very
120    /// useful yet.
121    _bounds: math::Aabb<f32>,
122    /// Hypothetical texture atlas allocation data for the current figure.
123    /// Will be useful if we decide to use a packed texture atlas for figures
124    /// like we do for terrain.
125    allocation: guillotiere::Allocation,
126    /// Texture used to store color/light information for this figure entry.
127    /* TODO: Consider using mipmaps instead of storing multiple texture atlases for different
128     * LOD levels. */
129    atlas_textures: AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData>,
130    /// Vertex ranges stored in this figure entry; there may be several for one
131    /// figure, because of LOD models.
132    lod_vertex_ranges: [Range<u32>; N],
133    model: FigureModel,
134}
135
136impl<const N: usize> ModelEntry for FigureModelEntry<N> {
137    fn allocation(&self) -> &guillotiere::Allocation { &self.allocation }
138
139    fn lod_model(&self, lod: usize) -> Option<SubModel<'_, TerrainVertex>> {
140        // Note: Range doesn't impl Copy even for trivially Cloneable things
141        self.model
142            .opaque
143            .as_ref()
144            .map(|m| m.submodel(self.lod_vertex_ranges[lod].clone()))
145    }
146
147    fn atlas_textures(&self) -> &AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData> {
148        &self.atlas_textures
149    }
150}
151
152/// An entry holding enough information to draw or destroy a figure in a
153/// particular cache.
154pub struct TerrainModelEntry<const N: usize> {
155    /// The estimated bounds of this figure, in voxels.  This may not be very
156    /// useful yet.
157    _bounds: math::Aabb<f32>,
158    /// Hypothetical texture atlas allocation data for the current figure.
159    /// Will be useful if we decide to use a packed texture atlas for figures
160    /// like we do for terrain.
161    allocation: guillotiere::Allocation,
162    /// Texture used to store color/light information for this figure entry.
163    /* TODO: Consider using mipmaps instead of storing multiple texture atlases for different
164     * LOD levels. */
165    atlas_textures: AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData>,
166    /// Vertex ranges stored in this figure entry; there may be several for one
167    /// figure, because of LOD models.
168    lod_vertex_ranges: [Range<u32>; N],
169    model: FigureModel,
170
171    blocks_offset: Vec3<f32>,
172
173    sprite_instances: [Instances<SpriteInstance>; SPRITE_LOD_LEVELS],
174
175    blocks_of_interest: BlocksOfInterest,
176}
177
178impl<const N: usize> ModelEntry for TerrainModelEntry<N> {
179    fn allocation(&self) -> &guillotiere::Allocation { &self.allocation }
180
181    fn lod_model(&self, lod: usize) -> Option<SubModel<'_, TerrainVertex>> {
182        // Note: Range doesn't impl Copy even for trivially Cloneable things
183        self.model
184            .opaque
185            .as_ref()
186            .map(|m| m.submodel(self.lod_vertex_ranges[lod].clone()))
187    }
188
189    fn atlas_textures(&self) -> &AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData> {
190        &self.atlas_textures
191    }
192}
193
194#[derive(Clone, Copy)]
195pub enum ModelEntryRef<'a, const N: usize> {
196    Figure(&'a FigureModelEntry<N>),
197    Terrain(&'a TerrainModelEntry<N>),
198}
199
200impl<'a, const N: usize> ModelEntryRef<'a, N> {
201    fn lod_model(&self, lod: usize) -> Option<SubModel<'a, TerrainVertex>> {
202        match self {
203            ModelEntryRef::Figure(e) => e.lod_model(lod),
204            ModelEntryRef::Terrain(e) => e.lod_model(lod),
205        }
206    }
207
208    fn atlas_textures(
209        &self,
210    ) -> &'a AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData> {
211        match self {
212            ModelEntryRef::Figure(e) => e.atlas_textures(),
213            ModelEntryRef::Terrain(e) => e.atlas_textures(),
214        }
215    }
216}
217
218#[derive(Default)]
219pub struct FigureMgrStates {
220    pub character_states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
221    pub quadruped_small_states: HashMap<EcsEntity, FigureState<QuadrupedSmallSkeleton>>,
222    pub quadruped_medium_states: HashMap<EcsEntity, FigureState<QuadrupedMediumSkeleton>>,
223    pub quadruped_low_states: HashMap<EcsEntity, FigureState<QuadrupedLowSkeleton>>,
224    pub bird_medium_states: HashMap<EcsEntity, FigureState<BirdMediumSkeleton>>,
225    pub fish_medium_states: HashMap<EcsEntity, FigureState<FishMediumSkeleton>>,
226    pub theropod_states: HashMap<EcsEntity, FigureState<TheropodSkeleton>>,
227    pub dragon_states: HashMap<EcsEntity, FigureState<DragonSkeleton>>,
228    pub bird_large_states: HashMap<EcsEntity, FigureState<BirdLargeSkeleton>>,
229    pub fish_small_states: HashMap<EcsEntity, FigureState<FishSmallSkeleton>>,
230    pub biped_large_states: HashMap<EcsEntity, FigureState<BipedLargeSkeleton>>,
231    pub biped_small_states: HashMap<EcsEntity, FigureState<BipedSmallSkeleton>>,
232    pub golem_states: HashMap<EcsEntity, FigureState<GolemSkeleton>>,
233    pub object_states: HashMap<EcsEntity, FigureState<ObjectSkeleton>>,
234    pub item_states: HashMap<EcsEntity, FigureState<ItemSkeleton>>,
235    pub ship_states: HashMap<EcsEntity, FigureState<ShipSkeleton, BoundTerrainLocals>>,
236    pub volume_states: HashMap<EcsEntity, FigureState<VolumeKey, BoundTerrainLocals>>,
237    pub arthropod_states: HashMap<EcsEntity, FigureState<ArthropodSkeleton>>,
238    pub crustacean_states: HashMap<EcsEntity, FigureState<CrustaceanSkeleton>>,
239    #[cfg(feature = "plugins")]
240    pub plugin_states: HashMap<EcsEntity, FigureState<PluginSkeleton>>,
241}
242
243impl FigureMgrStates {
244    fn get_mut<'a, Q>(&'a mut self, body: &Body, entity: &Q) -> Option<&'a mut FigureStateMeta>
245    where
246        EcsEntity: Borrow<Q>,
247        Q: Hash + Eq + ?Sized,
248    {
249        match body {
250            Body::Humanoid(_) => self
251                .character_states
252                .get_mut(entity)
253                .map(DerefMut::deref_mut),
254            Body::QuadrupedSmall(_) => self
255                .quadruped_small_states
256                .get_mut(entity)
257                .map(DerefMut::deref_mut),
258            Body::QuadrupedMedium(_) => self
259                .quadruped_medium_states
260                .get_mut(entity)
261                .map(DerefMut::deref_mut),
262            Body::QuadrupedLow(_) => self
263                .quadruped_low_states
264                .get_mut(entity)
265                .map(DerefMut::deref_mut),
266            Body::BirdMedium(_) => self
267                .bird_medium_states
268                .get_mut(entity)
269                .map(DerefMut::deref_mut),
270            Body::FishMedium(_) => self
271                .fish_medium_states
272                .get_mut(entity)
273                .map(DerefMut::deref_mut),
274            Body::Theropod(_) => self
275                .theropod_states
276                .get_mut(entity)
277                .map(DerefMut::deref_mut),
278            Body::Dragon(_) => self.dragon_states.get_mut(entity).map(DerefMut::deref_mut),
279            Body::BirdLarge(_) => self
280                .bird_large_states
281                .get_mut(entity)
282                .map(DerefMut::deref_mut),
283            Body::FishSmall(_) => self
284                .fish_small_states
285                .get_mut(entity)
286                .map(DerefMut::deref_mut),
287            Body::BipedLarge(_) => self
288                .biped_large_states
289                .get_mut(entity)
290                .map(DerefMut::deref_mut),
291            Body::BipedSmall(_) => self
292                .biped_small_states
293                .get_mut(entity)
294                .map(DerefMut::deref_mut),
295            Body::Golem(_) => self.golem_states.get_mut(entity).map(DerefMut::deref_mut),
296            Body::Object(_) => self.object_states.get_mut(entity).map(DerefMut::deref_mut),
297            Body::Item(_) => self.item_states.get_mut(entity).map(DerefMut::deref_mut),
298            Body::Ship(ship) => {
299                if ship.manifest_entry().is_some() {
300                    self.ship_states.get_mut(entity).map(DerefMut::deref_mut)
301                } else {
302                    self.volume_states.get_mut(entity).map(DerefMut::deref_mut)
303                }
304            },
305            Body::Arthropod(_) => self
306                .arthropod_states
307                .get_mut(entity)
308                .map(DerefMut::deref_mut),
309            Body::Crustacean(_) => self
310                .crustacean_states
311                .get_mut(entity)
312                .map(DerefMut::deref_mut),
313            Body::Plugin(_body) => {
314                #[cfg(not(feature = "plugins"))]
315                unreachable!("Plugins require feature");
316                #[cfg(feature = "plugins")]
317                self.plugin_states.get_mut(entity).map(DerefMut::deref_mut)
318            },
319        }
320    }
321
322    fn remove<Q>(&mut self, body: &Body, entity: &Q) -> Option<FigureStateMeta>
323    where
324        EcsEntity: Borrow<Q>,
325        Q: Hash + Eq + ?Sized,
326    {
327        match body {
328            Body::Humanoid(_) => self.character_states.remove(entity).map(|e| e.meta),
329            Body::QuadrupedSmall(_) => self.quadruped_small_states.remove(entity).map(|e| e.meta),
330            Body::QuadrupedMedium(_) => self.quadruped_medium_states.remove(entity).map(|e| e.meta),
331            Body::QuadrupedLow(_) => self.quadruped_low_states.remove(entity).map(|e| e.meta),
332            Body::BirdMedium(_) => self.bird_medium_states.remove(entity).map(|e| e.meta),
333            Body::FishMedium(_) => self.fish_medium_states.remove(entity).map(|e| e.meta),
334            Body::Theropod(_) => self.theropod_states.remove(entity).map(|e| e.meta),
335            Body::Dragon(_) => self.dragon_states.remove(entity).map(|e| e.meta),
336            Body::BirdLarge(_) => self.bird_large_states.remove(entity).map(|e| e.meta),
337            Body::FishSmall(_) => self.fish_small_states.remove(entity).map(|e| e.meta),
338            Body::BipedLarge(_) => self.biped_large_states.remove(entity).map(|e| e.meta),
339            Body::BipedSmall(_) => self.biped_small_states.remove(entity).map(|e| e.meta),
340            Body::Golem(_) => self.golem_states.remove(entity).map(|e| e.meta),
341            Body::Object(_) => self.object_states.remove(entity).map(|e| e.meta),
342            Body::Item(_) => self.item_states.remove(entity).map(|e| e.meta),
343            Body::Ship(ship) => {
344                if matches!(ship, ship::Body::Volume) {
345                    self.volume_states.remove(entity).map(|e| e.meta)
346                } else if ship.manifest_entry().is_some() {
347                    self.ship_states.remove(entity).map(|e| e.meta)
348                } else {
349                    None
350                }
351            },
352            Body::Arthropod(_) => self.arthropod_states.remove(entity).map(|e| e.meta),
353            Body::Crustacean(_) => self.crustacean_states.remove(entity).map(|e| e.meta),
354            Body::Plugin(_) => {
355                #[cfg(not(feature = "plugins"))]
356                unreachable!("Plugins require feature");
357                #[cfg(feature = "plugins")]
358                self.plugin_states.remove(entity).map(|e| e.meta)
359            },
360        }
361    }
362
363    fn retain(&mut self, mut f: impl FnMut(&EcsEntity, &mut FigureStateMeta) -> bool) {
364        span!(_guard, "retain", "FigureManagerStates::retain");
365        self.character_states.retain(|k, v| f(k, &mut *v));
366        self.quadruped_small_states.retain(|k, v| f(k, &mut *v));
367        self.quadruped_medium_states.retain(|k, v| f(k, &mut *v));
368        self.quadruped_low_states.retain(|k, v| f(k, &mut *v));
369        self.bird_medium_states.retain(|k, v| f(k, &mut *v));
370        self.fish_medium_states.retain(|k, v| f(k, &mut *v));
371        self.theropod_states.retain(|k, v| f(k, &mut *v));
372        self.dragon_states.retain(|k, v| f(k, &mut *v));
373        self.bird_large_states.retain(|k, v| f(k, &mut *v));
374        self.fish_small_states.retain(|k, v| f(k, &mut *v));
375        self.biped_large_states.retain(|k, v| f(k, &mut *v));
376        self.biped_small_states.retain(|k, v| f(k, &mut *v));
377        self.golem_states.retain(|k, v| f(k, &mut *v));
378        self.object_states.retain(|k, v| f(k, &mut *v));
379        self.item_states.retain(|k, v| f(k, &mut *v));
380        self.ship_states.retain(|k, v| f(k, &mut *v));
381        self.volume_states.retain(|k, v| f(k, &mut *v));
382        self.arthropod_states.retain(|k, v| f(k, &mut *v));
383        self.crustacean_states.retain(|k, v| f(k, &mut *v));
384        #[cfg(feature = "plugins")]
385        self.plugin_states.retain(|k, v| f(k, &mut *v));
386    }
387
388    fn count(&self) -> usize {
389        #[cfg(feature = "plugins")]
390        let plugin_states = self.plugin_states.len();
391        #[cfg(not(feature = "plugins"))]
392        let plugin_states = 0;
393        self.character_states.len()
394            + self.quadruped_small_states.len()
395            + self.character_states.len()
396            + self.quadruped_medium_states.len()
397            + self.quadruped_low_states.len()
398            + self.bird_medium_states.len()
399            + self.fish_medium_states.len()
400            + self.theropod_states.len()
401            + self.dragon_states.len()
402            + self.bird_large_states.len()
403            + self.fish_small_states.len()
404            + self.biped_large_states.len()
405            + self.biped_small_states.len()
406            + self.golem_states.len()
407            + self.object_states.len()
408            + self.item_states.len()
409            + self.ship_states.len()
410            + self.volume_states.len()
411            + self.arthropod_states.len()
412            + self.crustacean_states.len()
413            + plugin_states
414    }
415
416    fn count_visible(&self) -> usize {
417        #[cfg(feature = "plugins")]
418        let plugin_states = self
419            .plugin_states
420            .iter()
421            .filter(|(_, c)| c.visible())
422            .count();
423        #[cfg(not(feature = "plugins"))]
424        let plugin_states = 0;
425        self.character_states
426            .iter()
427            .filter(|(_, c)| c.visible())
428            .count()
429            + self
430                .quadruped_small_states
431                .iter()
432                .filter(|(_, c)| c.visible())
433                .count()
434            + self
435                .quadruped_medium_states
436                .iter()
437                .filter(|(_, c)| c.visible())
438                .count()
439            + self
440                .quadruped_low_states
441                .iter()
442                .filter(|(_, c)| c.visible())
443                .count()
444            + self
445                .bird_medium_states
446                .iter()
447                .filter(|(_, c)| c.visible())
448                .count()
449            + self
450                .theropod_states
451                .iter()
452                .filter(|(_, c)| c.visible())
453                .count()
454            + self
455                .dragon_states
456                .iter()
457                .filter(|(_, c)| c.visible())
458                .count()
459            + self
460                .fish_medium_states
461                .iter()
462                .filter(|(_, c)| c.visible())
463                .count()
464            + self
465                .bird_large_states
466                .iter()
467                .filter(|(_, c)| c.visible())
468                .count()
469            + self
470                .fish_small_states
471                .iter()
472                .filter(|(_, c)| c.visible())
473                .count()
474            + self
475                .biped_large_states
476                .iter()
477                .filter(|(_, c)| c.visible())
478                .count()
479            + self
480                .biped_small_states
481                .iter()
482                .filter(|(_, c)| c.visible())
483                .count()
484            + self
485                .golem_states
486                .iter()
487                .filter(|(_, c)| c.visible())
488                .count()
489            + self
490                .object_states
491                .iter()
492                .filter(|(_, c)| c.visible())
493                .count()
494            + self.item_states.iter().filter(|(_, c)| c.visible()).count()
495            + self
496                .arthropod_states
497                .iter()
498                .filter(|(_, c)| c.visible())
499                .count()
500            + self
501                .crustacean_states
502                .iter()
503                .filter(|(_, c)| c.visible())
504                .count()
505            + self.ship_states.iter().filter(|(_, c)| c.visible()).count()
506            + self
507                .volume_states
508                .iter()
509                .filter(|(_, c)| c.visible())
510                .count()
511            + plugin_states
512    }
513
514    fn get_terrain_locals<'a, Q>(
515        &'a self,
516        body: &Body,
517        entity: &Q,
518    ) -> Option<&'a BoundTerrainLocals>
519    where
520        EcsEntity: Borrow<Q>,
521        Q: Hash + Eq + ?Sized,
522    {
523        match body {
524            Body::Ship(body) => {
525                if matches!(body, ship::Body::Volume) {
526                    self.volume_states.get(entity).map(|state| &state.extra)
527                } else if body.manifest_entry().is_some() {
528                    self.ship_states.get(entity).map(|state| &state.extra)
529                } else {
530                    None
531                }
532            },
533            _ => None,
534        }
535    }
536}
537
538#[derive(SystemData)]
539struct FigureReadData<'a> {
540    terrain_grid: ReadExpect<'a, TerrainGrid>,
541    id_maps: ReadExpect<'a, IdMaps>,
542    entities: Entities<'a>,
543    positions: ReadStorage<'a, Pos>,
544    controllers: ReadStorage<'a, Controller>,
545    interpolated: ReadStorage<'a, Interpolated>,
546    velocities: ReadStorage<'a, Vel>,
547    scales: ReadStorage<'a, Scale>,
548    bodies: ReadStorage<'a, Body>,
549    character_states: ReadStorage<'a, CharacterState>,
550    character_activitys: ReadStorage<'a, CharacterActivity>,
551    last_character_states: ReadStorage<'a, Last<CharacterState>>,
552    physics_states: ReadStorage<'a, PhysicsState>,
553    healths: ReadStorage<'a, Health>,
554    inventories: ReadStorage<'a, Inventory>,
555    pickup_items: ReadStorage<'a, PickupItem>,
556    thrown_items: ReadStorage<'a, ThrownItem>,
557    light_emitters: ReadStorage<'a, LightEmitter>,
558    is_riders: ReadStorage<'a, Is<Rider>>,
559    is_mounts: ReadStorage<'a, Is<Mount>>,
560    is_volume_riders: ReadStorage<'a, Is<VolumeRider>>,
561    volume_riders: ReadStorage<'a, VolumeRiders>,
562    colliders: ReadStorage<'a, Collider>,
563    heads: ReadStorage<'a, Heads>,
564}
565
566struct FigureUpdateData<'a, CSS, COR> {
567    #[cfg(feature = "plugins")]
568    plugins: &'a mut common_state::plugin::PluginMgr,
569    scene_data: &'a SceneData<'a>,
570    terrain: Option<&'a Terrain>,
571    camera_mode: CameraMode,
572    can_shadow_sun: CSS,
573    can_occlude_rain: COR,
574    tick: u64,
575    time: f32,
576    renderer: &'a mut Renderer,
577    trail_mgr: &'a mut TrailMgr,
578    slow_jobs: &'a SlowJobPool,
579    update_buf: &'a mut [anim::FigureBoneData; anim::MAX_BONE_COUNT],
580    dt_lerp: f32,
581    dt: f32,
582    player_pos: anim::vek::Vec3<f32>,
583    view_distance: u32,
584    frustum: &'a treeculler::Frustum<f32>,
585    focus_pos: anim::vek::Vec3<f32>,
586}
587
588impl FigureReadData<'_> {
589    pub fn get_entity(&self, entity: EcsEntity) -> Option<FigureUpdateParams<'_>> {
590        Some(FigureUpdateParams {
591            entity,
592            pos: self.positions.get(entity)?,
593            controller: self.controllers.get(entity),
594            interpolated: self.interpolated.get(entity),
595            vel: self.velocities.get(entity)?,
596            scale: self.scales.get(entity),
597            body: self.bodies.get(entity)?,
598            character_state: self.character_states.get(entity),
599            character_activity: self.character_activitys.get(entity),
600            last_character_state: self.last_character_states.get(entity),
601            physics_state: self.physics_states.get(entity)?,
602            health: self.healths.get(entity),
603            inventory: self.inventories.get(entity),
604            pickup_item: self.pickup_items.get(entity),
605            thrown_item: self.thrown_items.get(entity),
606            light_emitter: self.light_emitters.get(entity),
607            is_rider: self.is_riders.get(entity),
608            is_mount: self.is_mounts.get(entity),
609            is_volume_rider: self.is_volume_riders.get(entity),
610            volume_riders: self.volume_riders.get(entity),
611            collider: self.colliders.get(entity),
612            heads: self.heads.get(entity),
613        })
614    }
615
616    pub fn iter(&self) -> impl Iterator<Item = FigureUpdateParams<'_>> {
617        (
618            &self.entities,
619            &self.positions,
620            self.controllers.maybe(),
621            self.interpolated.maybe(),
622            &self.velocities,
623            self.scales.maybe(),
624            &self.bodies,
625            self.character_states.maybe(),
626            self.character_activitys.maybe(),
627            self.last_character_states.maybe(),
628            &self.physics_states,
629            self.healths.maybe(),
630            self.inventories.maybe(),
631            self.pickup_items.maybe(),
632            (
633                self.thrown_items.maybe(),
634                self.light_emitters.maybe(),
635                self.is_riders.maybe(),
636                self.is_mounts.maybe(),
637                self.is_volume_riders.maybe(),
638                self.volume_riders.maybe(),
639                self.colliders.maybe(),
640                self.heads.maybe(),
641            ),
642        )
643            .join()
644            .map(
645                |(
646                    entity,
647                    pos,
648                    controller,
649                    interpolated,
650                    vel,
651                    scale,
652                    body,
653                    character_state,
654                    character_activity,
655                    last_character_state,
656                    physics_state,
657                    health,
658                    inventory,
659                    pickup_item,
660                    (
661                        thrown_item,
662                        light_emitter,
663                        is_rider,
664                        is_mount,
665                        is_volume_rider,
666                        volume_riders,
667                        collider,
668                        heads,
669                    ),
670                )| FigureUpdateParams {
671                    entity,
672                    pos,
673                    controller,
674                    interpolated,
675                    vel,
676                    scale,
677                    body,
678                    character_state,
679                    character_activity,
680                    last_character_state,
681                    physics_state,
682                    health,
683                    inventory,
684                    pickup_item,
685                    thrown_item,
686                    light_emitter,
687                    is_rider,
688                    is_mount,
689                    is_volume_rider,
690                    volume_riders,
691                    collider,
692                    heads,
693                },
694            )
695    }
696}
697
698struct FigureUpdateParams<'a> {
699    entity: EcsEntity,
700    pos: &'a Pos,
701    controller: Option<&'a Controller>,
702    interpolated: Option<&'a Interpolated>,
703    vel: &'a Vel,
704    scale: Option<&'a Scale>,
705    body: &'a Body,
706    character_state: Option<&'a CharacterState>,
707    character_activity: Option<&'a CharacterActivity>,
708    last_character_state: Option<&'a Last<CharacterState>>,
709    physics_state: &'a PhysicsState,
710    health: Option<&'a Health>,
711    inventory: Option<&'a Inventory>,
712    pickup_item: Option<&'a PickupItem>,
713    thrown_item: Option<&'a ThrownItem>,
714    light_emitter: Option<&'a LightEmitter>,
715    is_rider: Option<&'a Is<Rider>>,
716    is_mount: Option<&'a Is<Mount>>,
717    is_volume_rider: Option<&'a Is<VolumeRider>>,
718    volume_riders: Option<&'a VolumeRiders>,
719    collider: Option<&'a Collider>,
720    heads: Option<&'a Heads>,
721}
722
723pub struct FigureMgr {
724    atlas: FigureAtlas,
725    character_model_cache: FigureModelCache<CharacterSkeleton>,
726    theropod_model_cache: FigureModelCache<TheropodSkeleton>,
727    quadruped_small_model_cache: FigureModelCache<QuadrupedSmallSkeleton>,
728    quadruped_medium_model_cache: FigureModelCache<QuadrupedMediumSkeleton>,
729    quadruped_low_model_cache: FigureModelCache<QuadrupedLowSkeleton>,
730    bird_medium_model_cache: FigureModelCache<BirdMediumSkeleton>,
731    bird_large_model_cache: FigureModelCache<BirdLargeSkeleton>,
732    dragon_model_cache: FigureModelCache<DragonSkeleton>,
733    fish_medium_model_cache: FigureModelCache<FishMediumSkeleton>,
734    fish_small_model_cache: FigureModelCache<FishSmallSkeleton>,
735    biped_large_model_cache: FigureModelCache<BipedLargeSkeleton>,
736    biped_small_model_cache: FigureModelCache<BipedSmallSkeleton>,
737    object_model_cache: FigureModelCache<ObjectSkeleton>,
738    item_model_cache: FigureModelCache<ItemSkeleton>,
739    ship_model_cache: FigureModelCache<ShipSkeleton>,
740    golem_model_cache: FigureModelCache<GolemSkeleton>,
741    volume_model_cache: FigureModelCache<VolumeKey>,
742    arthropod_model_cache: FigureModelCache<ArthropodSkeleton>,
743    crustacean_model_cache: FigureModelCache<CrustaceanSkeleton>,
744    #[cfg(feature = "plugins")]
745    plugin_model_cache: FigureModelCache<PluginSkeleton>,
746    pub states: FigureMgrStates,
747}
748
749impl FigureMgr {
750    pub fn new(renderer: &mut Renderer) -> Self {
751        Self {
752            atlas: FigureAtlas::new(renderer),
753            character_model_cache: FigureModelCache::new(),
754            theropod_model_cache: FigureModelCache::new(),
755            quadruped_small_model_cache: FigureModelCache::new(),
756            quadruped_medium_model_cache: FigureModelCache::new(),
757            quadruped_low_model_cache: FigureModelCache::new(),
758            bird_medium_model_cache: FigureModelCache::new(),
759            bird_large_model_cache: FigureModelCache::new(),
760            dragon_model_cache: FigureModelCache::new(),
761            fish_medium_model_cache: FigureModelCache::new(),
762            fish_small_model_cache: FigureModelCache::new(),
763            biped_large_model_cache: FigureModelCache::new(),
764            biped_small_model_cache: FigureModelCache::new(),
765            object_model_cache: FigureModelCache::new(),
766            item_model_cache: FigureModelCache::new(),
767            ship_model_cache: FigureModelCache::new(),
768            golem_model_cache: FigureModelCache::new(),
769            volume_model_cache: FigureModelCache::new(),
770            arthropod_model_cache: FigureModelCache::new(),
771            crustacean_model_cache: FigureModelCache::new(),
772            #[cfg(feature = "plugins")]
773            plugin_model_cache: FigureModelCache::new(),
774            states: FigureMgrStates::default(),
775        }
776    }
777
778    pub fn atlas(&self) -> &FigureAtlas { &self.atlas }
779
780    fn any_watcher_reloaded(&mut self) -> bool {
781        #[cfg(feature = "plugins")]
782        let plugin_reloaded = self.plugin_model_cache.watcher_reloaded();
783        #[cfg(not(feature = "plugins"))]
784        let plugin_reloaded = false;
785        self.character_model_cache.watcher_reloaded()
786            || self.theropod_model_cache.watcher_reloaded()
787            || self.quadruped_small_model_cache.watcher_reloaded()
788            || self.quadruped_medium_model_cache.watcher_reloaded()
789            || self.quadruped_low_model_cache.watcher_reloaded()
790            || self.bird_medium_model_cache.watcher_reloaded()
791            || self.bird_large_model_cache.watcher_reloaded()
792            || self.dragon_model_cache.watcher_reloaded()
793            || self.fish_medium_model_cache.watcher_reloaded()
794            || self.fish_small_model_cache.watcher_reloaded()
795            || self.biped_large_model_cache.watcher_reloaded()
796            || self.biped_small_model_cache.watcher_reloaded()
797            || self.object_model_cache.watcher_reloaded()
798            || self.item_model_cache.watcher_reloaded()
799            || self.ship_model_cache.watcher_reloaded()
800            || self.golem_model_cache.watcher_reloaded()
801            || self.volume_model_cache.watcher_reloaded()
802            || self.arthropod_model_cache.watcher_reloaded()
803            || self.crustacean_model_cache.watcher_reloaded()
804            || plugin_reloaded
805    }
806
807    pub fn clean(&mut self, tick: u64) {
808        span!(_guard, "clean", "FigureManager::clean");
809
810        if self.any_watcher_reloaded() {
811            self.atlas.allocator.clear();
812
813            self.character_model_cache.clear_models();
814            self.theropod_model_cache.clear_models();
815            self.quadruped_small_model_cache.clear_models();
816            self.quadruped_medium_model_cache.clear_models();
817            self.quadruped_low_model_cache.clear_models();
818            self.bird_medium_model_cache.clear_models();
819            self.bird_large_model_cache.clear_models();
820            self.dragon_model_cache.clear_models();
821            self.fish_medium_model_cache.clear_models();
822            self.fish_small_model_cache.clear_models();
823            self.biped_large_model_cache.clear_models();
824            self.biped_small_model_cache.clear_models();
825            self.object_model_cache.clear_models();
826            self.item_model_cache.clear_models();
827            self.ship_model_cache.clear_models();
828            self.golem_model_cache.clear_models();
829            self.volume_model_cache.clear_models();
830            self.arthropod_model_cache.clear_models();
831            self.crustacean_model_cache.clear_models();
832            #[cfg(feature = "plugins")]
833            self.plugin_model_cache.clear_models();
834        }
835
836        self.character_model_cache.clean(&mut self.atlas, tick);
837        self.theropod_model_cache.clean(&mut self.atlas, tick);
838        self.quadruped_small_model_cache
839            .clean(&mut self.atlas, tick);
840        self.quadruped_medium_model_cache
841            .clean(&mut self.atlas, tick);
842        self.quadruped_low_model_cache.clean(&mut self.atlas, tick);
843        self.bird_medium_model_cache.clean(&mut self.atlas, tick);
844        self.bird_large_model_cache.clean(&mut self.atlas, tick);
845        self.dragon_model_cache.clean(&mut self.atlas, tick);
846        self.fish_medium_model_cache.clean(&mut self.atlas, tick);
847        self.fish_small_model_cache.clean(&mut self.atlas, tick);
848        self.biped_large_model_cache.clean(&mut self.atlas, tick);
849        self.biped_small_model_cache.clean(&mut self.atlas, tick);
850        self.object_model_cache.clean(&mut self.atlas, tick);
851        self.item_model_cache.clean(&mut self.atlas, tick);
852        self.ship_model_cache.clean(&mut self.atlas, tick);
853        self.golem_model_cache.clean(&mut self.atlas, tick);
854        self.volume_model_cache.clean(&mut self.atlas, tick);
855        self.arthropod_model_cache.clean(&mut self.atlas, tick);
856        self.crustacean_model_cache.clean(&mut self.atlas, tick);
857        #[cfg(feature = "plugins")]
858        self.plugin_model_cache.clean(&mut self.atlas, tick);
859    }
860
861    pub fn update_lighting(&mut self, scene_data: &SceneData) {
862        span!(_guard, "update_lighting", "FigureManager::update_lighting");
863        let ecs = scene_data.state.ecs();
864        for (entity, body, light_emitter) in (
865            &ecs.entities(),
866            ecs.read_storage::<Body>().maybe(),
867            &ecs.read_storage::<LightEmitter>(),
868        )
869            .join()
870        {
871            // Add LightAnimation for objects with a LightEmitter
872            let mut anim_storage = ecs.write_storage::<LightAnimation>();
873            if anim_storage.get_mut(entity).is_none() {
874                let anim = LightAnimation {
875                    offset: body
876                        .map(|b| b.default_light_offset())
877                        .unwrap_or_else(Vec3::zero),
878                    col: light_emitter.col,
879                    strength: light_emitter.strength,
880                    dir: light_emitter.dir,
881                };
882                let _ = anim_storage.insert(entity, anim);
883            }
884        }
885        let dt = ecs.fetch::<DeltaTime>().0;
886        let updater = ecs.read_resource::<LazyUpdate>();
887        for (entity, light_emitter_opt, interpolated, pos, ori, body, light_anim) in (
888            &ecs.entities(),
889            ecs.read_storage::<LightEmitter>().maybe(),
890            ecs.read_storage::<Interpolated>().maybe(),
891            &ecs.read_storage::<Pos>(),
892            ecs.read_storage::<Ori>().maybe(),
893            ecs.read_storage::<Body>().maybe(),
894            &mut ecs.write_storage::<LightAnimation>(),
895        )
896            .join()
897        {
898            let lantern_mat = self
899                .lantern_mat(entity)
900                .or_else(|| Some(ori?.to_quat().into()));
901            let (target_col, target_strength, flicker, animated, clamp) =
902                if let Some(emitter) = light_emitter_opt {
903                    // Transform light direction to lantern direction
904                    light_anim.dir = emitter
905                        .dir
906                        .zip(lantern_mat)
907                        .map(|((dir, fov), m)| (m.mul_direction(dir), fov))
908                        .or(emitter.dir);
909                    (
910                        emitter.col,
911                        if emitter.strength.is_normal() {
912                            emitter.strength
913                        } else {
914                            0.0
915                        },
916                        emitter.flicker,
917                        emitter.animated,
918                        true,
919                    )
920                } else {
921                    (Rgb::zero(), 0.0, 0.0, true, false)
922                };
923            let lantern_offset = match body {
924                Some(Body::Humanoid(_)) => {
925                    lantern_mat.map(|m| m.mul_point(Vec3::new(0.0, 0.5, -6.0)))
926                },
927                Some(Body::Item(_)) => lantern_mat.map(|m| m.mul_point(Vec3::new(0.0, 0.0, 3.5))),
928                _ => None,
929            };
930            if let Some(lantern_offset) = body
931                .and_then(|body| self.states.get_mut(body, &entity))
932                .and_then(|state| {
933                    // Calculate the correct lantern position
934                    let pos = anim::vek::Vec3::from(
935                        interpolated.map(|i| i.pos).unwrap_or(pos.0).into_array(),
936                    );
937                    Some(state.wpos_of(lantern_offset?) - pos)
938                })
939            {
940                light_anim.offset = lantern_offset;
941            } else if let Some(body) = body {
942                light_anim.offset = body.default_light_offset();
943            }
944            if !light_anim.strength.is_normal() {
945                light_anim.strength = 0.0;
946            }
947            if animated {
948                let flicker = (rand::random::<f32>() - 0.5) * flicker / dt.sqrt();
949                // Close gap between current and target strength by 95% per second
950                let delta = 0.05_f32.powf(dt);
951                light_anim.strength =
952                    light_anim.strength * delta + (target_strength + flicker) * (1.0 - delta);
953                light_anim.col = light_anim.col * delta + target_col * (1.0 - delta);
954                if clamp {
955                    light_anim.strength = light_anim.strength.min(target_strength);
956                    light_anim.col = Rgb::partial_min(light_anim.col, target_col);
957                }
958            } else {
959                light_anim.strength = target_strength;
960                light_anim.col = target_col;
961            }
962            // NOTE: We add `LIGHT_EPSILON` because if we wait for numbers to become
963            // equal to target (or even within a subnormal), it will take a minimum
964            // of 30 seconds for a light to fully turn off (for initial
965            // strength ≥ 1), which prevents optimizations (particularly those that
966            // can kick in with zero lights).
967            const LIGHT_EPSILON: f32 = 0.0001;
968            if (light_anim.strength - target_strength).abs() < LIGHT_EPSILON {
969                light_anim.strength = target_strength;
970                if light_anim.strength == 0.0 {
971                    updater.remove::<LightAnimation>(entity);
972                }
973            }
974        }
975    }
976
977    pub fn maintain(
978        &mut self,
979        renderer: &mut Renderer,
980        trail_mgr: &mut TrailMgr,
981        scene_data: &SceneData,
982        // Visible chunk data.
983        visible_psr_bounds: math::Aabr<f32>,
984        visible_por_bounds: math::Aabr<f32>,
985        camera: &Camera,
986        terrain: Option<&Terrain>,
987    ) -> anim::vek::Aabb<f32> {
988        span!(_guard, "maintain", "FigureManager::maintain");
989        let state = scene_data.state;
990        let time = state.get_time() as f32;
991        let tick = scene_data.tick;
992        let ecs = state.ecs();
993        let view_distance = scene_data.entity_view_distance;
994        let dt = state.get_delta_time();
995        let dt_lerp = (15.0 * dt).min(1.0);
996        let frustum = camera.frustum();
997
998        // Sun shadows--find the bounding box of the shadow map plane (i.e. the bounds
999        // of the image rendered from the light).  If the position projected
1000        // with the ray_mat matrix is valid, and shadows are otherwise enabled,
1001        // we mark can_shadow.
1002        // Rain occlusion is very similar to sun shadows, but using a different ray_mat,
1003        // and only if it's raining.
1004        let (can_shadow_sun, can_occlude_rain) = {
1005            let Dependents {
1006                proj_mat: _,
1007                view_mat: _,
1008                cam_pos,
1009                ..
1010            } = camera.dependents();
1011
1012            let sun_dir = scene_data.get_sun_dir();
1013            let is_daylight = sun_dir.z < 0.0/*0.6*/;
1014            // Are shadows enabled at all?
1015            let can_shadow_sun = renderer.pipeline_modes().shadow.is_map() && is_daylight;
1016
1017            let weather = scene_data.client.weather_at_player();
1018
1019            let focus_off = camera.get_focus_pos().map(f32::trunc);
1020            let focus_off_mat = math::Mat4::translation_3d(-focus_off);
1021
1022            let collides_with_aabr = |a: math::Aabr<f32>, b: math::Aabr<f32>| {
1023                let min = math::Vec4::new(a.min.x, a.min.y, b.min.x, b.min.y);
1024                let max = math::Vec4::new(b.max.x, b.max.y, a.max.x, a.max.y);
1025                #[cfg(feature = "simd")]
1026                return min.partial_cmple_simd(max).reduce_and();
1027                #[cfg(not(feature = "simd"))]
1028                return min.partial_cmple(&max).reduce_and();
1029            };
1030
1031            let can_shadow = |ray_direction: Vec3<f32>,
1032                              enabled: bool,
1033                              visible_bounds: math::Aabr<f32>| {
1034                // Transform (semi) world space to light space.
1035                let ray_mat: math::Mat4<f32> =
1036                    math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, math::Vec3::unit_y());
1037                let ray_mat = ray_mat * focus_off_mat;
1038                move |pos: (anim::vek::Vec3<f32>,), radius: f32| {
1039                    // Short circuit when there are no shadows to cast.
1040                    if !enabled {
1041                        return false;
1042                    }
1043                    // First project center onto shadow map.
1044                    let center = (ray_mat * math::Vec4::new(pos.0.x, pos.0.y, pos.0.z, 1.0)).xy();
1045                    // Then, create an approximate bounding box (± radius).
1046                    let figure_box = math::Aabr {
1047                        min: center - radius,
1048                        max: center + radius,
1049                    };
1050                    // Quick intersection test for membership in the PSC (potential shader caster)
1051                    // list.
1052                    collides_with_aabr(figure_box, visible_bounds)
1053                }
1054            };
1055            (
1056                can_shadow(sun_dir, can_shadow_sun, visible_psr_bounds),
1057                can_shadow(
1058                    weather.rain_vel(),
1059                    weather.rain > RAIN_THRESHOLD,
1060                    visible_por_bounds,
1061                ),
1062            )
1063        };
1064
1065        let read_data = ecs.system_data::<FigureReadData>();
1066
1067        // Get player position.
1068        let player_pos = read_data
1069            .positions
1070            .get(scene_data.viewpoint_entity)
1071            .map_or(anim::vek::Vec3::zero(), |pos| pos.0);
1072        let visible_aabb = anim::vek::Aabb {
1073            min: player_pos - 2.0,
1074            max: player_pos + 2.0,
1075        };
1076        let slow_jobs = state.slow_job_pool();
1077
1078        let focus_pos = camera.get_focus_pos();
1079
1080        let mut data = FigureUpdateData {
1081            #[cfg(feature = "plugins")]
1082            plugins: &mut ecs.write_resource(),
1083            scene_data,
1084            terrain,
1085            camera_mode: camera.get_mode(),
1086            can_shadow_sun,
1087            can_occlude_rain,
1088            tick,
1089            renderer,
1090            trail_mgr,
1091            slow_jobs: &slow_jobs,
1092            time,
1093            update_buf: &mut [Default::default(); anim::MAX_BONE_COUNT],
1094            dt_lerp,
1095            dt,
1096            player_pos,
1097            view_distance,
1098            frustum,
1099            focus_pos,
1100        };
1101
1102        fn update_riders(
1103            this: &mut FigureMgr,
1104            mount_data: &FigureUpdateParams,
1105            read_data: &FigureReadData,
1106            data: &mut FigureUpdateData<
1107                impl Fn((anim::vek::Vec3<f32>,), f32) -> bool,
1108                impl Fn((anim::vek::Vec3<f32>,), f32) -> bool,
1109            >,
1110        ) {
1111            if let Some(is_mount) = mount_data.is_mount
1112                && let Some(rider) = read_data.id_maps.uid_entity(is_mount.rider)
1113                && let Some(rider_data) = read_data.get_entity(rider)
1114            {
1115                this.maintain_entity(&rider_data, read_data, data);
1116                update_riders(this, &rider_data, read_data, data);
1117            }
1118            if let Some(volume_riders) = mount_data.volume_riders {
1119                for rider_data in volume_riders
1120                    .iter_riders()
1121                    .filter_map(|rider| read_data.id_maps.uid_entity(rider))
1122                    .filter_map(|rider| read_data.get_entity(rider))
1123                {
1124                    this.maintain_entity(&rider_data, read_data, data);
1125                    update_riders(this, &rider_data, read_data, data);
1126                }
1127            }
1128        }
1129
1130        for (i, entity_data) in read_data.iter().enumerate() {
1131            // Riders are updated by root-mount, as long as it is loaded.
1132            if entity_data
1133                .is_rider
1134                .is_some_and(|is_rider| read_data.id_maps.uid_entity(is_rider.mount).is_some())
1135                || entity_data
1136                    .is_volume_rider
1137                    .is_some_and(|is_volume_rider| match is_volume_rider.pos.kind {
1138                        Volume::Terrain => false,
1139                        Volume::Entity(uid) => read_data.id_maps.uid_entity(uid).is_some(),
1140                    })
1141            {
1142                continue;
1143            }
1144
1145            let pos = entity_data
1146                .interpolated
1147                .map_or(entity_data.pos.0, |i| i.pos);
1148
1149            // Maintaining figure data and sending new figure data to the GPU turns out to
1150            // be a very expensive operation. We want to avoid doing it as much
1151            // as possible, so we make the assumption that players don't care so
1152            // much about the update *rate* for far away things. As the entity
1153            // goes further and further away, we start to 'skip' update ticks.
1154            // TODO: Investigate passing the velocity into the shader so we can at least
1155            // interpolate motion
1156            const MIN_PERFECT_RATE_DIST: f32 = 100.0;
1157
1158            if !(i as u64 + data.tick).is_multiple_of(
1159                ((((pos.distance_squared(focus_pos) / entity_data.scale.map_or(1.0, |s| s.0))
1160                    .powf(0.25)
1161                    - MIN_PERFECT_RATE_DIST.sqrt())
1162                .max(0.0)
1163                    / 3.0) as u64)
1164                    .saturating_add(1),
1165            ) {
1166                continue;
1167            }
1168
1169            self.maintain_entity(&entity_data, &read_data, &mut data);
1170            update_riders(self, &entity_data, &read_data, &mut data);
1171        }
1172
1173        // Update lighting (lanterns) for figures
1174        self.update_lighting(scene_data);
1175
1176        // Clear states that have deleted entities.
1177        self.states
1178            .retain(|entity, _| ecs.entities().is_alive(*entity));
1179
1180        visible_aabb
1181    }
1182
1183    fn maintain_entity(
1184        &mut self,
1185        entity_data: &FigureUpdateParams,
1186        read_data: &FigureReadData,
1187        data: &mut FigureUpdateData<
1188            impl Fn((anim::vek::Vec3<f32>,), f32) -> bool,
1189            impl Fn((anim::vek::Vec3<f32>,), f32) -> bool,
1190        >,
1191    ) {
1192        let FigureUpdateParams {
1193            entity,
1194            pos,
1195            controller,
1196            interpolated,
1197            vel,
1198            scale,
1199            body,
1200            character_state: character,
1201            character_activity,
1202            last_character_state: last_character,
1203            physics_state: physics,
1204            health,
1205            inventory,
1206            pickup_item: item,
1207            thrown_item,
1208            light_emitter,
1209            is_rider,
1210            is_mount: _,
1211            is_volume_rider,
1212            volume_riders: _,
1213            collider,
1214            heads,
1215        } = *entity_data;
1216
1217        let renderer = &mut *data.renderer;
1218        let tick = data.tick;
1219        let slow_jobs = data.slow_jobs;
1220        let dt = data.dt;
1221        let time = data.time;
1222        let dt_lerp = data.dt_lerp;
1223        let update_buf = &mut *data.update_buf;
1224
1225        // Velocity relative to the current ground
1226        let rel_vel = (vel.0 - physics.ground_vel) / scale.map_or(1.0, |s| s.0);
1227
1228        // Priortise CharacterActivity as the source of the look direction
1229        let look_dir = character_activity.and_then(|ca| ca.look_dir)
1230                // Failing that, take the controller as the source of truth
1231                .or_else(|| controller.map(|c| c.inputs.look_dir))
1232                // If that still didn't work, fall back to the interpolation orientation
1233                .or_else(|| interpolated.map(|i| i.ori.look_dir()))
1234                .unwrap_or_default();
1235        let is_viewpoint = data.scene_data.viewpoint_entity == entity;
1236        let viewpoint_camera_mode = if is_viewpoint {
1237            data.camera_mode
1238        } else {
1239            CameraMode::default()
1240        };
1241        let viewpoint_character_state = if is_viewpoint { character } else { None };
1242
1243        let (pos, ori) = interpolated
1244            .map(|i| ((i.pos,), anim::vek::Quaternion::<f32>::from(i.ori)))
1245            .unwrap_or(((pos.0,), anim::vek::Quaternion::<f32>::default()));
1246        let wall_dir = physics.on_wall;
1247
1248        // Check whether we could have been shadowing last frame.
1249        let mut state = self.states.get_mut(body, &entity);
1250        let can_shadow_prev = state
1251            .as_mut()
1252            .map(|state| state.can_shadow_sun())
1253            .unwrap_or(false);
1254
1255        // Don't process figures outside the vd
1256        let vd_frac = anim::vek::Vec2::from(pos.0 - data.player_pos)
1257            .map2(TerrainChunk::RECT_SIZE, |d: f32, sz| d.abs() / sz as f32)
1258            .magnitude()
1259            / data.view_distance as f32;
1260
1261        // Keep from re-adding/removing entities on the border of the vd
1262        if vd_frac > 1.2 {
1263            self.states.remove(body, &entity);
1264            return;
1265        } else if vd_frac > 1.0 {
1266            state.as_mut().map(|state| state.visible = false);
1267            // Keep processing if this might be a shadow caster.
1268            // NOTE: Not worth to do for rain_occlusion, since that only happens in closeby
1269            // chunks.
1270            if !can_shadow_prev {
1271                return;
1272            }
1273        }
1274
1275        // Don't display figures outside the frustum spectrum (this is important to do
1276        // for any figure that potentially casts a shadow, since we use this
1277        // to estimate bounds for shadow maps).  Currently, we don't do this before the
1278        // update cull, so it's possible that faraway figures will not
1279        // shadow correctly until their next update.  For now, we treat this
1280        // as an acceptable tradeoff.
1281        let radius = scale.unwrap_or(&Scale(1.0)).0 * 2.0;
1282        let (in_frustum, _lpindex) = if let Some(ref mut meta) = state {
1283            let (in_frustum, lpindex) = BoundingSphere::new(pos.0.into_array(), radius)
1284                .coherent_test_against_frustum(data.frustum, meta.lpindex);
1285            let in_frustum = in_frustum
1286                || matches!(body, Body::Ship(_))
1287                || pos.0.distance_squared(data.focus_pos) < 32.0f32.powi(2);
1288            meta.visible = in_frustum;
1289            meta.lpindex = lpindex;
1290            if in_frustum {
1291                /* // Update visible bounds.
1292                visible_aabb.expand_to_contain(Aabb {
1293                    min: pos.0 - radius,
1294                    max: pos.0 + radius,
1295                }); */
1296            } else {
1297                // Check whether we can shadow.
1298                meta.can_shadow_sun = (data.can_shadow_sun)(pos, radius);
1299                meta.can_occlude_rain = (data.can_occlude_rain)(pos, radius);
1300            }
1301            (in_frustum, lpindex)
1302        } else {
1303            (true, 0)
1304        };
1305
1306        if !in_frustum {
1307            return;
1308        }
1309
1310        // Change in health as color!
1311        let col = health
1312                .map(|h| {
1313                    let time = data.scene_data.state.ecs().read_resource::<Time>();
1314                    let time_since_health_change = time.0 - h.last_change.time.0;
1315                    Rgba::broadcast(1.0)
1316                        + Rgba::new(10.0, 10.0, 10.0, 0.0).map(|c| {
1317                            (c / (1.0 + DAMAGE_FADE_COEFFICIENT * time_since_health_change)) as f32
1318                        })
1319                })
1320                .unwrap_or_else(|| Rgba::broadcast(1.0))
1321            // Highlight targeted collectible entities
1322            * if item.is_some() && data.scene_data.target_entities.contains(&entity) {
1323                Rgba::new(1.5, 1.5, 1.5, 1.0)
1324            } else {
1325                Rgba::one()
1326            };
1327
1328        let scale = scale.map(|s| s.0).unwrap_or(1.0);
1329
1330        let mut state_animation_rate = 1.0;
1331
1332        let tool_info = |equip_slot| {
1333            inventory
1334                .and_then(|i| i.equipped(equip_slot))
1335                .map(|i| {
1336                    if let ItemKind::Tool(tool) = &*i.kind() {
1337                        (Some(tool.kind), Some(tool.hands), i.ability_spec())
1338                    } else {
1339                        (None, None, None)
1340                    }
1341                })
1342                .unwrap_or((None, None, None))
1343        };
1344
1345        let (active_tool_kind, active_tool_hand, active_tool_spec) =
1346            tool_info(EquipSlot::ActiveMainhand);
1347        let active_tool_spec = active_tool_spec.as_deref();
1348        let (second_tool_kind, second_tool_hand, second_tool_spec) =
1349            tool_info(EquipSlot::ActiveOffhand);
1350        let second_tool_spec = second_tool_spec.as_deref();
1351        let hands = (active_tool_hand, second_tool_hand);
1352
1353        let ability_id = character.and_then(|c| {
1354            c.ability_info()
1355                .and_then(|a| a.ability)
1356                .and_then(|a| a.ability_id(Some(c), inventory))
1357        });
1358
1359        let move_dir = {
1360            let ori = ori * *Dir::default();
1361            let theta = vel.0.y.atan2(vel.0.x) - ori.y.atan2(ori.x);
1362            anim::vek::Vec2::unit_y().rotated_z(theta)
1363        };
1364
1365        // If a mount exists, get its animated mounting transform and its position
1366        let mount_transform_pos = (|| -> Option<_> {
1367            if let Some(is_rider) = is_rider {
1368                let mount = is_rider.mount;
1369                let mount = read_data.id_maps.uid_entity(mount)?;
1370                if let Some(mount_transform) = self.mount_transform(data.scene_data, mount) {
1371                    let body = *read_data.bodies.get(mount)?;
1372                    let meta = self.states.get_mut(&body, &mount)?;
1373                    Some((mount_transform, meta.mount_world_pos))
1374                } else {
1375                    None
1376                }
1377            } else if let Some(is_volume_rider) = is_volume_rider
1378                && matches!(is_volume_rider.pos.kind, Volume::Entity(_))
1379            {
1380                let (mat, _) = is_volume_rider.pos.get_mount_mat(
1381                    &read_data.terrain_grid,
1382                    &read_data.id_maps,
1383                    |e| read_data.interpolated.get(e).map(|i| (Pos(i.pos), i.ori)),
1384                    &read_data.colliders,
1385                )?;
1386                Some((anim::vek::Transform::default(), mat.mul_point(Vec3::zero())))
1387            } else {
1388                None
1389            }
1390        })();
1391
1392        let body = *body;
1393
1394        // Only use trail manager when trails are enabled
1395        let trail_mgr = data
1396            .scene_data
1397            .weapon_trails_enabled
1398            .then_some(&mut *data.trail_mgr);
1399
1400        let common_params = FigureUpdateCommonParameters {
1401            entity: Some(entity),
1402            pos: pos.0,
1403            ori,
1404            scale,
1405            mount_transform_pos,
1406            body: Some(body),
1407            col,
1408            dt,
1409            is_player: is_viewpoint,
1410            terrain: data.terrain,
1411            ground_vel: physics.ground_vel,
1412            primary_trail_points: self.trail_points(data.scene_data, entity, true),
1413            secondary_trail_points: self.trail_points(data.scene_data, entity, false),
1414        };
1415
1416        match body {
1417            Body::Humanoid(body) => {
1418                let (model, skeleton_attr) = self.character_model_cache.get_or_create_model(
1419                    renderer,
1420                    &mut self.atlas,
1421                    body,
1422                    inventory,
1423                    (),
1424                    tick,
1425                    viewpoint_camera_mode,
1426                    viewpoint_character_state,
1427                    slow_jobs,
1428                    None,
1429                );
1430
1431                let holding_lantern = inventory
1432                    .is_some_and(|i| i.equipped(EquipSlot::Lantern).is_some())
1433                    && light_emitter.is_some()
1434                    && ((second_tool_hand.is_none()
1435                        && matches!(active_tool_hand, Some(Hands::One)))
1436                        || !character.is_some_and(|c| c.is_wield()))
1437                    && !character.is_some_and(|c| c.is_using_hands())
1438                    && physics.in_liquid().is_none()
1439                    && is_volume_rider.is_none_or(|volume_rider| {
1440                        !matches!(volume_rider.block.get_sprite(), Some(SpriteKind::Helm))
1441                    });
1442
1443                let back_carry_offset = inventory
1444                    .and_then(|i| i.equipped(EquipSlot::Armor(ArmorSlot::Back)))
1445                    .and_then(|i| {
1446                        if let ItemKind::Armor(armor) = i.kind().as_ref() {
1447                            match &armor.kind {
1448                                ArmorKind::Backpack => Some(4.0),
1449                                ArmorKind::Back => Some(1.5),
1450                                _ => None,
1451                            }
1452                        } else {
1453                            None
1454                        }
1455                    })
1456                    .unwrap_or(0.0);
1457
1458                let state = self
1459                    .states
1460                    .character_states
1461                    .entry(entity)
1462                    .or_insert_with(|| {
1463                        FigureState::new(
1464                            renderer,
1465                            CharacterSkeleton::new(holding_lantern, back_carry_offset),
1466                            body,
1467                        )
1468                    });
1469
1470                // Average velocity relative to the current ground
1471                let rel_avg_vel = (state.avg_vel - physics.ground_vel) / scale;
1472
1473                let orientation = ori * anim::vek::Vec3::<f32>::unit_y();
1474                let last_ori = state.last_ori * anim::vek::Vec3::<f32>::unit_y();
1475
1476                let (character, last_character) = match (character, last_character) {
1477                    (Some(c), Some(l)) => (c, l),
1478                    _ => return,
1479                };
1480
1481                if !character.same_variant(&last_character.0) {
1482                    state.state_time = 0.0;
1483                }
1484
1485                let is_riding = is_rider.is_some() || is_volume_rider.is_some();
1486
1487                let target_base = match (
1488                    physics.on_ground.is_some(),
1489                    rel_vel.magnitude_squared() > 0.1, // Moving
1490                    physics.in_liquid().is_some(),     // In water
1491                    is_riding,
1492                    physics.skating_active,
1493                ) {
1494                    // Standing or Skating
1495                    (true, false, false, false, _) | (_, _, false, false, true) => {
1496                        anim::character::StandAnimation::update_skeleton(
1497                            &CharacterSkeleton::new(holding_lantern, back_carry_offset),
1498                            (
1499                                active_tool_kind,
1500                                second_tool_kind,
1501                                hands,
1502                                // TODO: Update to use the quaternion.
1503                                ori * anim::vek::Vec3::<f32>::unit_y(),
1504                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
1505                                look_dir,
1506                                time,
1507                                rel_avg_vel,
1508                            ),
1509                            state.state_time,
1510                            &mut state_animation_rate,
1511                            skeleton_attr,
1512                        )
1513                    },
1514                    // Running
1515                    (true, true, false, false, _) => {
1516                        anim::character::RunAnimation::update_skeleton(
1517                            &CharacterSkeleton::new(holding_lantern, back_carry_offset),
1518                            (
1519                                active_tool_kind,
1520                                second_tool_kind,
1521                                hands,
1522                                rel_vel,
1523                                // TODO: Update to use the quaternion.
1524                                ori * anim::vek::Vec3::<f32>::unit_y(),
1525                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
1526                                *look_dir,
1527                                time,
1528                                rel_avg_vel,
1529                                state.acc_vel,
1530                                wall_dir,
1531                            ),
1532                            state.state_time,
1533                            &mut state_animation_rate,
1534                            skeleton_attr,
1535                        )
1536                    },
1537                    // In air
1538                    (false, _, false, false, _) => {
1539                        anim::character::JumpAnimation::update_skeleton(
1540                            &CharacterSkeleton::new(holding_lantern, back_carry_offset),
1541                            (
1542                                active_tool_kind,
1543                                second_tool_kind,
1544                                hands,
1545                                rel_vel,
1546                                // TODO: Update to use the quaternion.
1547                                ori * anim::vek::Vec3::<f32>::unit_y(),
1548                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
1549                                look_dir,
1550                                time,
1551                            ),
1552                            state.state_time,
1553                            &mut state_animation_rate,
1554                            skeleton_attr,
1555                        )
1556                    },
1557                    // Swim
1558                    (_, _, true, false, _) => anim::character::SwimAnimation::update_skeleton(
1559                        &CharacterSkeleton::new(holding_lantern, back_carry_offset),
1560                        (
1561                            active_tool_kind,
1562                            second_tool_kind,
1563                            hands,
1564                            rel_vel,
1565                            // TODO: Update to use the quaternion.
1566                            ori * anim::vek::Vec3::<f32>::unit_y(),
1567                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
1568                            time,
1569                            rel_avg_vel,
1570                        ),
1571                        state.state_time,
1572                        &mut state_animation_rate,
1573                        skeleton_attr,
1574                    ),
1575                    // Mount
1576                    (_, _, _, true, _) => {
1577                        let base = anim::character::MountAnimation::update_skeleton(
1578                            &CharacterSkeleton::new(holding_lantern, back_carry_offset),
1579                            (
1580                                active_tool_kind,
1581                                second_tool_kind,
1582                                hands,
1583                                time,
1584                                rel_vel,
1585                                rel_avg_vel,
1586                                // TODO: Update to use the quaternion.
1587                                ori * anim::vek::Vec3::<f32>::unit_y(),
1588                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
1589                                *look_dir,
1590                            ),
1591                            state.state_time,
1592                            &mut state_animation_rate,
1593                            skeleton_attr,
1594                        );
1595                        if let Some(is_volume_rider) = is_volume_rider
1596                            && let Some(sprite) = is_volume_rider.block.get_sprite()
1597                        {
1598                            match sprite {
1599                                _ if sprite.is_controller() => {
1600                                    anim::character::SteerAnimation::update_skeleton(
1601                                        &base,
1602                                        (
1603                                            active_tool_kind,
1604                                            second_tool_kind,
1605                                            character_activity.map(|a| a.steer_dir).unwrap_or(0.0),
1606                                            time,
1607                                        ),
1608                                        state.state_time,
1609                                        &mut state_animation_rate,
1610                                        skeleton_attr,
1611                                    )
1612                                },
1613                                _ if sprite.is_bed() => {
1614                                    anim::character::SleepAnimation::update_skeleton(
1615                                        &base,
1616                                        (active_tool_kind, second_tool_kind, time),
1617                                        state.state_time,
1618                                        &mut state_animation_rate,
1619                                        skeleton_attr,
1620                                    )
1621                                },
1622                                _ => anim::character::SitAnimation::update_skeleton(
1623                                    &base,
1624                                    (
1625                                        active_tool_kind,
1626                                        second_tool_kind,
1627                                        state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
1628                                        look_dir,
1629                                        time,
1630                                    ),
1631                                    state.state_time,
1632                                    &mut state_animation_rate,
1633                                    skeleton_attr,
1634                                ),
1635                            }
1636                        } else {
1637                            base
1638                        }
1639                    },
1640                };
1641                let target_bones = match &character {
1642                    CharacterState::Roll(s) => {
1643                        let stage_time = s.timer.as_secs_f32();
1644                        let wield_status = s.was_wielded;
1645                        let stage_progress = match s.stage_section {
1646                            StageSection::Buildup => {
1647                                stage_time / s.static_data.buildup_duration.as_secs_f32()
1648                            },
1649                            StageSection::Movement => {
1650                                stage_time / s.static_data.movement_duration.as_secs_f32()
1651                            },
1652                            StageSection::Recover => {
1653                                stage_time / s.static_data.recover_duration.as_secs_f32()
1654                            },
1655                            _ => 0.0,
1656                        };
1657                        anim::character::RollAnimation::update_skeleton(
1658                            &target_base,
1659                            (
1660                                active_tool_kind,
1661                                second_tool_kind,
1662                                hands,
1663                                wield_status,
1664                                // TODO: Update to use the quaternion.
1665                                ori * anim::vek::Vec3::<f32>::unit_y(),
1666                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
1667                                time,
1668                                Some(s.stage_section),
1669                                s.prev_aimed_dir,
1670                            ),
1671                            stage_progress,
1672                            &mut state_animation_rate,
1673                            skeleton_attr,
1674                        )
1675                    },
1676                    CharacterState::Throw(s) => {
1677                        let timer = character.timer();
1678                        let stage_section = character.stage_section();
1679                        let durations = character.durations();
1680                        let progress = if let Some(((timer, stage_section), durations)) =
1681                            timer.zip(stage_section).zip(durations)
1682                        {
1683                            let base_dur = match stage_section {
1684                                StageSection::Buildup => durations.buildup,
1685                                StageSection::Charge => durations.charge,
1686                                StageSection::Movement => None,
1687                                StageSection::Action => durations.action,
1688                                StageSection::Recover => durations.recover,
1689                            };
1690                            if let Some(base_dur) = base_dur {
1691                                timer.as_secs_f32() / base_dur.as_secs_f32()
1692                            } else {
1693                                timer.as_secs_f32()
1694                            }
1695                        } else {
1696                            0.0
1697                        };
1698
1699                        anim::character::ThrowAnimation::update_skeleton(
1700                            &target_base,
1701                            (
1702                                stage_section,
1703                                s.static_data.tool_kind,
1704                                s.static_data.hand_info,
1705                            ),
1706                            progress,
1707                            &mut state_animation_rate,
1708                            skeleton_attr,
1709                        )
1710                    },
1711                    CharacterState::BasicMelee(_)
1712                    | CharacterState::FinisherMelee(_)
1713                    | CharacterState::DiveMelee(_)
1714                    | CharacterState::SelfBuff(_)
1715                    | CharacterState::ChargedRanged(_)
1716                    | CharacterState::BasicRanged(_)
1717                    | CharacterState::ChargedMelee(_)
1718                    | CharacterState::DashMelee(_)
1719                    | CharacterState::Shockwave(_)
1720                    | CharacterState::BasicAura(_)
1721                    | CharacterState::StaticAura(_)
1722                    | CharacterState::BasicBeam(_)
1723                    | CharacterState::BasicBlock(_)
1724                    | CharacterState::RiposteMelee(_) => {
1725                        let timer = character.timer();
1726                        let stage_section = character.stage_section();
1727                        let durations = character.durations();
1728                        let progress = if let Some(((timer, stage_section), durations)) =
1729                            timer.zip(stage_section).zip(durations)
1730                        {
1731                            let base_dur = match stage_section {
1732                                StageSection::Buildup => durations.buildup,
1733                                StageSection::Charge => {
1734                                    if matches!(character, CharacterState::DashMelee(_)) {
1735                                        None
1736                                    } else {
1737                                        durations.charge
1738                                    }
1739                                },
1740                                StageSection::Movement => {
1741                                    if matches!(character, CharacterState::DiveMelee(_)) {
1742                                        None
1743                                    } else {
1744                                        durations.movement
1745                                    }
1746                                },
1747                                StageSection::Action => {
1748                                    if matches!(
1749                                        character,
1750                                        CharacterState::BasicBeam(_)
1751                                            | CharacterState::BasicBlock(_)
1752                                    ) {
1753                                        None
1754                                    } else {
1755                                        durations.action
1756                                    }
1757                                },
1758                                StageSection::Recover => durations.recover,
1759                            };
1760                            if let Some(base_dur) = base_dur {
1761                                timer.as_secs_f32() / base_dur.as_secs_f32()
1762                            } else {
1763                                timer.as_secs_f32()
1764                            }
1765                        } else {
1766                            0.0
1767                        };
1768
1769                        anim::character::BasicAction::update_skeleton(
1770                            &target_base,
1771                            anim::character::BasicActionDependency {
1772                                ability_id,
1773                                hands,
1774                                stage_section,
1775                                ability_info: character.ability_info(),
1776                                velocity: rel_vel,
1777                                last_ori,
1778                                orientation,
1779                                look_dir,
1780                                is_riding,
1781                            },
1782                            progress,
1783                            &mut state_animation_rate,
1784                            skeleton_attr,
1785                        )
1786                    },
1787                    CharacterState::ComboMelee2(_)
1788                    | CharacterState::RepeaterRanged(_)
1789                    | CharacterState::RapidMelee(_) => {
1790                        let timer = character.timer();
1791                        let stage_section = character.stage_section();
1792                        let durations = character.durations();
1793                        let progress = if let Some(((timer, stage_section), durations)) =
1794                            timer.zip(stage_section).zip(durations)
1795                        {
1796                            let base_dur = match stage_section {
1797                                StageSection::Buildup => durations.buildup,
1798                                StageSection::Charge => durations.charge,
1799                                StageSection::Movement => durations.movement,
1800                                StageSection::Action => durations.action,
1801                                StageSection::Recover => durations.recover,
1802                            };
1803                            if let Some(base_dur) = base_dur {
1804                                timer.as_secs_f32() / base_dur.as_secs_f32()
1805                            } else {
1806                                timer.as_secs_f32()
1807                            }
1808                        } else {
1809                            0.0
1810                        };
1811
1812                        let (current_action, max_actions) = match character {
1813                            CharacterState::ComboMelee2(s) => (
1814                                (s.completed_strikes % s.static_data.strikes.len()) as u32,
1815                                Some(s.static_data.strikes.len() as u32),
1816                            ),
1817                            CharacterState::RepeaterRanged(s) => (s.projectiles_fired, None),
1818                            CharacterState::RapidMelee(s) => {
1819                                (s.current_strike, s.static_data.max_strikes)
1820                            },
1821                            _ => (0, None),
1822                        };
1823
1824                        anim::character::MultiAction::update_skeleton(
1825                            &target_base,
1826                            anim::character::MultiActionDependency {
1827                                ability_id,
1828                                stage_section,
1829                                ability_info: character.ability_info(),
1830                                current_action,
1831                                max_actions,
1832                                move_dir,
1833                                orientation,
1834                                look_dir,
1835                                velocity: rel_vel,
1836                                is_riding,
1837                            },
1838                            progress,
1839                            &mut state_animation_rate,
1840                            skeleton_attr,
1841                        )
1842                    },
1843                    CharacterState::Idle(idle::Data {
1844                        is_sneaking: true, ..
1845                    }) => {
1846                        anim::character::SneakAnimation::update_skeleton(
1847                            &target_base,
1848                            (
1849                                active_tool_kind,
1850                                rel_vel,
1851                                // TODO: Update to use the quaternion.
1852                                ori * anim::vek::Vec3::<f32>::unit_y(),
1853                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
1854                                *look_dir,
1855                                time,
1856                            ),
1857                            state.state_time,
1858                            &mut state_animation_rate,
1859                            skeleton_attr,
1860                        )
1861                    },
1862                    CharacterState::Interact(s) => {
1863                        let stage_time = s.timer.as_secs_f32();
1864                        let interact_pos = match s.static_data.interact {
1865                            interact::InteractKind::Invalid => pos.0,
1866                            interact::InteractKind::Entity { target, .. } => read_data
1867                                .id_maps
1868                                .uid_entity(target)
1869                                .and_then(|target| read_data.positions.get(target))
1870                                .map(|pos| pos.0)
1871                                .unwrap_or(pos.0),
1872                            interact::InteractKind::Sprite { pos, .. } => pos.as_() + 0.5,
1873                        };
1874                        let stage_progress = match s.stage_section {
1875                            StageSection::Buildup => {
1876                                stage_time / s.static_data.buildup_duration.as_secs_f32()
1877                            },
1878                            StageSection::Action => s.timer.as_secs_f32(),
1879                            StageSection::Recover => {
1880                                stage_time / s.static_data.recover_duration.as_secs_f32()
1881                            },
1882                            _ => 0.0,
1883                        };
1884                        match s.static_data.interact {
1885                            interact::InteractKind::Entity {
1886                                kind: InteractionKind::Pet,
1887                                ..
1888                            } => anim::character::PetAnimation::update_skeleton(
1889                                &target_base,
1890                                (pos.0, interact_pos, time),
1891                                state.state_time,
1892                                &mut state_animation_rate,
1893                                skeleton_attr,
1894                            ),
1895                            _ => anim::character::CollectAnimation::update_skeleton(
1896                                &target_base,
1897                                (pos.0, time, Some(s.stage_section), interact_pos, is_riding),
1898                                stage_progress,
1899                                &mut state_animation_rate,
1900                                skeleton_attr,
1901                            ),
1902                        }
1903                    },
1904                    CharacterState::Boost(_) => anim::character::BoostAnimation::update_skeleton(
1905                        &target_base,
1906                        (),
1907                        0.5,
1908                        &mut state_animation_rate,
1909                        skeleton_attr,
1910                    ),
1911                    CharacterState::Stunned(s) => {
1912                        let stage_time = s.timer.as_secs_f32();
1913                        let wield_status = s.was_wielded;
1914                        let stage_progress = match s.stage_section {
1915                            StageSection::Buildup => {
1916                                stage_time / s.static_data.buildup_duration.as_secs_f32()
1917                            },
1918                            StageSection::Recover => {
1919                                stage_time / s.static_data.recover_duration.as_secs_f32()
1920                            },
1921                            _ => 0.0,
1922                        };
1923                        match s.static_data.poise_state {
1924                            PoiseState::Normal | PoiseState::Stunned | PoiseState::Interrupted => {
1925                                anim::character::StunnedAnimation::update_skeleton(
1926                                    &target_base,
1927                                    (
1928                                        active_tool_kind,
1929                                        second_tool_kind,
1930                                        hands,
1931                                        rel_vel.magnitude(),
1932                                        time,
1933                                        Some(s.stage_section),
1934                                        state.state_time,
1935                                        wield_status,
1936                                    ),
1937                                    stage_progress,
1938                                    &mut state_animation_rate,
1939                                    skeleton_attr,
1940                                )
1941                            },
1942                            PoiseState::Dazed | PoiseState::KnockedDown => {
1943                                anim::character::StaggeredAnimation::update_skeleton(
1944                                    &target_base,
1945                                    (
1946                                        active_tool_kind,
1947                                        second_tool_kind,
1948                                        hands,
1949                                        rel_vel.magnitude(),
1950                                        time,
1951                                        Some(s.stage_section),
1952                                        state.state_time,
1953                                        wield_status,
1954                                    ),
1955                                    stage_progress,
1956                                    &mut state_animation_rate,
1957                                    skeleton_attr,
1958                                )
1959                            },
1960                        }
1961                    },
1962                    CharacterState::UseItem(s) => {
1963                        let stage_time = s.timer.as_secs_f32();
1964                        let item_kind = s.static_data.item_kind;
1965                        let stage_progress = match s.stage_section {
1966                            StageSection::Buildup => {
1967                                stage_time / s.static_data.buildup_duration.as_secs_f32()
1968                            },
1969                            StageSection::Action => stage_time,
1970                            StageSection::Recover => {
1971                                stage_time / s.static_data.recover_duration.as_secs_f32()
1972                            },
1973                            _ => 0.0,
1974                        };
1975                        anim::character::ConsumeAnimation::update_skeleton(
1976                            &target_base,
1977                            (time, Some(s.stage_section), Some(item_kind)),
1978                            stage_progress,
1979                            &mut state_animation_rate,
1980                            skeleton_attr,
1981                        )
1982                    },
1983                    CharacterState::Equipping(equipping::Data { is_sneaking, .. }) => {
1984                        if *is_sneaking {
1985                            anim::character::SneakEquipAnimation::update_skeleton(
1986                                &target_base,
1987                                (
1988                                    active_tool_kind,
1989                                    rel_vel,
1990                                    // TODO: Update to use the quaternion.
1991                                    ori * anim::vek::Vec3::<f32>::unit_y(),
1992                                    state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
1993                                    time,
1994                                ),
1995                                state.state_time,
1996                                &mut state_animation_rate,
1997                                skeleton_attr,
1998                            )
1999                        } else {
2000                            anim::character::EquipAnimation::update_skeleton(
2001                                &target_base,
2002                                (
2003                                    active_tool_kind,
2004                                    second_tool_kind,
2005                                    rel_vel.magnitude(),
2006                                    time,
2007                                ),
2008                                state.state_time,
2009                                &mut state_animation_rate,
2010                                skeleton_attr,
2011                            )
2012                        }
2013                    },
2014                    CharacterState::Talk(_) => anim::character::TalkAnimation::update_skeleton(
2015                        &target_base,
2016                        (
2017                            active_tool_kind,
2018                            second_tool_kind,
2019                            rel_vel.magnitude(),
2020                            time,
2021                            look_dir,
2022                        ),
2023                        state.state_time,
2024                        &mut state_animation_rate,
2025                        skeleton_attr,
2026                    ),
2027                    CharacterState::Wielding(wielding::Data { is_sneaking, .. }) => {
2028                        if physics.in_liquid().is_some() {
2029                            anim::character::SwimWieldAnimation::update_skeleton(
2030                                &target_base,
2031                                (
2032                                    active_tool_kind,
2033                                    second_tool_kind,
2034                                    hands,
2035                                    rel_vel.magnitude(),
2036                                    time,
2037                                ),
2038                                state.state_time,
2039                                &mut state_animation_rate,
2040                                skeleton_attr,
2041                            )
2042                        } else if *is_sneaking {
2043                            anim::character::SneakWieldAnimation::update_skeleton(
2044                                &target_base,
2045                                (
2046                                    (active_tool_kind, active_tool_spec),
2047                                    second_tool_kind,
2048                                    hands,
2049                                    rel_vel,
2050                                    // TODO: Update to use the quaternion.
2051                                    ori * anim::vek::Vec3::<f32>::unit_y(),
2052                                    state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2053                                    *look_dir,
2054                                    time,
2055                                ),
2056                                state.state_time,
2057                                &mut state_animation_rate,
2058                                skeleton_attr,
2059                            )
2060                        } else {
2061                            anim::character::WieldAnimation::update_skeleton(
2062                                &target_base,
2063                                (
2064                                    (active_tool_kind, active_tool_spec),
2065                                    second_tool_kind,
2066                                    hands,
2067                                    // TODO: Update to use the quaternion.
2068                                    ori * anim::vek::Vec3::<f32>::unit_y(),
2069                                    state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2070                                    look_dir,
2071                                    rel_vel,
2072                                    is_riding,
2073                                    time,
2074                                ),
2075                                state.state_time,
2076                                &mut state_animation_rate,
2077                                skeleton_attr,
2078                            )
2079                        }
2080                    },
2081                    CharacterState::Glide(data) => {
2082                        anim::character::GlidingAnimation::update_skeleton(
2083                            &target_base,
2084                            (rel_vel, ori, data.ori.into(), time, state.acc_vel),
2085                            state.state_time,
2086                            &mut state_animation_rate,
2087                            skeleton_attr,
2088                        )
2089                    },
2090                    CharacterState::Climb { .. } => {
2091                        anim::character::ClimbAnimation::update_skeleton(
2092                            &target_base,
2093                            (
2094                                active_tool_kind,
2095                                second_tool_kind,
2096                                rel_vel,
2097                                // TODO: Update to use the quaternion.
2098                                ori * anim::vek::Vec3::<f32>::unit_y(),
2099                                time,
2100                            ),
2101                            state.state_time,
2102                            &mut state_animation_rate,
2103                            skeleton_attr,
2104                        )
2105                    },
2106                    CharacterState::Sit => anim::character::SitAnimation::update_skeleton(
2107                        &target_base,
2108                        (
2109                            active_tool_kind,
2110                            second_tool_kind,
2111                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2112                            look_dir,
2113                            time,
2114                        ),
2115                        state.state_time,
2116                        &mut state_animation_rate,
2117                        skeleton_attr,
2118                    ),
2119                    CharacterState::Crawl => {
2120                        anim::character::CrawlAnimation::update_skeleton(
2121                            &target_base,
2122                            (
2123                                rel_vel,
2124                                // TODO: Update to use the quaternion.
2125                                ori * anim::vek::Vec3::<f32>::unit_y(),
2126                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2127                                time,
2128                            ),
2129                            state.state_time,
2130                            &mut state_animation_rate,
2131                            skeleton_attr,
2132                        )
2133                    },
2134                    CharacterState::GlideWield(data) => {
2135                        anim::character::GlideWieldAnimation::update_skeleton(
2136                            &target_base,
2137                            (ori, data.ori.into()),
2138                            state.state_time,
2139                            &mut state_animation_rate,
2140                            skeleton_attr,
2141                        )
2142                    },
2143                    CharacterState::Wallrun(data) => {
2144                        anim::character::WallrunAnimation::update_skeleton(
2145                            &target_base,
2146                            (
2147                                (active_tool_kind, active_tool_spec),
2148                                second_tool_kind,
2149                                hands,
2150                                ori * anim::vek::Vec3::<f32>::unit_y(),
2151                                state.acc_vel,
2152                                wall_dir,
2153                                data.was_wielded,
2154                            ),
2155                            state.state_time,
2156                            &mut state_animation_rate,
2157                            skeleton_attr,
2158                        )
2159                    },
2160                    CharacterState::Dance => anim::character::DanceAnimation::update_skeleton(
2161                        &target_base,
2162                        (active_tool_kind, second_tool_kind, time),
2163                        state.state_time,
2164                        &mut state_animation_rate,
2165                        skeleton_attr,
2166                    ),
2167                    CharacterState::Music(s) => anim::character::MusicAnimation::update_skeleton(
2168                        &target_base,
2169                        (
2170                            hands,
2171                            (Some(s.static_data.ability_info), time),
2172                            rel_vel,
2173                            ability_id,
2174                        ),
2175                        state.state_time,
2176                        &mut state_animation_rate,
2177                        skeleton_attr,
2178                    ),
2179                    _ => target_base,
2180                };
2181
2182                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, data.dt_lerp);
2183                state.update(
2184                    renderer,
2185                    trail_mgr,
2186                    update_buf,
2187                    &common_params,
2188                    state_animation_rate,
2189                    model,
2190                    body,
2191                );
2192            },
2193            Body::QuadrupedSmall(body) => {
2194                let (model, skeleton_attr) = self.quadruped_small_model_cache.get_or_create_model(
2195                    renderer,
2196                    &mut self.atlas,
2197                    body,
2198                    inventory,
2199                    (),
2200                    data.tick,
2201                    viewpoint_camera_mode,
2202                    viewpoint_character_state,
2203                    slow_jobs,
2204                    None,
2205                );
2206
2207                let state = self
2208                    .states
2209                    .quadruped_small_states
2210                    .entry(entity)
2211                    .or_insert_with(|| {
2212                        FigureState::new(renderer, QuadrupedSmallSkeleton::default(), body)
2213                    });
2214
2215                // Average velocity relative to the current ground
2216                let rel_avg_vel = state.avg_vel - physics.ground_vel;
2217
2218                let (character, last_character) = match (character, last_character) {
2219                    (Some(c), Some(l)) => (c, l),
2220                    _ => return,
2221                };
2222
2223                if !character.same_variant(&last_character.0) {
2224                    state.state_time = 0.0;
2225                }
2226
2227                let target_base = match (
2228                    physics.on_ground.is_some(),
2229                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
2230                    physics.in_liquid().is_some(),                      // In water
2231                ) {
2232                    // Standing
2233                    (true, false, false) => anim::quadruped_small::IdleAnimation::update_skeleton(
2234                        &QuadrupedSmallSkeleton::default(),
2235                        time,
2236                        state.state_time,
2237                        &mut state_animation_rate,
2238                        skeleton_attr,
2239                    ),
2240                    // Running
2241                    (true, true, false) => {
2242                        anim::quadruped_small::RunAnimation::update_skeleton(
2243                            &QuadrupedSmallSkeleton::default(),
2244                            (
2245                                rel_vel.magnitude(),
2246                                // TODO: Update to use the quaternion.
2247                                ori * anim::vek::Vec3::<f32>::unit_y(),
2248                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2249                                time,
2250                                rel_avg_vel,
2251                                state.acc_vel,
2252                            ),
2253                            state.state_time,
2254                            &mut state_animation_rate,
2255                            skeleton_attr,
2256                        )
2257                    },
2258                    // Swimming
2259                    (_, _, true) => anim::quadruped_small::RunAnimation::update_skeleton(
2260                        &QuadrupedSmallSkeleton::default(),
2261                        (
2262                            rel_vel.magnitude(),
2263                            // TODO: Update to use the quaternion.
2264                            ori * anim::vek::Vec3::<f32>::unit_y(),
2265                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2266                            time,
2267                            rel_avg_vel,
2268                            state.acc_vel,
2269                        ),
2270                        state.state_time,
2271                        &mut state_animation_rate,
2272                        skeleton_attr,
2273                    ),
2274                    // In air
2275                    (false, _, false) => anim::quadruped_small::RunAnimation::update_skeleton(
2276                        &QuadrupedSmallSkeleton::default(),
2277                        (
2278                            rel_vel.magnitude(),
2279                            // TODO: Update to use the quaternion.
2280                            ori * anim::vek::Vec3::<f32>::unit_y(),
2281                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2282                            time,
2283                            rel_avg_vel,
2284                            state.acc_vel,
2285                        ),
2286                        state.state_time,
2287                        &mut state_animation_rate,
2288                        skeleton_attr,
2289                    ),
2290                };
2291                let target_bones = match &character {
2292                    CharacterState::BasicMelee(s) => {
2293                        let stage_time = s.timer.as_secs_f32();
2294
2295                        let stage_progress = match s.stage_section {
2296                            StageSection::Buildup => {
2297                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2298                            },
2299                            StageSection::Action => {
2300                                stage_time / s.static_data.swing_duration.as_secs_f32()
2301                            },
2302                            StageSection::Recover => {
2303                                stage_time / s.static_data.recover_duration.as_secs_f32()
2304                            },
2305
2306                            _ => 0.0,
2307                        };
2308                        anim::quadruped_small::AlphaAnimation::update_skeleton(
2309                            &target_base,
2310                            (time, s.stage_section, state.state_time),
2311                            stage_progress,
2312                            &mut state_animation_rate,
2313                            skeleton_attr,
2314                        )
2315                    },
2316                    CharacterState::ComboMelee2(s) => {
2317                        let timer = s.timer.as_secs_f32();
2318                        let current_strike = s.completed_strikes % s.static_data.strikes.len();
2319                        let strike_data = s.static_data.strikes[current_strike];
2320                        let progress = match s.stage_section {
2321                            StageSection::Buildup => {
2322                                timer / strike_data.buildup_duration.as_secs_f32()
2323                            },
2324                            StageSection::Action => {
2325                                timer / strike_data.swing_duration.as_secs_f32()
2326                            },
2327                            StageSection::Recover => {
2328                                timer / strike_data.recover_duration.as_secs_f32()
2329                            },
2330                            _ => 0.0,
2331                        };
2332
2333                        anim::quadruped_small::ComboAnimation::update_skeleton(
2334                            &target_base,
2335                            (
2336                                ability_id,
2337                                Some(s.stage_section),
2338                                Some(s.static_data.ability_info),
2339                                current_strike,
2340                                time,
2341                                state.state_time,
2342                            ),
2343                            progress,
2344                            &mut state_animation_rate,
2345                            skeleton_attr,
2346                        )
2347                    },
2348                    CharacterState::Stunned(s) => {
2349                        let stage_time = s.timer.as_secs_f32();
2350                        let stage_progress = match s.stage_section {
2351                            StageSection::Buildup => {
2352                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2353                            },
2354                            StageSection::Recover => {
2355                                stage_time / s.static_data.recover_duration.as_secs_f32()
2356                            },
2357                            _ => 0.0,
2358                        };
2359                        match s.static_data.poise_state {
2360                            PoiseState::Normal
2361                            | PoiseState::Interrupted
2362                            | PoiseState::Stunned
2363                            | PoiseState::Dazed
2364                            | PoiseState::KnockedDown => {
2365                                anim::quadruped_small::StunnedAnimation::update_skeleton(
2366                                    &target_base,
2367                                    (
2368                                        rel_vel.magnitude(),
2369                                        time,
2370                                        Some(s.stage_section),
2371                                        state.state_time,
2372                                    ),
2373                                    stage_progress,
2374                                    &mut state_animation_rate,
2375                                    skeleton_attr,
2376                                )
2377                            },
2378                        }
2379                    },
2380                    CharacterState::Sit => anim::quadruped_small::FeedAnimation::update_skeleton(
2381                        &target_base,
2382                        time,
2383                        state.state_time,
2384                        &mut state_animation_rate,
2385                        skeleton_attr,
2386                    ),
2387                    CharacterState::Shockwave(s) => {
2388                        let stage_time = s.timer.as_secs_f32();
2389                        let stage_progress = match s.stage_section {
2390                            StageSection::Buildup => {
2391                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2392                            },
2393                            StageSection::Charge => stage_time,
2394                            StageSection::Action => {
2395                                stage_time / s.static_data.swing_duration.as_secs_f32()
2396                            },
2397                            StageSection::Recover => {
2398                                stage_time / s.static_data.recover_duration.as_secs_f32()
2399                            },
2400                            _ => 0.0,
2401                        };
2402                        anim::quadruped_small::ShockwaveAnimation::update_skeleton(
2403                            &target_base,
2404                            (
2405                                rel_vel.magnitude(),
2406                                time,
2407                                Some(s.stage_section),
2408                                state.state_time,
2409                            ),
2410                            stage_progress,
2411                            &mut state_animation_rate,
2412                            skeleton_attr,
2413                        )
2414                    },
2415                    // TODO!
2416                    _ => target_base,
2417                };
2418
2419                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
2420                state.update(
2421                    renderer,
2422                    trail_mgr,
2423                    update_buf,
2424                    &common_params,
2425                    state_animation_rate,
2426                    model,
2427                    body,
2428                );
2429            },
2430            Body::QuadrupedMedium(body) => {
2431                let (model, skeleton_attr) = self.quadruped_medium_model_cache.get_or_create_model(
2432                    renderer,
2433                    &mut self.atlas,
2434                    body,
2435                    inventory,
2436                    (),
2437                    tick,
2438                    viewpoint_camera_mode,
2439                    viewpoint_character_state,
2440                    slow_jobs,
2441                    None,
2442                );
2443
2444                let state = self
2445                    .states
2446                    .quadruped_medium_states
2447                    .entry(entity)
2448                    .or_insert_with(|| {
2449                        FigureState::new(renderer, QuadrupedMediumSkeleton::default(), body)
2450                    });
2451
2452                // Average velocity relative to the current ground
2453                let rel_avg_vel = state.avg_vel - physics.ground_vel;
2454
2455                let (character, last_character) = match (character, last_character) {
2456                    (Some(c), Some(l)) => (c, l),
2457                    _ => return,
2458                };
2459
2460                if !character.same_variant(&last_character.0) {
2461                    state.state_time = 0.0;
2462                }
2463
2464                let target_base = match (
2465                    physics.on_ground.is_some(),
2466                    rel_vel.magnitude_squared() > 0.25, // Moving
2467                    physics.in_liquid().is_some(),      // In water
2468                ) {
2469                    // Standing
2470                    (true, false, false) => anim::quadruped_medium::IdleAnimation::update_skeleton(
2471                        &QuadrupedMediumSkeleton::default(),
2472                        time,
2473                        state.state_time,
2474                        &mut state_animation_rate,
2475                        skeleton_attr,
2476                    ),
2477                    // Running
2478                    (true, true, false) => {
2479                        anim::quadruped_medium::RunAnimation::update_skeleton(
2480                            &QuadrupedMediumSkeleton::default(),
2481                            (
2482                                rel_vel.magnitude(),
2483                                // TODO: Update to use the quaternion.
2484                                ori * anim::vek::Vec3::<f32>::unit_y(),
2485                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2486                                time,
2487                                rel_avg_vel,
2488                                state.acc_vel,
2489                            ),
2490                            state.state_time,
2491                            &mut state_animation_rate,
2492                            skeleton_attr,
2493                        )
2494                    },
2495                    //Swimming
2496                    (_, _, true) => anim::quadruped_medium::RunAnimation::update_skeleton(
2497                        &QuadrupedMediumSkeleton::default(),
2498                        (
2499                            rel_vel.magnitude(),
2500                            // TODO: Update to use the quaternion.
2501                            ori * anim::vek::Vec3::<f32>::unit_y(),
2502                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2503                            time,
2504                            rel_avg_vel,
2505                            state.acc_vel,
2506                        ),
2507                        state.state_time,
2508                        &mut state_animation_rate,
2509                        skeleton_attr,
2510                    ),
2511                    // In air
2512                    (false, _, false) => anim::quadruped_medium::JumpAnimation::update_skeleton(
2513                        &QuadrupedMediumSkeleton::default(),
2514                        (time, rel_vel, rel_avg_vel),
2515                        state.state_time,
2516                        &mut state_animation_rate,
2517                        skeleton_attr,
2518                    ),
2519                };
2520                let target_bones = match &character {
2521                    CharacterState::BasicMelee(s) => {
2522                        let stage_time = s.timer.as_secs_f32();
2523
2524                        let stage_progress = match s.stage_section {
2525                            StageSection::Buildup => {
2526                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2527                            },
2528                            StageSection::Action => {
2529                                stage_time / s.static_data.swing_duration.as_secs_f32()
2530                            },
2531                            StageSection::Recover => {
2532                                stage_time / s.static_data.recover_duration.as_secs_f32()
2533                            },
2534
2535                            _ => 0.0,
2536                        };
2537                        anim::quadruped_medium::HoofAnimation::update_skeleton(
2538                            &target_base,
2539                            (
2540                                rel_vel.magnitude(),
2541                                time,
2542                                Some(s.stage_section),
2543                                state.state_time,
2544                            ),
2545                            stage_progress,
2546                            &mut state_animation_rate,
2547                            skeleton_attr,
2548                        )
2549                    },
2550                    CharacterState::DashMelee(s) => {
2551                        let stage_time = s.timer.as_secs_f32();
2552                        let stage_progress = match s.stage_section {
2553                            StageSection::Buildup => {
2554                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2555                            },
2556                            StageSection::Charge => stage_time,
2557                            StageSection::Action => {
2558                                stage_time / s.static_data.swing_duration.as_secs_f32()
2559                            },
2560                            StageSection::Recover => {
2561                                stage_time / s.static_data.recover_duration.as_secs_f32()
2562                            },
2563                            _ => 0.0,
2564                        };
2565                        anim::quadruped_medium::DashAnimation::update_skeleton(
2566                            &target_base,
2567                            (
2568                                rel_vel.magnitude(),
2569                                time,
2570                                Some(s.stage_section),
2571                                state.state_time,
2572                            ),
2573                            stage_progress,
2574                            &mut state_animation_rate,
2575                            skeleton_attr,
2576                        )
2577                    },
2578                    CharacterState::Shockwave(s) => {
2579                        let stage_time = s.timer.as_secs_f32();
2580                        let stage_progress = match s.stage_section {
2581                            StageSection::Buildup => {
2582                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2583                            },
2584                            StageSection::Charge => stage_time,
2585                            StageSection::Action => {
2586                                stage_time / s.static_data.swing_duration.as_secs_f32()
2587                            },
2588                            StageSection::Recover => {
2589                                stage_time / s.static_data.recover_duration.as_secs_f32()
2590                            },
2591                            _ => 0.0,
2592                        };
2593                        anim::quadruped_medium::ShockwaveAnimation::update_skeleton(
2594                            &target_base,
2595                            (
2596                                ability_id,
2597                                rel_vel.magnitude(),
2598                                time,
2599                                Some(s.stage_section),
2600                                state.state_time,
2601                            ),
2602                            stage_progress,
2603                            &mut state_animation_rate,
2604                            skeleton_attr,
2605                        )
2606                    },
2607                    CharacterState::BasicBeam(s) => {
2608                        let stage_time = s.timer.as_secs_f32();
2609                        let stage_progress = match s.stage_section {
2610                            StageSection::Buildup => {
2611                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2612                            },
2613                            StageSection::Action => s.timer.as_secs_f32(),
2614                            StageSection::Recover => {
2615                                stage_time / s.static_data.recover_duration.as_secs_f32()
2616                            },
2617                            _ => 0.0,
2618                        };
2619                        anim::quadruped_medium::BeamAnimation::update_skeleton(
2620                            &target_base,
2621                            (ability_id, Some(s.stage_section)),
2622                            stage_progress,
2623                            &mut state_animation_rate,
2624                            skeleton_attr,
2625                        )
2626                    },
2627                    CharacterState::LeapMelee(s) => {
2628                        let stage_time = s.timer.as_secs_f32();
2629                        let stage_progress = match s.stage_section {
2630                            StageSection::Buildup => {
2631                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2632                            },
2633                            StageSection::Movement => {
2634                                stage_time / s.static_data.movement_duration.as_secs_f32()
2635                            },
2636                            StageSection::Action => {
2637                                stage_time / s.static_data.swing_duration.as_secs_f32()
2638                            },
2639                            StageSection::Recover => {
2640                                stage_time / s.static_data.recover_duration.as_secs_f32()
2641                            },
2642                            _ => 0.0,
2643                        };
2644                        anim::quadruped_medium::LeapMeleeAnimation::update_skeleton(
2645                            &target_base,
2646                            (
2647                                rel_vel.magnitude(),
2648                                time,
2649                                Some(s.stage_section),
2650                                state.state_time,
2651                            ),
2652                            stage_progress,
2653                            &mut state_animation_rate,
2654                            skeleton_attr,
2655                        )
2656                    },
2657                    CharacterState::ComboMelee2(s) => {
2658                        let timer = s.timer.as_secs_f32();
2659                        let current_strike = s.completed_strikes % s.static_data.strikes.len();
2660                        let strike_data = s.static_data.strikes[current_strike];
2661                        let progress = match s.stage_section {
2662                            StageSection::Buildup => {
2663                                timer / strike_data.buildup_duration.as_secs_f32()
2664                            },
2665                            StageSection::Action => {
2666                                timer / strike_data.swing_duration.as_secs_f32()
2667                            },
2668                            StageSection::Recover => {
2669                                timer / strike_data.recover_duration.as_secs_f32()
2670                            },
2671                            _ => 0.0,
2672                        };
2673
2674                        anim::quadruped_medium::ComboAnimation::update_skeleton(
2675                            &target_base,
2676                            (
2677                                ability_id,
2678                                s.stage_section,
2679                                current_strike,
2680                                rel_vel.magnitude(),
2681                                time,
2682                                state.state_time,
2683                            ),
2684                            progress,
2685                            &mut state_animation_rate,
2686                            skeleton_attr,
2687                        )
2688                    },
2689                    CharacterState::RapidMelee(s) => {
2690                        let stage_time = s.timer.as_secs_f32();
2691                        let stage_progress = match s.stage_section {
2692                            StageSection::Buildup => {
2693                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2694                            },
2695
2696                            StageSection::Action => {
2697                                stage_time / s.static_data.swing_duration.as_secs_f32()
2698                            },
2699                            StageSection::Recover => {
2700                                stage_time / s.static_data.recover_duration.as_secs_f32()
2701                            },
2702                            _ => 0.0,
2703                        };
2704
2705                        anim::quadruped_medium::RapidMeleeAnimation::update_skeleton(
2706                            &target_base,
2707                            (ability_id, Some(s.stage_section)),
2708                            stage_progress,
2709                            &mut state_animation_rate,
2710                            skeleton_attr,
2711                        )
2712                    },
2713                    CharacterState::Stunned(s) => {
2714                        let stage_time = s.timer.as_secs_f32();
2715                        let stage_progress = match s.stage_section {
2716                            StageSection::Buildup => {
2717                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2718                            },
2719                            StageSection::Recover => {
2720                                stage_time / s.static_data.recover_duration.as_secs_f32()
2721                            },
2722                            _ => 0.0,
2723                        };
2724                        match s.static_data.poise_state {
2725                            PoiseState::Normal | PoiseState::Stunned | PoiseState::Interrupted => {
2726                                anim::quadruped_medium::StunnedAnimation::update_skeleton(
2727                                    &target_base,
2728                                    (
2729                                        rel_vel.magnitude(),
2730                                        time,
2731                                        Some(s.stage_section),
2732                                        state.state_time,
2733                                    ),
2734                                    stage_progress,
2735                                    &mut state_animation_rate,
2736                                    skeleton_attr,
2737                                )
2738                            },
2739                            PoiseState::Dazed | PoiseState::KnockedDown => {
2740                                anim::quadruped_medium::StunnedAnimation::update_skeleton(
2741                                    &target_base,
2742                                    (
2743                                        rel_vel.magnitude(),
2744                                        time,
2745                                        Some(s.stage_section),
2746                                        state.state_time,
2747                                    ),
2748                                    stage_progress,
2749                                    &mut state_animation_rate,
2750                                    skeleton_attr,
2751                                )
2752                            },
2753                        }
2754                    },
2755                    CharacterState::Sit => anim::quadruped_medium::FeedAnimation::update_skeleton(
2756                        &target_base,
2757                        time,
2758                        state.state_time,
2759                        &mut state_animation_rate,
2760                        skeleton_attr,
2761                    ),
2762                    // TODO!
2763                    _ => target_base,
2764                };
2765
2766                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
2767                state.update(
2768                    renderer,
2769                    trail_mgr,
2770                    update_buf,
2771                    &common_params,
2772                    state_animation_rate,
2773                    model,
2774                    body,
2775                );
2776            },
2777            Body::QuadrupedLow(body) => {
2778                let (model, skeleton_attr) = self.quadruped_low_model_cache.get_or_create_model(
2779                    renderer,
2780                    &mut self.atlas,
2781                    body,
2782                    inventory,
2783                    (),
2784                    data.tick,
2785                    viewpoint_camera_mode,
2786                    viewpoint_character_state,
2787                    slow_jobs,
2788                    None,
2789                );
2790
2791                let state = self
2792                    .states
2793                    .quadruped_low_states
2794                    .entry(entity)
2795                    .or_insert_with(|| {
2796                        FigureState::new(renderer, QuadrupedLowSkeleton::default(), body)
2797                    });
2798
2799                // Average velocity relative to the current ground
2800                let rel_avg_vel = state.avg_vel - physics.ground_vel;
2801
2802                let (character, last_character) = match (character, last_character) {
2803                    (Some(c), Some(l)) => (c, l),
2804                    _ => return,
2805                };
2806
2807                if !character.same_variant(&last_character.0) {
2808                    state.state_time = 0.0;
2809                }
2810
2811                let heads = heads
2812                    .and_then(|heads| {
2813                        let res = heads.heads().try_into().ok();
2814
2815                        if res.is_none() {
2816                            tracing::error!(
2817                                "Server sent another amount of heads than 3 for a QuadrupedLow \
2818                                 body"
2819                            );
2820                        }
2821                        res
2822                    })
2823                    .unwrap_or([
2824                        HeadState::Attached,
2825                        HeadState::Attached,
2826                        HeadState::Attached,
2827                    ]);
2828
2829                let target_base = match (
2830                    physics.on_ground.is_some(),
2831                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
2832                    physics.in_liquid().is_some(),                      // In water
2833                ) {
2834                    // Standing
2835                    (true, false, false) => anim::quadruped_low::IdleAnimation::update_skeleton(
2836                        &QuadrupedLowSkeleton::default(),
2837                        (time, heads),
2838                        state.state_time,
2839                        &mut state_animation_rate,
2840                        skeleton_attr,
2841                    ),
2842                    // Running
2843                    (true, true, false) => anim::quadruped_low::RunAnimation::update_skeleton(
2844                        &QuadrupedLowSkeleton::default(),
2845                        (
2846                            rel_vel.magnitude(),
2847                            // TODO: Update to use the quaternion.
2848                            ori * anim::vek::Vec3::<f32>::unit_y(),
2849                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2850                            time,
2851                            rel_avg_vel,
2852                            state.acc_vel,
2853                            heads,
2854                        ),
2855                        state.state_time,
2856                        &mut state_animation_rate,
2857                        skeleton_attr,
2858                    ),
2859                    // Swimming
2860                    (_, _, true) => anim::quadruped_low::RunAnimation::update_skeleton(
2861                        &QuadrupedLowSkeleton::default(),
2862                        (
2863                            rel_vel.magnitude(),
2864                            // TODO: Update to use the quaternion.
2865                            ori * anim::vek::Vec3::<f32>::unit_y(),
2866                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2867                            time,
2868                            rel_avg_vel,
2869                            state.acc_vel,
2870                            heads,
2871                        ),
2872                        state.state_time,
2873                        &mut state_animation_rate,
2874                        skeleton_attr,
2875                    ),
2876                    // In air
2877                    (false, _, false) => anim::quadruped_low::RunAnimation::update_skeleton(
2878                        &QuadrupedLowSkeleton::default(),
2879                        (
2880                            rel_vel.magnitude(),
2881                            // TODO: Update to use the quaternion.
2882                            ori * anim::vek::Vec3::<f32>::unit_y(),
2883                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
2884                            time,
2885                            rel_avg_vel,
2886                            state.acc_vel,
2887                            heads,
2888                        ),
2889                        state.state_time,
2890                        &mut state_animation_rate,
2891                        skeleton_attr,
2892                    ),
2893                };
2894                let target_bones = match &character {
2895                    CharacterState::BasicRanged(s) => {
2896                        let stage_time = s.timer.as_secs_f32();
2897
2898                        let stage_progress = match s.stage_section {
2899                            StageSection::Buildup => {
2900                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2901                            },
2902                            StageSection::Recover => {
2903                                stage_time / s.static_data.recover_duration.as_secs_f32()
2904                            },
2905
2906                            _ => 0.0,
2907                        };
2908                        anim::quadruped_low::ShootAnimation::update_skeleton(
2909                            &target_base,
2910                            (ability_id, rel_vel.magnitude(), time, Some(s.stage_section)),
2911                            stage_progress,
2912                            &mut state_animation_rate,
2913                            skeleton_attr,
2914                        )
2915                    },
2916                    CharacterState::Shockwave(s) => {
2917                        let stage_time = s.timer.as_secs_f32();
2918                        let stage_progress = match s.stage_section {
2919                            StageSection::Buildup => {
2920                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2921                            },
2922                            StageSection::Charge => stage_time,
2923                            StageSection::Action => {
2924                                stage_time / s.static_data.swing_duration.as_secs_f32()
2925                            },
2926                            StageSection::Recover => {
2927                                stage_time / s.static_data.recover_duration.as_secs_f32()
2928                            },
2929                            _ => 0.0,
2930                        };
2931                        anim::quadruped_low::ShockwaveAnimation::update_skeleton(
2932                            &target_base,
2933                            (
2934                                rel_vel.magnitude(),
2935                                time,
2936                                Some(s.stage_section),
2937                                state.state_time,
2938                            ),
2939                            stage_progress,
2940                            &mut state_animation_rate,
2941                            skeleton_attr,
2942                        )
2943                    },
2944                    CharacterState::SpriteSummon(s) => {
2945                        let stage_time = s.timer.as_secs_f32();
2946                        let stage_progress = match s.stage_section {
2947                            StageSection::Buildup => {
2948                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2949                            },
2950                            StageSection::Action => {
2951                                stage_time / s.static_data.cast_duration.as_secs_f32()
2952                            },
2953                            StageSection::Recover => {
2954                                stage_time / s.static_data.recover_duration.as_secs_f32()
2955                            },
2956                            _ => 0.0,
2957                        };
2958                        anim::quadruped_low::SpriteSummonAnimation::update_skeleton(
2959                            &target_base,
2960                            (
2961                                rel_vel.magnitude(),
2962                                time,
2963                                Some(s.stage_section),
2964                                state.state_time,
2965                            ),
2966                            stage_progress,
2967                            &mut state_animation_rate,
2968                            skeleton_attr,
2969                        )
2970                    },
2971                    CharacterState::BasicMelee(s) => {
2972                        let stage_time = s.timer.as_secs_f32();
2973
2974                        let stage_progress = match s.stage_section {
2975                            StageSection::Buildup => {
2976                                stage_time / s.static_data.buildup_duration.as_secs_f32()
2977                            },
2978                            StageSection::Action => {
2979                                stage_time / s.static_data.swing_duration.as_secs_f32()
2980                            },
2981                            StageSection::Recover => {
2982                                stage_time / s.static_data.recover_duration.as_secs_f32()
2983                            },
2984
2985                            _ => 0.0,
2986                        };
2987                        anim::quadruped_low::BetaAnimation::update_skeleton(
2988                            &target_base,
2989                            (rel_vel.magnitude(), time, s.stage_section, state.state_time),
2990                            stage_progress,
2991                            &mut state_animation_rate,
2992                            skeleton_attr,
2993                        )
2994                    },
2995
2996                    CharacterState::ChargedMelee(s) => {
2997                        let stage_time = s.timer.as_secs_f32();
2998
2999                        let stage_progress = match s.stage_section {
3000                            StageSection::Buildup => {
3001                                if let Some((dur, _)) = s.static_data.buildup_strike {
3002                                    stage_time / dur.as_secs_f32()
3003                                } else {
3004                                    stage_time
3005                                }
3006                            },
3007                            StageSection::Charge => {
3008                                stage_time / s.static_data.charge_duration.as_secs_f32()
3009                            },
3010                            StageSection::Action => {
3011                                stage_time / s.static_data.swing_duration.as_secs_f32()
3012                            },
3013                            StageSection::Recover => {
3014                                stage_time / s.static_data.recover_duration.as_secs_f32()
3015                            },
3016
3017                            _ => 0.0,
3018                        };
3019                        anim::quadruped_low::TailwhipAnimation::update_skeleton(
3020                            &target_base,
3021                            (
3022                                ability_id,
3023                                rel_vel.magnitude(),
3024                                time,
3025                                Some(s.stage_section),
3026                                state.state_time,
3027                            ),
3028                            stage_progress,
3029                            &mut state_animation_rate,
3030                            skeleton_attr,
3031                        )
3032                    },
3033                    CharacterState::Stunned(s) => {
3034                        let stage_time = s.timer.as_secs_f32();
3035                        let stage_progress = match s.stage_section {
3036                            StageSection::Buildup => {
3037                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3038                            },
3039                            StageSection::Recover => {
3040                                stage_time / s.static_data.recover_duration.as_secs_f32()
3041                            },
3042                            _ => 0.0,
3043                        };
3044                        match s.static_data.poise_state {
3045                            PoiseState::Normal | PoiseState::Stunned | PoiseState::Interrupted => {
3046                                anim::quadruped_low::StunnedAnimation::update_skeleton(
3047                                    &target_base,
3048                                    (
3049                                        rel_vel.magnitude(),
3050                                        time,
3051                                        Some(s.stage_section),
3052                                        state.state_time,
3053                                    ),
3054                                    stage_progress,
3055                                    &mut state_animation_rate,
3056                                    skeleton_attr,
3057                                )
3058                            },
3059                            PoiseState::Dazed | PoiseState::KnockedDown => {
3060                                anim::quadruped_low::StunnedAnimation::update_skeleton(
3061                                    &target_base,
3062                                    (
3063                                        rel_vel.magnitude(),
3064                                        time,
3065                                        Some(s.stage_section),
3066                                        state.state_time,
3067                                    ),
3068                                    stage_progress,
3069                                    &mut state_animation_rate,
3070                                    skeleton_attr,
3071                                )
3072                            },
3073                        }
3074                    },
3075                    CharacterState::ComboMelee2(s) => {
3076                        let timer = s.timer.as_secs_f32();
3077                        let current_strike = s.completed_strikes % s.static_data.strikes.len();
3078                        let strike_data = s.static_data.strikes[current_strike];
3079                        let progress = match s.stage_section {
3080                            StageSection::Buildup => {
3081                                timer / strike_data.buildup_duration.as_secs_f32()
3082                            },
3083                            StageSection::Action => {
3084                                timer / strike_data.swing_duration.as_secs_f32()
3085                            },
3086                            StageSection::Recover => {
3087                                timer / strike_data.recover_duration.as_secs_f32()
3088                            },
3089                            _ => 0.0,
3090                        };
3091
3092                        anim::quadruped_low::ComboAnimation::update_skeleton(
3093                            &target_base,
3094                            (
3095                                ability_id,
3096                                s.stage_section,
3097                                current_strike,
3098                                time,
3099                                state.state_time,
3100                            ),
3101                            progress,
3102                            &mut state_animation_rate,
3103                            skeleton_attr,
3104                        )
3105                    },
3106                    CharacterState::BasicBeam(s) => {
3107                        let stage_time = s.timer.as_secs_f32();
3108                        let stage_progress = match s.stage_section {
3109                            StageSection::Buildup => {
3110                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3111                            },
3112                            StageSection::Action => s.timer.as_secs_f32(),
3113                            StageSection::Recover => {
3114                                stage_time / s.static_data.recover_duration.as_secs_f32()
3115                            },
3116                            _ => 0.0,
3117                        };
3118                        anim::quadruped_low::BreatheAnimation::update_skeleton(
3119                            &target_base,
3120                            (
3121                                rel_vel.magnitude(),
3122                                time,
3123                                Some(s.stage_section),
3124                                state.state_time,
3125                            ),
3126                            stage_progress,
3127                            &mut state_animation_rate,
3128                            skeleton_attr,
3129                        )
3130                    },
3131                    CharacterState::DashMelee(s) => {
3132                        let stage_time = s.timer.as_secs_f32();
3133                        let stage_progress = match s.stage_section {
3134                            StageSection::Buildup => {
3135                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3136                            },
3137                            StageSection::Charge => stage_time,
3138                            StageSection::Action => {
3139                                stage_time / s.static_data.swing_duration.as_secs_f32()
3140                            },
3141                            StageSection::Recover => {
3142                                stage_time / s.static_data.recover_duration.as_secs_f32()
3143                            },
3144                            _ => 0.0,
3145                        };
3146                        anim::quadruped_low::DashAnimation::update_skeleton(
3147                            &target_base,
3148                            (
3149                                ability_id,
3150                                rel_vel.magnitude(),
3151                                time,
3152                                Some(s.stage_section),
3153                                state.state_time,
3154                                heads,
3155                            ),
3156                            stage_progress,
3157                            &mut state_animation_rate,
3158                            skeleton_attr,
3159                        )
3160                    },
3161                    CharacterState::LeapShockwave(s) => {
3162                        let stage_time = s.timer.as_secs_f32();
3163                        let stage_progress = match s.stage_section {
3164                            StageSection::Buildup => {
3165                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3166                            },
3167                            StageSection::Movement => {
3168                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3169                            },
3170                            StageSection::Action => {
3171                                stage_time / s.static_data.swing_duration.as_secs_f32()
3172                            },
3173                            StageSection::Recover => {
3174                                stage_time / s.static_data.recover_duration.as_secs_f32()
3175                            },
3176                            _ => 0.0,
3177                        };
3178                        anim::quadruped_low::LeapShockAnimation::update_skeleton(
3179                            &target_base,
3180                            (ability_id, rel_vel, time, Some(s.stage_section), heads),
3181                            stage_progress,
3182                            &mut state_animation_rate,
3183                            skeleton_attr,
3184                        )
3185                    },
3186                    // TODO!
3187                    _ => target_base,
3188                };
3189
3190                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
3191                state.update(
3192                    renderer,
3193                    trail_mgr,
3194                    update_buf,
3195                    &common_params,
3196                    state_animation_rate,
3197                    model,
3198                    body,
3199                );
3200            },
3201            Body::BirdMedium(body) => {
3202                let (model, skeleton_attr) = self.bird_medium_model_cache.get_or_create_model(
3203                    renderer,
3204                    &mut self.atlas,
3205                    body,
3206                    inventory,
3207                    (),
3208                    tick,
3209                    viewpoint_camera_mode,
3210                    viewpoint_character_state,
3211                    slow_jobs,
3212                    None,
3213                );
3214
3215                let state = self
3216                    .states
3217                    .bird_medium_states
3218                    .entry(entity)
3219                    .or_insert_with(|| {
3220                        FigureState::new(renderer, BirdMediumSkeleton::default(), body)
3221                    });
3222
3223                // Average velocity relative to the current ground
3224                let rel_avg_vel = state.avg_vel - physics.ground_vel;
3225
3226                let (character, last_character) = match (character, last_character) {
3227                    (Some(c), Some(l)) => (c, l),
3228                    _ => return,
3229                };
3230
3231                if !character.same_variant(&last_character.0) {
3232                    state.state_time = 0.0;
3233                }
3234
3235                let target_base = match (
3236                    physics.on_ground.is_some(),
3237                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
3238                    physics.in_liquid().is_some(),                      // In water
3239                    is_rider.is_some() || is_volume_rider.is_some(),
3240                ) {
3241                    // Standing
3242                    (true, false, false, _) => anim::bird_medium::IdleAnimation::update_skeleton(
3243                        &BirdMediumSkeleton::default(),
3244                        time,
3245                        state.state_time,
3246                        &mut state_animation_rate,
3247                        skeleton_attr,
3248                    ),
3249                    // Running
3250                    (true, true, false, false) => {
3251                        anim::bird_medium::RunAnimation::update_skeleton(
3252                            &BirdMediumSkeleton::default(),
3253                            (
3254                                rel_vel,
3255                                // TODO: Update to use the quaternion.
3256                                ori * anim::vek::Vec3::<f32>::unit_y(),
3257                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3258                                rel_avg_vel,
3259                                state.acc_vel,
3260                            ),
3261                            state.state_time,
3262                            &mut state_animation_rate,
3263                            skeleton_attr,
3264                        )
3265                    },
3266                    // In air
3267                    (false, _, false, false) => {
3268                        anim::bird_medium::FlyAnimation::update_skeleton(
3269                            &BirdMediumSkeleton::default(),
3270                            (
3271                                rel_vel,
3272                                // TODO: Update to use the quaternion.
3273                                ori * anim::vek::Vec3::<f32>::unit_y(),
3274                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3275                            ),
3276                            state.state_time,
3277                            &mut state_animation_rate,
3278                            skeleton_attr,
3279                        )
3280                    },
3281                    // Swim
3282                    (_, true, _, false) => anim::bird_medium::SwimAnimation::update_skeleton(
3283                        &BirdMediumSkeleton::default(),
3284                        time,
3285                        state.state_time,
3286                        &mut state_animation_rate,
3287                        skeleton_attr,
3288                    ),
3289                    // TODO!
3290                    _ => anim::bird_medium::IdleAnimation::update_skeleton(
3291                        &BirdMediumSkeleton::default(),
3292                        time,
3293                        state.state_time,
3294                        &mut state_animation_rate,
3295                        skeleton_attr,
3296                    ),
3297                };
3298                let target_bones = match &character {
3299                    CharacterState::Sit => anim::bird_medium::FeedAnimation::update_skeleton(
3300                        &target_base,
3301                        time,
3302                        state.state_time,
3303                        &mut state_animation_rate,
3304                        skeleton_attr,
3305                    ),
3306                    CharacterState::BasicBeam(s) => {
3307                        let stage_time = s.timer.as_secs_f32();
3308                        let stage_progress = match s.stage_section {
3309                            StageSection::Buildup => {
3310                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3311                            },
3312                            StageSection::Action => s.timer.as_secs_f32(),
3313                            StageSection::Recover => {
3314                                stage_time / s.static_data.recover_duration.as_secs_f32()
3315                            },
3316                            _ => 0.0,
3317                        };
3318                        anim::bird_medium::BreatheAnimation::update_skeleton(
3319                            &target_base,
3320                            (
3321                                rel_vel,
3322                                time,
3323                                ori * anim::vek::Vec3::<f32>::unit_y(),
3324                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3325                                Some(s.stage_section),
3326                                state.state_time,
3327                                look_dir,
3328                                physics.on_ground.is_some(),
3329                            ),
3330                            stage_progress,
3331                            &mut state_animation_rate,
3332                            skeleton_attr,
3333                        )
3334                    },
3335                    CharacterState::BasicMelee(s) => {
3336                        let stage_time = s.timer.as_secs_f32();
3337                        let stage_progress = match s.stage_section {
3338                            StageSection::Buildup => {
3339                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3340                            },
3341                            StageSection::Action => {
3342                                stage_time / s.static_data.swing_duration.as_secs_f32()
3343                            },
3344                            StageSection::Recover => {
3345                                stage_time / s.static_data.recover_duration.as_secs_f32()
3346                            },
3347                            _ => 0.0,
3348                        };
3349                        anim::bird_medium::AlphaAnimation::update_skeleton(
3350                            &target_base,
3351                            (
3352                                Some(s.stage_section),
3353                                time,
3354                                state.state_time,
3355                                ori * anim::vek::Vec3::<f32>::unit_y(),
3356                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3357                                physics.on_ground.is_some(),
3358                            ),
3359                            stage_progress,
3360                            &mut state_animation_rate,
3361                            skeleton_attr,
3362                        )
3363                    },
3364                    CharacterState::BasicRanged(s) => {
3365                        let stage_time = s.timer.as_secs_f32();
3366
3367                        let stage_progress = match s.stage_section {
3368                            StageSection::Buildup => {
3369                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3370                            },
3371                            StageSection::Recover => {
3372                                stage_time / s.static_data.recover_duration.as_secs_f32()
3373                            },
3374
3375                            _ => 0.0,
3376                        };
3377                        anim::bird_medium::ShootAnimation::update_skeleton(
3378                            &target_base,
3379                            (
3380                                rel_vel,
3381                                time,
3382                                Some(s.stage_section),
3383                                state.state_time,
3384                                look_dir,
3385                                physics.on_ground.is_some(),
3386                            ),
3387                            stage_progress,
3388                            &mut state_animation_rate,
3389                            skeleton_attr,
3390                        )
3391                    },
3392                    CharacterState::Shockwave(s) => {
3393                        let stage_time = s.timer.as_secs_f32();
3394                        let stage_progress = match s.stage_section {
3395                            StageSection::Buildup => {
3396                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3397                            },
3398                            StageSection::Action => {
3399                                stage_time / s.static_data.swing_duration.as_secs_f32()
3400                            },
3401                            StageSection::Recover => {
3402                                stage_time / s.static_data.recover_duration.as_secs_f32()
3403                            },
3404                            _ => 0.0,
3405                        };
3406                        anim::bird_medium::ShockwaveAnimation::update_skeleton(
3407                            &target_base,
3408                            (Some(s.stage_section), physics.on_ground.is_some()),
3409                            stage_progress,
3410                            &mut state_animation_rate,
3411                            skeleton_attr,
3412                        )
3413                    },
3414                    CharacterState::BasicSummon(s) => {
3415                        let stage_time = s.timer.as_secs_f32();
3416                        let stage_progress = match s.stage_section {
3417                            StageSection::Buildup => {
3418                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3419                            },
3420
3421                            StageSection::Action => {
3422                                stage_time / s.static_data.cast_duration.as_secs_f32()
3423                            },
3424                            StageSection::Recover => {
3425                                stage_time / s.static_data.recover_duration.as_secs_f32()
3426                            },
3427                            _ => 0.0,
3428                        };
3429
3430                        anim::bird_medium::SummonAnimation::update_skeleton(
3431                            &target_base,
3432                            (
3433                                time,
3434                                Some(s.stage_section),
3435                                state.state_time,
3436                                look_dir,
3437                                physics.on_ground.is_some(),
3438                            ),
3439                            stage_progress,
3440                            &mut state_animation_rate,
3441                            skeleton_attr,
3442                        )
3443                    },
3444                    CharacterState::DashMelee(s) => {
3445                        let stage_time = s.timer.as_secs_f32();
3446                        let stage_progress = match s.stage_section {
3447                            StageSection::Buildup => {
3448                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3449                            },
3450                            StageSection::Charge => {
3451                                stage_time / s.static_data.charge_duration.as_secs_f32()
3452                            },
3453                            StageSection::Action => {
3454                                stage_time / s.static_data.swing_duration.as_secs_f32()
3455                            },
3456                            StageSection::Recover => {
3457                                stage_time / s.static_data.recover_duration.as_secs_f32()
3458                            },
3459                            _ => 0.0,
3460                        };
3461                        anim::bird_medium::DashAnimation::update_skeleton(
3462                            &target_base,
3463                            (
3464                                rel_vel,
3465                                // TODO: Update to use the quaternion.
3466                                ori * anim::vek::Vec3::<f32>::unit_y(),
3467                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3468                                state.acc_vel,
3469                                Some(s.stage_section),
3470                                time,
3471                                state.state_time,
3472                            ),
3473                            stage_progress,
3474                            &mut state_animation_rate,
3475                            skeleton_attr,
3476                        )
3477                    },
3478                    CharacterState::Stunned(s) => {
3479                        let stage_time = s.timer.as_secs_f32();
3480                        let stage_progress = match s.stage_section {
3481                            StageSection::Buildup => {
3482                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3483                            },
3484                            StageSection::Recover => {
3485                                stage_time / s.static_data.recover_duration.as_secs_f32()
3486                            },
3487                            _ => 0.0,
3488                        };
3489                        match s.static_data.poise_state {
3490                            PoiseState::Normal
3491                            | PoiseState::Interrupted
3492                            | PoiseState::Stunned
3493                            | PoiseState::Dazed
3494                            | PoiseState::KnockedDown => {
3495                                anim::bird_medium::StunnedAnimation::update_skeleton(
3496                                    &target_base,
3497                                    (time, Some(s.stage_section), state.state_time),
3498                                    stage_progress,
3499                                    &mut state_animation_rate,
3500                                    skeleton_attr,
3501                                )
3502                            },
3503                        }
3504                    },
3505                    // TODO!
3506                    _ => target_base,
3507                };
3508
3509                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
3510                state.update(
3511                    renderer,
3512                    trail_mgr,
3513                    update_buf,
3514                    &common_params,
3515                    state_animation_rate,
3516                    model,
3517                    body,
3518                );
3519            },
3520            Body::FishMedium(body) => {
3521                let (model, skeleton_attr) = self.fish_medium_model_cache.get_or_create_model(
3522                    renderer,
3523                    &mut self.atlas,
3524                    body,
3525                    inventory,
3526                    (),
3527                    tick,
3528                    viewpoint_camera_mode,
3529                    viewpoint_character_state,
3530                    slow_jobs,
3531                    None,
3532                );
3533
3534                let state = self
3535                    .states
3536                    .fish_medium_states
3537                    .entry(entity)
3538                    .or_insert_with(|| {
3539                        FigureState::new(renderer, FishMediumSkeleton::default(), body)
3540                    });
3541
3542                // Average velocity relative to the current ground
3543                let rel_avg_vel = state.avg_vel - physics.ground_vel;
3544
3545                let (character, last_character) = match (character, last_character) {
3546                    (Some(c), Some(l)) => (c, l),
3547                    _ => return,
3548                };
3549
3550                if !character.same_variant(&last_character.0) {
3551                    state.state_time = 0.0;
3552                }
3553
3554                let target_base = match (
3555                    physics.on_ground.is_some(),
3556                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
3557                    physics.in_liquid().is_some(),                      // In water
3558                ) {
3559                    // Idle
3560                    (_, false, _) => anim::fish_medium::IdleAnimation::update_skeleton(
3561                        &FishMediumSkeleton::default(),
3562                        (
3563                            rel_vel,
3564                            // TODO: Update to use the quaternion.
3565                            ori * anim::vek::Vec3::<f32>::unit_y(),
3566                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3567                            time,
3568                            rel_avg_vel,
3569                        ),
3570                        state.state_time,
3571                        &mut state_animation_rate,
3572                        skeleton_attr,
3573                    ),
3574                    // Swim
3575                    (_, true, _) => anim::fish_medium::SwimAnimation::update_skeleton(
3576                        &FishMediumSkeleton::default(),
3577                        (
3578                            rel_vel,
3579                            // TODO: Update to use the quaternion.
3580                            ori * anim::vek::Vec3::<f32>::unit_y(),
3581                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3582                            time,
3583                            rel_avg_vel,
3584                            state.acc_vel,
3585                        ),
3586                        state.state_time,
3587                        &mut state_animation_rate,
3588                        skeleton_attr,
3589                    ),
3590                };
3591
3592                state.skeleton = Lerp::lerp(&state.skeleton, &target_base, dt_lerp);
3593                state.update(
3594                    renderer,
3595                    trail_mgr,
3596                    update_buf,
3597                    &common_params,
3598                    state_animation_rate,
3599                    model,
3600                    body,
3601                );
3602            },
3603            Body::BipedSmall(body) => {
3604                let (model, skeleton_attr) = self.biped_small_model_cache.get_or_create_model(
3605                    renderer,
3606                    &mut self.atlas,
3607                    body,
3608                    inventory,
3609                    (),
3610                    tick,
3611                    viewpoint_camera_mode,
3612                    viewpoint_character_state,
3613                    slow_jobs,
3614                    None,
3615                );
3616
3617                let state = self
3618                    .states
3619                    .biped_small_states
3620                    .entry(entity)
3621                    .or_insert_with(|| {
3622                        FigureState::new(renderer, BipedSmallSkeleton::default(), body)
3623                    });
3624
3625                // Average velocity relative to the current ground
3626                let rel_avg_vel = state.avg_vel - physics.ground_vel;
3627
3628                let (character, last_character) = match (character, last_character) {
3629                    (Some(c), Some(l)) => (c, l),
3630                    _ => return,
3631                };
3632
3633                if !character.same_variant(&last_character.0) {
3634                    state.state_time = 0.0;
3635                }
3636
3637                let target_base = match (
3638                    physics.on_ground.is_some(),
3639                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
3640                    physics.in_liquid().is_some(),                      // In water
3641                ) {
3642                    // Idle
3643                    (true, false, false) => anim::biped_small::IdleAnimation::update_skeleton(
3644                        &BipedSmallSkeleton::default(),
3645                        (
3646                            rel_vel,
3647                            ori * anim::vek::Vec3::<f32>::unit_y(),
3648                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3649                            time,
3650                            rel_avg_vel,
3651                        ),
3652                        state.state_time,
3653                        &mut state_animation_rate,
3654                        skeleton_attr,
3655                    ),
3656                    // Run
3657                    (true, true, _) => anim::biped_small::RunAnimation::update_skeleton(
3658                        &BipedSmallSkeleton::default(),
3659                        (
3660                            rel_vel,
3661                            ori * anim::vek::Vec3::<f32>::unit_y(),
3662                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3663                            time,
3664                            rel_avg_vel,
3665                            state.acc_vel,
3666                        ),
3667                        state.state_time,
3668                        &mut state_animation_rate,
3669                        skeleton_attr,
3670                    ),
3671                    // Jump
3672                    (false, _, false) => anim::biped_small::RunAnimation::update_skeleton(
3673                        &BipedSmallSkeleton::default(),
3674                        (
3675                            rel_vel,
3676                            ori * anim::vek::Vec3::<f32>::unit_y(),
3677                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3678                            time,
3679                            rel_avg_vel,
3680                            state.acc_vel,
3681                        ),
3682                        state.state_time,
3683                        &mut state_animation_rate,
3684                        skeleton_attr,
3685                    ),
3686                    // Swim
3687                    (false, _, true) => anim::biped_small::RunAnimation::update_skeleton(
3688                        &BipedSmallSkeleton::default(),
3689                        (
3690                            rel_vel,
3691                            ori * anim::vek::Vec3::<f32>::unit_y(),
3692                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3693                            time,
3694                            rel_avg_vel,
3695                            state.acc_vel,
3696                        ),
3697                        state.state_time,
3698                        &mut state_animation_rate,
3699                        skeleton_attr,
3700                    ),
3701                    _ => anim::biped_small::IdleAnimation::update_skeleton(
3702                        &BipedSmallSkeleton::default(),
3703                        (
3704                            rel_vel,
3705                            ori * anim::vek::Vec3::<f32>::unit_y(),
3706                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3707                            time,
3708                            rel_avg_vel,
3709                        ),
3710                        state.state_time,
3711                        &mut state_animation_rate,
3712                        skeleton_attr,
3713                    ),
3714                };
3715
3716                let target_bones = match &character {
3717                    CharacterState::Wielding { .. } => {
3718                        anim::biped_small::WieldAnimation::update_skeleton(
3719                            &target_base,
3720                            (
3721                                (active_tool_kind, active_tool_spec),
3722                                second_tool_kind,
3723                                rel_vel,
3724                                ori * anim::vek::Vec3::<f32>::unit_y(),
3725                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3726                                time,
3727                                rel_avg_vel,
3728                                state.acc_vel,
3729                            ),
3730                            state.state_time,
3731                            &mut state_animation_rate,
3732                            skeleton_attr,
3733                        )
3734                    },
3735                    CharacterState::DashMelee(s) => {
3736                        let stage_time = s.timer.as_secs_f32();
3737                        let stage_progress = match s.stage_section {
3738                            StageSection::Buildup => {
3739                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3740                            },
3741                            StageSection::Charge => {
3742                                stage_time / s.static_data.charge_duration.as_secs_f32()
3743                            },
3744                            StageSection::Action => {
3745                                stage_time / s.static_data.swing_duration.as_secs_f32()
3746                            },
3747                            StageSection::Recover => {
3748                                stage_time / s.static_data.recover_duration.as_secs_f32()
3749                            },
3750                            _ => 0.0,
3751                        };
3752                        anim::biped_small::DashAnimation::update_skeleton(
3753                            &target_base,
3754                            (
3755                                ability_id,
3756                                rel_vel,
3757                                ori * anim::vek::Vec3::<f32>::unit_y(),
3758                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3759                                time,
3760                                rel_avg_vel,
3761                                state.acc_vel,
3762                                Some(s.stage_section),
3763                                state.state_time,
3764                            ),
3765                            stage_progress,
3766                            &mut state_animation_rate,
3767                            skeleton_attr,
3768                        )
3769                    },
3770                    CharacterState::ComboMelee2(s) => {
3771                        let timer = s.timer.as_secs_f32();
3772                        let current_strike = s.completed_strikes % s.static_data.strikes.len();
3773                        let strike_data = s.static_data.strikes[current_strike];
3774                        let progress = match s.stage_section {
3775                            StageSection::Buildup => {
3776                                timer / strike_data.buildup_duration.as_secs_f32()
3777                            },
3778                            StageSection::Action => {
3779                                timer / strike_data.swing_duration.as_secs_f32()
3780                            },
3781                            StageSection::Recover => {
3782                                timer / strike_data.recover_duration.as_secs_f32()
3783                            },
3784                            _ => 0.0,
3785                        };
3786
3787                        anim::biped_small::ComboAnimation::update_skeleton(
3788                            &target_base,
3789                            (
3790                                ability_id,
3791                                s.stage_section,
3792                                current_strike,
3793                                rel_vel,
3794                                time,
3795                                state.state_time,
3796                            ),
3797                            progress,
3798                            &mut state_animation_rate,
3799                            skeleton_attr,
3800                        )
3801                    },
3802                    CharacterState::Stunned(s) => {
3803                        let stage_time = s.timer.as_secs_f32();
3804                        let wield_status = s.was_wielded;
3805                        let stage_progress = match s.stage_section {
3806                            StageSection::Buildup => {
3807                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3808                            },
3809                            StageSection::Recover => {
3810                                stage_time / s.static_data.recover_duration.as_secs_f32()
3811                            },
3812                            _ => 0.0,
3813                        };
3814                        match s.static_data.poise_state {
3815                            PoiseState::Normal
3816                            | PoiseState::Interrupted
3817                            | PoiseState::Stunned
3818                            | PoiseState::Dazed
3819                            | PoiseState::KnockedDown => {
3820                                anim::biped_small::StunnedAnimation::update_skeleton(
3821                                    &target_base,
3822                                    (
3823                                        active_tool_kind,
3824                                        rel_vel,
3825                                        ori * anim::vek::Vec3::<f32>::unit_y(),
3826                                        state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3827                                        time,
3828                                        state.avg_vel,
3829                                        state.acc_vel,
3830                                        wield_status,
3831                                        Some(s.stage_section),
3832                                        state.state_time,
3833                                    ),
3834                                    stage_progress,
3835                                    &mut state_animation_rate,
3836                                    skeleton_attr,
3837                                )
3838                            },
3839                        }
3840                    },
3841                    CharacterState::ChargedRanged(s) => {
3842                        let stage_time = s.timer.as_secs_f32();
3843
3844                        let stage_progress = match s.stage_section {
3845                            StageSection::Buildup => {
3846                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3847                            },
3848                            StageSection::Recover => {
3849                                stage_time / s.static_data.recover_duration.as_secs_f32()
3850                            },
3851
3852                            _ => 0.0,
3853                        };
3854                        anim::biped_small::ShootAnimation::update_skeleton(
3855                            &target_base,
3856                            (
3857                                active_tool_kind,
3858                                rel_vel,
3859                                ori * anim::vek::Vec3::<f32>::unit_y(),
3860                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3861                                time,
3862                                rel_avg_vel,
3863                                state.acc_vel,
3864                                Some(s.stage_section),
3865                                state.state_time,
3866                                ability_id,
3867                            ),
3868                            stage_progress,
3869                            &mut state_animation_rate,
3870                            skeleton_attr,
3871                        )
3872                    },
3873                    CharacterState::RepeaterRanged(s) => {
3874                        let stage_time = s.timer.as_secs_f32();
3875
3876                        let stage_progress = match s.stage_section {
3877                            StageSection::Buildup => {
3878                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3879                            },
3880                            StageSection::Recover => {
3881                                stage_time / s.static_data.recover_duration.as_secs_f32()
3882                            },
3883
3884                            _ => 0.0,
3885                        };
3886                        anim::biped_small::ShootAnimation::update_skeleton(
3887                            &target_base,
3888                            (
3889                                active_tool_kind,
3890                                rel_vel,
3891                                ori * anim::vek::Vec3::<f32>::unit_y(),
3892                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3893                                time,
3894                                rel_avg_vel,
3895                                state.acc_vel,
3896                                Some(s.stage_section),
3897                                state.state_time,
3898                                ability_id,
3899                            ),
3900                            stage_progress,
3901                            &mut state_animation_rate,
3902                            skeleton_attr,
3903                        )
3904                    },
3905                    CharacterState::BasicRanged(s) => {
3906                        let stage_time = s.timer.as_secs_f32();
3907
3908                        let stage_progress = match s.stage_section {
3909                            StageSection::Buildup => {
3910                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3911                            },
3912                            StageSection::Recover => {
3913                                stage_time / s.static_data.recover_duration.as_secs_f32()
3914                            },
3915
3916                            _ => 0.0,
3917                        };
3918                        anim::biped_small::ShootAnimation::update_skeleton(
3919                            &target_base,
3920                            (
3921                                active_tool_kind,
3922                                rel_vel,
3923                                ori * anim::vek::Vec3::<f32>::unit_y(),
3924                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3925                                time,
3926                                rel_avg_vel,
3927                                state.acc_vel,
3928                                Some(s.stage_section),
3929                                state.state_time,
3930                                ability_id,
3931                            ),
3932                            stage_progress,
3933                            &mut state_animation_rate,
3934                            skeleton_attr,
3935                        )
3936                    },
3937                    CharacterState::BasicBeam(s) => {
3938                        let stage_time = s.timer.as_secs_f32();
3939                        let stage_progress = match s.stage_section {
3940                            StageSection::Buildup => {
3941                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3942                            },
3943                            StageSection::Action => s.timer.as_secs_f32(),
3944                            StageSection::Recover => {
3945                                stage_time / s.static_data.recover_duration.as_secs_f32()
3946                            },
3947                            _ => 0.0,
3948                        };
3949                        anim::biped_small::BeamAnimation::update_skeleton(
3950                            &target_base,
3951                            (
3952                                ability_id,
3953                                active_tool_kind,
3954                                rel_vel,
3955                                ori * anim::vek::Vec3::<f32>::unit_y(),
3956                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3957                                time,
3958                                rel_avg_vel,
3959                                state.acc_vel,
3960                                Some(s.stage_section),
3961                                state.state_time,
3962                            ),
3963                            stage_progress,
3964                            &mut state_animation_rate,
3965                            skeleton_attr,
3966                        )
3967                    },
3968                    CharacterState::BasicMelee(s) => {
3969                        let stage_time = s.timer.as_secs_f32();
3970                        let stage_progress = match s.stage_section {
3971                            StageSection::Buildup => {
3972                                stage_time / s.static_data.buildup_duration.as_secs_f32()
3973                            },
3974                            StageSection::Action => {
3975                                stage_time / s.static_data.swing_duration.as_secs_f32()
3976                            },
3977                            StageSection::Recover => {
3978                                stage_time / s.static_data.recover_duration.as_secs_f32()
3979                            },
3980                            _ => 0.0,
3981                        };
3982                        anim::biped_small::AlphaAnimation::update_skeleton(
3983                            &target_base,
3984                            (
3985                                ability_id,
3986                                active_tool_kind,
3987                                rel_vel,
3988                                ori * anim::vek::Vec3::<f32>::unit_y(),
3989                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
3990                                time,
3991                                rel_avg_vel,
3992                                state.acc_vel,
3993                                Some(s.stage_section),
3994                                state.state_time,
3995                            ),
3996                            stage_progress,
3997                            &mut state_animation_rate,
3998                            skeleton_attr,
3999                        )
4000                    },
4001                    CharacterState::LeapMelee(s) => {
4002                        let stage_time = s.timer.as_secs_f32();
4003                        let stage_progress = match s.stage_section {
4004                            StageSection::Buildup => {
4005                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4006                            },
4007                            StageSection::Movement => {
4008                                stage_time / s.static_data.movement_duration.as_secs_f32()
4009                            },
4010                            StageSection::Action => {
4011                                stage_time / s.static_data.swing_duration.as_secs_f32()
4012                            },
4013                            StageSection::Recover => {
4014                                stage_time / s.static_data.recover_duration.as_secs_f32()
4015                            },
4016                            _ => 0.0,
4017                        };
4018                        anim::biped_small::LeapAnimation::update_skeleton(
4019                            &target_base,
4020                            (
4021                                active_tool_kind,
4022                                second_tool_kind,
4023                                rel_vel,
4024                                time,
4025                                Some(s.stage_section),
4026                            ),
4027                            stage_progress,
4028                            &mut state_animation_rate,
4029                            skeleton_attr,
4030                        )
4031                    },
4032                    CharacterState::Shockwave(s) => {
4033                        let stage_time = s.timer.as_secs_f32();
4034                        let stage_progress = match s.stage_section {
4035                            StageSection::Buildup => {
4036                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4037                            },
4038                            StageSection::Action => {
4039                                stage_time / s.static_data.swing_duration.as_secs_f32()
4040                            },
4041                            StageSection::Recover => {
4042                                stage_time / s.static_data.recover_duration.as_secs_f32()
4043                            },
4044                            _ => 0.0,
4045                        };
4046                        anim::biped_small::ShockwaveAnimation::update_skeleton(
4047                            &target_base,
4048                            (
4049                                active_tool_kind,
4050                                second_tool_kind,
4051                                rel_vel,
4052                                ori * anim::vek::Vec3::<f32>::unit_y(),
4053                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4054                                time,
4055                                rel_avg_vel,
4056                                state.acc_vel,
4057                                Some(s.stage_section),
4058                                state.state_time,
4059                            ),
4060                            stage_progress,
4061                            &mut state_animation_rate,
4062                            skeleton_attr,
4063                        )
4064                    },
4065                    CharacterState::SpriteSummon(s) => {
4066                        let stage_time = s.timer.as_secs_f32();
4067                        let stage_progress = match s.stage_section {
4068                            StageSection::Buildup => {
4069                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4070                            },
4071                            StageSection::Action => {
4072                                stage_time / s.static_data.cast_duration.as_secs_f32()
4073                            },
4074                            StageSection::Recover => {
4075                                stage_time / s.static_data.recover_duration.as_secs_f32()
4076                            },
4077                            _ => 0.0,
4078                        };
4079                        anim::biped_small::SpriteSummonAnimation::update_skeleton(
4080                            &target_base,
4081                            (
4082                                active_tool_kind,
4083                                rel_vel,
4084                                ori * anim::vek::Vec3::<f32>::unit_y(),
4085                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4086                                time,
4087                                rel_avg_vel,
4088                                state.acc_vel,
4089                                Some(s.stage_section),
4090                                state.state_time,
4091                            ),
4092                            stage_progress,
4093                            &mut state_animation_rate,
4094                            skeleton_attr,
4095                        )
4096                    },
4097                    CharacterState::BasicSummon(s) => {
4098                        let stage_time = s.timer.as_secs_f32();
4099                        let stage_progress = match s.stage_section {
4100                            StageSection::Buildup => {
4101                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4102                            },
4103                            StageSection::Action => {
4104                                stage_time / s.static_data.cast_duration.as_secs_f32()
4105                            },
4106                            StageSection::Recover => {
4107                                stage_time / s.static_data.recover_duration.as_secs_f32()
4108                            },
4109                            _ => 0.0,
4110                        };
4111                        anim::biped_small::SummonAnimation::update_skeleton(
4112                            &target_base,
4113                            (
4114                                ability_id,
4115                                active_tool_kind,
4116                                rel_vel,
4117                                ori * anim::vek::Vec3::<f32>::unit_y(),
4118                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4119                                time,
4120                                rel_avg_vel,
4121                                state.acc_vel,
4122                                Some(s.stage_section),
4123                                state.state_time,
4124                            ),
4125                            stage_progress,
4126                            &mut state_animation_rate,
4127                            skeleton_attr,
4128                        )
4129                    },
4130                    CharacterState::SelfBuff(_) | CharacterState::BasicAura(_) => {
4131                        let progress = if let Some(((timer, stage_section), durations)) = character
4132                            .timer()
4133                            .zip(character.stage_section())
4134                            .zip(character.durations())
4135                        {
4136                            match stage_section {
4137                                StageSection::Buildup => durations.buildup,
4138                                StageSection::Action => durations.action,
4139                                StageSection::Recover => durations.recover,
4140                                _ => None,
4141                            }
4142                            .map_or(timer.as_secs_f32(), |stage_duration| {
4143                                timer.as_secs_f32() / stage_duration.as_secs_f32()
4144                            })
4145                        } else {
4146                            0.0
4147                        };
4148
4149                        anim::biped_small::BuffAnimation::update_skeleton(
4150                            &target_base,
4151                            (
4152                                active_tool_kind,
4153                                second_tool_kind,
4154                                character.stage_section(),
4155                            ),
4156                            progress,
4157                            &mut state_animation_rate,
4158                            skeleton_attr,
4159                        )
4160                    },
4161                    CharacterState::BasicBlock(s) => {
4162                        let stage_time = s.timer.as_secs_f32();
4163                        let stage_progress = match s.stage_section {
4164                            StageSection::Buildup => {
4165                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4166                            },
4167                            StageSection::Action => stage_time,
4168                            StageSection::Recover => {
4169                                stage_time / s.static_data.recover_duration.as_secs_f32()
4170                            },
4171                            _ => 0.0,
4172                        };
4173                        anim::biped_small::BlockAnimation::update_skeleton(
4174                            &target_base,
4175                            (ability_id, s.stage_section),
4176                            stage_progress,
4177                            &mut state_animation_rate,
4178                            skeleton_attr,
4179                        )
4180                    },
4181                    CharacterState::RapidMelee(s) => {
4182                        let stage_time = s.timer.as_secs_f32();
4183                        let stage_progress = match s.stage_section {
4184                            StageSection::Buildup => {
4185                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4186                            },
4187                            StageSection::Action => {
4188                                stage_time / s.static_data.swing_duration.as_secs_f32()
4189                            },
4190                            StageSection::Recover => {
4191                                stage_time / s.static_data.recover_duration.as_secs_f32()
4192                            },
4193                            _ => 0.0,
4194                        };
4195
4196                        anim::biped_small::RapidMeleeAnimation::update_skeleton(
4197                            &target_base,
4198                            (ability_id, s.stage_section),
4199                            stage_progress,
4200                            &mut state_animation_rate,
4201                            skeleton_attr,
4202                        )
4203                    },
4204                    CharacterState::RiposteMelee(s) => {
4205                        let stage_time = s.timer.as_secs_f32();
4206                        let stage_progress = match s.stage_section {
4207                            StageSection::Buildup => {
4208                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4209                            },
4210                            StageSection::Action => {
4211                                stage_time / s.static_data.swing_duration.as_secs_f32()
4212                            },
4213                            StageSection::Recover => {
4214                                let recover_duration = if s.whiffed {
4215                                    s.static_data.whiffed_recover_duration.as_secs_f32()
4216                                } else {
4217                                    s.static_data.recover_duration.as_secs_f32()
4218                                };
4219                                stage_time / recover_duration
4220                            },
4221                            _ => 0.0,
4222                        };
4223
4224                        anim::biped_small::RiposteMeleeAnimation::update_skeleton(
4225                            &target_base,
4226                            (ability_id, s.stage_section),
4227                            stage_progress,
4228                            &mut state_animation_rate,
4229                            skeleton_attr,
4230                        )
4231                    },
4232                    // TODO!
4233                    _ => target_base,
4234                };
4235
4236                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
4237                state.update(
4238                    renderer,
4239                    trail_mgr,
4240                    update_buf,
4241                    &common_params,
4242                    state_animation_rate,
4243                    model,
4244                    body,
4245                );
4246            },
4247            Body::Dragon(body) => {
4248                let (model, skeleton_attr) = self.dragon_model_cache.get_or_create_model(
4249                    renderer,
4250                    &mut self.atlas,
4251                    body,
4252                    inventory,
4253                    (),
4254                    tick,
4255                    viewpoint_camera_mode,
4256                    viewpoint_character_state,
4257                    slow_jobs,
4258                    None,
4259                );
4260
4261                let state =
4262                    self.states.dragon_states.entry(entity).or_insert_with(|| {
4263                        FigureState::new(renderer, DragonSkeleton::default(), body)
4264                    });
4265
4266                // Average velocity relative to the current ground
4267                let rel_avg_vel = state.avg_vel - physics.ground_vel;
4268
4269                let (character, last_character) = match (character, last_character) {
4270                    (Some(c), Some(l)) => (c, l),
4271                    _ => return,
4272                };
4273
4274                if !character.same_variant(&last_character.0) {
4275                    state.state_time = 0.0;
4276                }
4277
4278                let target_base = match (
4279                    physics.on_ground.is_some(),
4280                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
4281                    physics.in_liquid().is_some(),                      // In water
4282                ) {
4283                    // Standing
4284                    (true, false, false) => anim::dragon::IdleAnimation::update_skeleton(
4285                        &DragonSkeleton::default(),
4286                        time,
4287                        state.state_time,
4288                        &mut state_animation_rate,
4289                        skeleton_attr,
4290                    ),
4291                    // Running
4292                    (true, true, false) => anim::dragon::RunAnimation::update_skeleton(
4293                        &DragonSkeleton::default(),
4294                        (
4295                            rel_vel.magnitude(),
4296                            // TODO: Update to use the quaternion.
4297                            ori * anim::vek::Vec3::<f32>::unit_y(),
4298                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4299                            time,
4300                            rel_avg_vel,
4301                        ),
4302                        state.state_time,
4303                        &mut state_animation_rate,
4304                        skeleton_attr,
4305                    ),
4306                    // In air
4307                    (false, _, false) => anim::dragon::FlyAnimation::update_skeleton(
4308                        &DragonSkeleton::default(),
4309                        (rel_vel.magnitude(), time),
4310                        state.state_time,
4311                        &mut state_animation_rate,
4312                        skeleton_attr,
4313                    ),
4314                    // TODO!
4315                    _ => anim::dragon::IdleAnimation::update_skeleton(
4316                        &DragonSkeleton::default(),
4317                        time,
4318                        state.state_time,
4319                        &mut state_animation_rate,
4320                        skeleton_attr,
4321                    ),
4322                };
4323
4324                state.skeleton = Lerp::lerp(&state.skeleton, &target_base, dt_lerp);
4325                state.update(
4326                    renderer,
4327                    trail_mgr,
4328                    update_buf,
4329                    &common_params,
4330                    state_animation_rate,
4331                    model,
4332                    body,
4333                );
4334            },
4335            Body::Theropod(body) => {
4336                let (model, skeleton_attr) = self.theropod_model_cache.get_or_create_model(
4337                    renderer,
4338                    &mut self.atlas,
4339                    body,
4340                    inventory,
4341                    (),
4342                    tick,
4343                    viewpoint_camera_mode,
4344                    viewpoint_character_state,
4345                    slow_jobs,
4346                    None,
4347                );
4348
4349                let state = self
4350                    .states
4351                    .theropod_states
4352                    .entry(entity)
4353                    .or_insert_with(|| {
4354                        FigureState::new(renderer, TheropodSkeleton::default(), body)
4355                    });
4356
4357                // Average velocity relative to the current ground
4358                let rel_avg_vel = state.avg_vel - physics.ground_vel;
4359
4360                let (character, last_character) = match (character, last_character) {
4361                    (Some(c), Some(l)) => (c, l),
4362                    _ => return,
4363                };
4364
4365                if !character.same_variant(&last_character.0) {
4366                    state.state_time = 0.0;
4367                }
4368
4369                let target_base = match (
4370                    physics.on_ground.is_some(),
4371                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
4372                    physics.in_liquid().is_some(),                      // In water
4373                ) {
4374                    // Standing
4375                    (true, false, false) => anim::theropod::IdleAnimation::update_skeleton(
4376                        &TheropodSkeleton::default(),
4377                        time,
4378                        state.state_time,
4379                        &mut state_animation_rate,
4380                        skeleton_attr,
4381                    ),
4382                    // Running
4383                    (true, true, false) => anim::theropod::RunAnimation::update_skeleton(
4384                        &TheropodSkeleton::default(),
4385                        (
4386                            rel_vel,
4387                            // TODO: Update to use the quaternion.
4388                            ori * anim::vek::Vec3::<f32>::unit_y(),
4389                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4390                            time,
4391                            rel_avg_vel,
4392                            state.acc_vel,
4393                        ),
4394                        state.state_time,
4395                        &mut state_animation_rate,
4396                        skeleton_attr,
4397                    ),
4398                    // In air
4399                    (false, _, false) => anim::theropod::JumpAnimation::update_skeleton(
4400                        &TheropodSkeleton::default(),
4401                        (
4402                            rel_vel.magnitude(),
4403                            // TODO: Update to use the quaternion.
4404                            ori * anim::vek::Vec3::<f32>::unit_y(),
4405                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4406                            time,
4407                            rel_avg_vel,
4408                        ),
4409                        state.state_time,
4410                        &mut state_animation_rate,
4411                        skeleton_attr,
4412                    ),
4413                    _ => anim::theropod::IdleAnimation::update_skeleton(
4414                        &TheropodSkeleton::default(),
4415                        time,
4416                        state.state_time,
4417                        &mut state_animation_rate,
4418                        skeleton_attr,
4419                    ),
4420                };
4421                let target_bones = match &character {
4422                    CharacterState::ComboMelee2(s) => {
4423                        let timer = s.timer.as_secs_f32();
4424                        let current_strike = s.completed_strikes % s.static_data.strikes.len();
4425                        let strike_data = s.static_data.strikes[current_strike];
4426                        let progress = match s.stage_section {
4427                            StageSection::Buildup => {
4428                                timer / strike_data.buildup_duration.as_secs_f32()
4429                            },
4430                            StageSection::Action => {
4431                                timer / strike_data.swing_duration.as_secs_f32()
4432                            },
4433                            StageSection::Recover => {
4434                                timer / strike_data.recover_duration.as_secs_f32()
4435                            },
4436                            _ => 0.0,
4437                        };
4438
4439                        anim::theropod::ComboAnimation::update_skeleton(
4440                            &target_base,
4441                            (
4442                                ability_id,
4443                                s.stage_section,
4444                                current_strike,
4445                                time,
4446                                state.state_time,
4447                            ),
4448                            progress,
4449                            &mut state_animation_rate,
4450                            skeleton_attr,
4451                        )
4452                    },
4453                    CharacterState::DashMelee(s) => {
4454                        let stage_time = s.timer.as_secs_f32();
4455                        let stage_progress = match s.stage_section {
4456                            StageSection::Buildup => {
4457                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4458                            },
4459                            StageSection::Charge => {
4460                                stage_time / s.static_data.charge_duration.as_secs_f32()
4461                            },
4462                            StageSection::Action => {
4463                                stage_time / s.static_data.swing_duration.as_secs_f32()
4464                            },
4465                            StageSection::Recover => {
4466                                stage_time / s.static_data.recover_duration.as_secs_f32()
4467                            },
4468                            _ => 0.0,
4469                        };
4470                        anim::theropod::DashAnimation::update_skeleton(
4471                            &target_base,
4472                            (
4473                                rel_vel.magnitude(),
4474                                time,
4475                                Some(s.stage_section),
4476                                state.state_time,
4477                            ),
4478                            stage_progress,
4479                            &mut state_animation_rate,
4480                            skeleton_attr,
4481                        )
4482                    },
4483                    // TODO!
4484                    _ => target_base,
4485                };
4486
4487                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
4488                state.update(
4489                    renderer,
4490                    trail_mgr,
4491                    update_buf,
4492                    &common_params,
4493                    state_animation_rate,
4494                    model,
4495                    body,
4496                );
4497            },
4498            Body::Arthropod(body) => {
4499                let (model, skeleton_attr) = self.arthropod_model_cache.get_or_create_model(
4500                    renderer,
4501                    &mut self.atlas,
4502                    body,
4503                    inventory,
4504                    (),
4505                    tick,
4506                    viewpoint_camera_mode,
4507                    viewpoint_character_state,
4508                    slow_jobs,
4509                    None,
4510                );
4511
4512                let state = self
4513                    .states
4514                    .arthropod_states
4515                    .entry(entity)
4516                    .or_insert_with(|| {
4517                        FigureState::new(renderer, ArthropodSkeleton::default(), body)
4518                    });
4519
4520                // Average velocity relative to the current ground
4521                let rel_avg_vel = state.avg_vel - physics.ground_vel;
4522
4523                let (character, last_character) = match (character, last_character) {
4524                    (Some(c), Some(l)) => (c, l),
4525                    _ => return,
4526                };
4527
4528                if !character.same_variant(&last_character.0) {
4529                    state.state_time = 0.0;
4530                }
4531
4532                let target_base = match (
4533                    physics.on_ground.is_some(),
4534                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
4535                    physics.in_liquid().is_some(),                      // In water
4536                ) {
4537                    // Standing
4538                    (true, false, false) => anim::arthropod::IdleAnimation::update_skeleton(
4539                        &ArthropodSkeleton::default(),
4540                        time,
4541                        state.state_time,
4542                        &mut state_animation_rate,
4543                        skeleton_attr,
4544                    ),
4545                    // Running
4546                    (true, true, false) => anim::arthropod::RunAnimation::update_skeleton(
4547                        &ArthropodSkeleton::default(),
4548                        (
4549                            rel_vel,
4550                            // TODO: Update to use the quaternion.
4551                            ori * anim::vek::Vec3::<f32>::unit_y(),
4552                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4553                            time,
4554                            rel_avg_vel,
4555                            state.acc_vel,
4556                        ),
4557                        state.state_time,
4558                        &mut state_animation_rate,
4559                        skeleton_attr,
4560                    ),
4561                    // In air
4562                    (false, _, false) => anim::arthropod::JumpAnimation::update_skeleton(
4563                        &ArthropodSkeleton::default(),
4564                        (
4565                            rel_vel.magnitude(),
4566                            // TODO: Update to use the quaternion.
4567                            ori * anim::vek::Vec3::<f32>::unit_y(),
4568                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4569                            time,
4570                            rel_avg_vel,
4571                        ),
4572                        state.state_time,
4573                        &mut state_animation_rate,
4574                        skeleton_attr,
4575                    ),
4576                    _ => anim::arthropod::IdleAnimation::update_skeleton(
4577                        &ArthropodSkeleton::default(),
4578                        time,
4579                        state.state_time,
4580                        &mut state_animation_rate,
4581                        skeleton_attr,
4582                    ),
4583                };
4584                let target_bones = match &character {
4585                    CharacterState::BasicRanged(_)
4586                    | CharacterState::DashMelee(_)
4587                    | CharacterState::LeapMelee(_)
4588                    | CharacterState::LeapShockwave(_)
4589                    | CharacterState::SpriteSummon(_) => {
4590                        let timer = character.timer();
4591                        let stage_section = character.stage_section();
4592                        let durations = character.durations();
4593                        let progress = if let Some(((timer, stage_section), durations)) =
4594                            timer.zip(stage_section).zip(durations)
4595                        {
4596                            let base_dur = match stage_section {
4597                                StageSection::Buildup => durations.buildup,
4598                                StageSection::Charge => durations.charge,
4599                                StageSection::Movement => durations.movement,
4600                                StageSection::Action => durations.action,
4601                                StageSection::Recover => durations.recover,
4602                            };
4603                            if let Some(base_dur) = base_dur {
4604                                timer.as_secs_f32() / base_dur.as_secs_f32()
4605                            } else {
4606                                timer.as_secs_f32()
4607                            }
4608                        } else {
4609                            0.0
4610                        };
4611
4612                        anim::arthropod::BasicAction::update_skeleton(
4613                            &target_base,
4614                            anim::arthropod::BasicActionDependency {
4615                                ability_id,
4616                                stage_section,
4617                                global_time: time,
4618                                timer: state.state_time,
4619                            },
4620                            progress,
4621                            &mut state_animation_rate,
4622                            skeleton_attr,
4623                        )
4624                    },
4625                    CharacterState::ComboMelee2(_) => {
4626                        let timer = character.timer();
4627                        let stage_section = character.stage_section();
4628                        let durations = character.durations();
4629                        let progress = if let Some(((timer, stage_section), durations)) =
4630                            timer.zip(stage_section).zip(durations)
4631                        {
4632                            let base_dur = match stage_section {
4633                                StageSection::Buildup => durations.buildup,
4634                                StageSection::Charge => durations.charge,
4635                                StageSection::Movement => durations.movement,
4636                                StageSection::Action => durations.action,
4637                                StageSection::Recover => durations.recover,
4638                            };
4639                            if let Some(base_dur) = base_dur {
4640                                timer.as_secs_f32() / base_dur.as_secs_f32()
4641                            } else {
4642                                timer.as_secs_f32()
4643                            }
4644                        } else {
4645                            0.0
4646                        };
4647
4648                        let (current_action, max_actions) = match character {
4649                            CharacterState::ComboMelee2(s) => (
4650                                (s.completed_strikes % s.static_data.strikes.len()) as u32,
4651                                Some(s.static_data.strikes.len() as u32),
4652                            ),
4653                            _ => (0, None),
4654                        };
4655
4656                        anim::arthropod::MultiAction::update_skeleton(
4657                            &target_base,
4658                            anim::arthropod::MultiActionDependency {
4659                                ability_id,
4660                                stage_section,
4661                                current_action,
4662                                max_actions,
4663                                global_time: time,
4664                                timer: state.state_time,
4665                            },
4666                            progress,
4667                            &mut state_animation_rate,
4668                            skeleton_attr,
4669                        )
4670                    },
4671                    CharacterState::Stunned(s) => {
4672                        let stage_time = s.timer.as_secs_f32();
4673                        let stage_progress = match s.stage_section {
4674                            StageSection::Buildup => {
4675                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4676                            },
4677                            StageSection::Recover => {
4678                                stage_time / s.static_data.recover_duration.as_secs_f32()
4679                            },
4680                            _ => 0.0,
4681                        };
4682                        match s.static_data.poise_state {
4683                            PoiseState::Normal
4684                            | PoiseState::Interrupted
4685                            | PoiseState::Stunned
4686                            | PoiseState::Dazed
4687                            | PoiseState::KnockedDown => {
4688                                anim::arthropod::StunnedAnimation::update_skeleton(
4689                                    &target_base,
4690                                    (
4691                                        rel_vel.magnitude(),
4692                                        time,
4693                                        Some(s.stage_section),
4694                                        state.state_time,
4695                                    ),
4696                                    stage_progress,
4697                                    &mut state_animation_rate,
4698                                    skeleton_attr,
4699                                )
4700                            },
4701                        }
4702                    },
4703                    // TODO!
4704                    _ => target_base,
4705                };
4706
4707                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
4708                state.update(
4709                    renderer,
4710                    trail_mgr,
4711                    update_buf,
4712                    &common_params,
4713                    state_animation_rate,
4714                    model,
4715                    body,
4716                );
4717            },
4718            Body::Crustacean(body) => {
4719                let (model, skeleton_attr) = self.crustacean_model_cache.get_or_create_model(
4720                    renderer,
4721                    &mut self.atlas,
4722                    body,
4723                    inventory,
4724                    (),
4725                    tick,
4726                    viewpoint_camera_mode,
4727                    viewpoint_character_state,
4728                    slow_jobs,
4729                    None,
4730                );
4731
4732                let state = self
4733                    .states
4734                    .crustacean_states
4735                    .entry(entity)
4736                    .or_insert_with(|| {
4737                        FigureState::new(renderer, CrustaceanSkeleton::default(), body)
4738                    });
4739
4740                // Average velocity relative to the current ground
4741                let rel_avg_vel = state.avg_vel - physics.ground_vel;
4742
4743                let (character, last_character) = match (character, last_character) {
4744                    (Some(c), Some(l)) => (c, l),
4745                    _ => return,
4746                };
4747
4748                if !character.same_variant(&last_character.0) {
4749                    state.state_time = 0.0;
4750                }
4751
4752                let target_base = match (
4753                    physics.on_ground.is_some(),
4754                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
4755                    physics.in_liquid().is_some(),                      // In water
4756                ) {
4757                    // Standing
4758                    (true, false, false) => anim::crustacean::IdleAnimation::update_skeleton(
4759                        &CrustaceanSkeleton::default(),
4760                        time,
4761                        state.state_time,
4762                        &mut state_animation_rate,
4763                        skeleton_attr,
4764                    ),
4765                    // Running
4766                    (true, true, false) => anim::crustacean::RunAnimation::update_skeleton(
4767                        &CrustaceanSkeleton::default(),
4768                        (
4769                            rel_vel,
4770                            // TODO: Update to use the quaternion.
4771                            ori * anim::vek::Vec3::<f32>::unit_y(),
4772                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4773                            time,
4774                            rel_avg_vel,
4775                            state.acc_vel,
4776                        ),
4777                        state.state_time,
4778                        &mut state_animation_rate,
4779                        skeleton_attr,
4780                    ),
4781                    // In air
4782                    (false, _, false) => anim::crustacean::JumpAnimation::update_skeleton(
4783                        &CrustaceanSkeleton::default(),
4784                        (
4785                            rel_vel.magnitude(),
4786                            // TODO: Update to use the quaternion.
4787                            ori * anim::vek::Vec3::<f32>::unit_y(),
4788                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4789                            time,
4790                            rel_avg_vel,
4791                        ),
4792                        state.state_time,
4793                        &mut state_animation_rate,
4794                        skeleton_attr,
4795                    ),
4796                    //Swimming
4797                    (_, _, true) => anim::crustacean::SwimAnimation::update_skeleton(
4798                        &CrustaceanSkeleton::default(),
4799                        (
4800                            rel_vel,
4801                            // TODO: Update to use the quaternion.
4802                            ori * anim::vek::Vec3::<f32>::unit_y(),
4803                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
4804                            time,
4805                            rel_avg_vel,
4806                            state.acc_vel,
4807                        ),
4808                        state.state_time,
4809                        &mut state_animation_rate,
4810                        skeleton_attr,
4811                    ),
4812                };
4813                let target_bones = match &character {
4814                    CharacterState::ComboMelee2(s) => {
4815                        let timer = s.timer.as_secs_f32();
4816                        let current_strike = s.completed_strikes % s.static_data.strikes.len();
4817                        let strike_data = s.static_data.strikes[current_strike];
4818                        let progress = match s.stage_section {
4819                            StageSection::Buildup => {
4820                                timer / strike_data.buildup_duration.as_secs_f32()
4821                            },
4822                            StageSection::Action => {
4823                                timer / strike_data.swing_duration.as_secs_f32()
4824                            },
4825                            StageSection::Recover => {
4826                                timer / strike_data.recover_duration.as_secs_f32()
4827                            },
4828                            _ => 0.0,
4829                        };
4830
4831                        anim::crustacean::ComboAnimation::update_skeleton(
4832                            &target_base,
4833                            (
4834                                ability_id,
4835                                Some(s.stage_section),
4836                                Some(s.static_data.ability_info),
4837                                current_strike,
4838                                time,
4839                                rel_avg_vel,
4840                                state.state_time,
4841                            ),
4842                            progress,
4843                            &mut state_animation_rate,
4844                            skeleton_attr,
4845                        )
4846                    },
4847                    CharacterState::LeapMelee(s) => {
4848                        let stage_time = s.timer.as_secs_f32();
4849                        let stage_progress = match s.stage_section {
4850                            StageSection::Buildup => {
4851                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4852                            },
4853                            StageSection::Movement => {
4854                                stage_time / s.static_data.movement_duration.as_secs_f32()
4855                            },
4856                            StageSection::Action => {
4857                                stage_time / s.static_data.swing_duration.as_secs_f32()
4858                            },
4859                            StageSection::Recover => {
4860                                stage_time / s.static_data.recover_duration.as_secs_f32()
4861                            },
4862                            _ => 0.0,
4863                        };
4864                        anim::crustacean::LeapMeleeAnimation::update_skeleton(
4865                            &target_base,
4866                            (
4867                                ability_id,
4868                                rel_vel.magnitude(),
4869                                time,
4870                                Some(s.stage_section),
4871                                state.state_time,
4872                            ),
4873                            stage_progress,
4874                            &mut state_animation_rate,
4875                            skeleton_attr,
4876                        )
4877                    },
4878                    CharacterState::BasicSummon(s) => {
4879                        let stage_time = s.timer.as_secs_f32();
4880                        let stage_progress = match s.stage_section {
4881                            StageSection::Buildup => {
4882                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4883                            },
4884
4885                            StageSection::Action => {
4886                                stage_time / s.static_data.cast_duration.as_secs_f32()
4887                            },
4888                            StageSection::Recover => {
4889                                stage_time / s.static_data.recover_duration.as_secs_f32()
4890                            },
4891                            _ => 0.0,
4892                        };
4893
4894                        anim::crustacean::SummonAnimation::update_skeleton(
4895                            &target_base,
4896                            (
4897                                time,
4898                                Some(s.stage_section),
4899                                state.state_time,
4900                                look_dir,
4901                                physics.on_ground.is_some(),
4902                            ),
4903                            stage_progress,
4904                            &mut state_animation_rate,
4905                            skeleton_attr,
4906                        )
4907                    },
4908                    CharacterState::RiposteMelee(s) => {
4909                        let stage_time = s.timer.as_secs_f32();
4910                        let stage_progress = match s.stage_section {
4911                            StageSection::Buildup => {
4912                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4913                            },
4914                            StageSection::Action => {
4915                                stage_time / s.static_data.swing_duration.as_secs_f32()
4916                            },
4917                            StageSection::Recover => {
4918                                let recover_duration = if s.whiffed {
4919                                    s.static_data.whiffed_recover_duration.as_secs_f32()
4920                                } else {
4921                                    s.static_data.recover_duration.as_secs_f32()
4922                                };
4923                                stage_time / recover_duration
4924                            },
4925                            _ => 0.0,
4926                        };
4927                        anim::crustacean::RiposteMeleeAnimation::update_skeleton(
4928                            &target_base,
4929                            (ability_id, s.stage_section),
4930                            stage_progress,
4931                            &mut state_animation_rate,
4932                            skeleton_attr,
4933                        )
4934                    },
4935                    CharacterState::Stunned(s) => {
4936                        let stage_time = s.timer.as_secs_f32();
4937                        let stage_progress = match s.stage_section {
4938                            StageSection::Buildup => {
4939                                stage_time / s.static_data.buildup_duration.as_secs_f32()
4940                            },
4941                            StageSection::Recover => {
4942                                stage_time / s.static_data.recover_duration.as_secs_f32()
4943                            },
4944                            _ => 0.0,
4945                        };
4946                        match s.static_data.poise_state {
4947                            PoiseState::Normal
4948                            | PoiseState::Interrupted
4949                            | PoiseState::Stunned
4950                            | PoiseState::Dazed
4951                            | PoiseState::KnockedDown => {
4952                                anim::crustacean::StunnedAnimation::update_skeleton(
4953                                    &target_base,
4954                                    (
4955                                        rel_vel.magnitude(),
4956                                        time,
4957                                        Some(s.stage_section),
4958                                        state.state_time,
4959                                    ),
4960                                    stage_progress,
4961                                    &mut state_animation_rate,
4962                                    skeleton_attr,
4963                                )
4964                            },
4965                        }
4966                    },
4967                    // TODO!
4968                    _ => target_base,
4969                };
4970
4971                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
4972                state.update(
4973                    renderer,
4974                    trail_mgr,
4975                    update_buf,
4976                    &common_params,
4977                    state_animation_rate,
4978                    model,
4979                    body,
4980                );
4981            },
4982            Body::BirdLarge(body) => {
4983                let (model, skeleton_attr) = self.bird_large_model_cache.get_or_create_model(
4984                    renderer,
4985                    &mut self.atlas,
4986                    body,
4987                    inventory,
4988                    (),
4989                    tick,
4990                    viewpoint_camera_mode,
4991                    viewpoint_character_state,
4992                    slow_jobs,
4993                    None,
4994                );
4995
4996                let state = self
4997                    .states
4998                    .bird_large_states
4999                    .entry(entity)
5000                    .or_insert_with(|| {
5001                        FigureState::new(renderer, BirdLargeSkeleton::default(), body)
5002                    });
5003
5004                // Average velocity relative to the current ground
5005                let rel_avg_vel = state.avg_vel - physics.ground_vel;
5006
5007                let (character, last_character) = match (character, last_character) {
5008                    (Some(c), Some(l)) => (c, l),
5009                    _ => return,
5010                };
5011
5012                if !character.same_variant(&last_character.0) {
5013                    state.state_time = 0.0;
5014                }
5015
5016                let target_base = match (
5017                    physics.on_ground.is_some(),
5018                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
5019                    physics.in_liquid().is_some(),                      // In water
5020                ) {
5021                    // Standing
5022                    (true, false, false) => anim::bird_large::IdleAnimation::update_skeleton(
5023                        &BirdLargeSkeleton::default(),
5024                        time,
5025                        state.state_time,
5026                        &mut state_animation_rate,
5027                        skeleton_attr,
5028                    ),
5029                    // Running
5030                    (true, true, false) => anim::bird_large::RunAnimation::update_skeleton(
5031                        &BirdLargeSkeleton::default(),
5032                        (
5033                            rel_vel,
5034                            // TODO: Update to use the quaternion.
5035                            ori * anim::vek::Vec3::<f32>::unit_y(),
5036                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5037                            rel_avg_vel,
5038                            state.acc_vel,
5039                        ),
5040                        state.state_time,
5041                        &mut state_animation_rate,
5042                        skeleton_attr,
5043                    ),
5044                    // In air
5045                    (false, _, false) => anim::bird_large::FlyAnimation::update_skeleton(
5046                        &BirdLargeSkeleton::default(),
5047                        (
5048                            rel_vel,
5049                            // TODO: Update to use the quaternion.
5050                            ori * anim::vek::Vec3::<f32>::unit_y(),
5051                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5052                        ),
5053                        state.state_time,
5054                        &mut state_animation_rate,
5055                        skeleton_attr,
5056                    ),
5057                    // Swim
5058                    (_, true, _) => anim::bird_large::SwimAnimation::update_skeleton(
5059                        &BirdLargeSkeleton::default(),
5060                        time,
5061                        state.state_time,
5062                        &mut state_animation_rate,
5063                        skeleton_attr,
5064                    ),
5065                    // TODO!
5066                    _ => anim::bird_large::IdleAnimation::update_skeleton(
5067                        &BirdLargeSkeleton::default(),
5068                        time,
5069                        state.state_time,
5070                        &mut state_animation_rate,
5071                        skeleton_attr,
5072                    ),
5073                };
5074                let target_bones = match &character {
5075                    CharacterState::Sit => anim::bird_large::FeedAnimation::update_skeleton(
5076                        &target_base,
5077                        time,
5078                        state.state_time,
5079                        &mut state_animation_rate,
5080                        skeleton_attr,
5081                    ),
5082                    CharacterState::BasicBeam(s) => {
5083                        let stage_time = s.timer.as_secs_f32();
5084                        let stage_progress = match s.stage_section {
5085                            StageSection::Buildup => {
5086                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5087                            },
5088                            StageSection::Action => s.timer.as_secs_f32(),
5089                            StageSection::Recover => {
5090                                stage_time / s.static_data.recover_duration.as_secs_f32()
5091                            },
5092                            _ => 0.0,
5093                        };
5094                        anim::bird_large::BreatheAnimation::update_skeleton(
5095                            &target_base,
5096                            (
5097                                rel_vel,
5098                                time,
5099                                ori * anim::vek::Vec3::<f32>::unit_y(),
5100                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5101                                Some(s.stage_section),
5102                                state.state_time,
5103                                look_dir,
5104                                physics.on_ground.is_some(),
5105                                ability_id,
5106                            ),
5107                            stage_progress,
5108                            &mut state_animation_rate,
5109                            skeleton_attr,
5110                        )
5111                    },
5112                    CharacterState::ComboMelee2(s) => {
5113                        let timer = s.timer.as_secs_f32();
5114                        let current_strike = s.completed_strikes % s.static_data.strikes.len();
5115                        let strike_data = s.static_data.strikes[current_strike];
5116                        let progress = match s.stage_section {
5117                            StageSection::Buildup => {
5118                                timer / strike_data.buildup_duration.as_secs_f32()
5119                            },
5120                            StageSection::Action => {
5121                                timer / strike_data.swing_duration.as_secs_f32()
5122                            },
5123                            StageSection::Recover => {
5124                                timer / strike_data.recover_duration.as_secs_f32()
5125                            },
5126                            _ => 0.0,
5127                        };
5128
5129                        anim::bird_large::ComboAnimation::update_skeleton(
5130                            &target_base,
5131                            (
5132                                ability_id,
5133                                Some(s.stage_section),
5134                                current_strike,
5135                                time,
5136                                state.state_time,
5137                                ori * anim::vek::Vec3::<f32>::unit_y(),
5138                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5139                                physics.on_ground.is_some(),
5140                            ),
5141                            progress,
5142                            &mut state_animation_rate,
5143                            skeleton_attr,
5144                        )
5145                    },
5146                    CharacterState::BasicRanged(s) => {
5147                        let stage_time = s.timer.as_secs_f32();
5148
5149                        let stage_progress = match s.stage_section {
5150                            StageSection::Buildup => {
5151                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5152                            },
5153                            StageSection::Recover => {
5154                                stage_time / s.static_data.recover_duration.as_secs_f32()
5155                            },
5156
5157                            _ => 0.0,
5158                        };
5159                        anim::bird_large::ShootAnimation::update_skeleton(
5160                            &target_base,
5161                            (
5162                                rel_vel,
5163                                time,
5164                                Some(s.stage_section),
5165                                state.state_time,
5166                                look_dir,
5167                                physics.on_ground.is_some(),
5168                                ability_id,
5169                            ),
5170                            stage_progress,
5171                            &mut state_animation_rate,
5172                            skeleton_attr,
5173                        )
5174                    },
5175                    CharacterState::RepeaterRanged(s) => {
5176                        let stage_time = s.timer.as_secs_f32();
5177
5178                        let stage_progress = match s.stage_section {
5179                            StageSection::Buildup => {
5180                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5181                            },
5182                            StageSection::Recover => {
5183                                stage_time / s.static_data.recover_duration.as_secs_f32()
5184                            },
5185
5186                            _ => 0.0,
5187                        };
5188                        anim::bird_large::ShootAnimation::update_skeleton(
5189                            &target_base,
5190                            (
5191                                rel_vel,
5192                                time,
5193                                Some(s.stage_section),
5194                                state.state_time,
5195                                look_dir,
5196                                physics.on_ground.is_some(),
5197                                ability_id,
5198                            ),
5199                            stage_progress,
5200                            &mut state_animation_rate,
5201                            skeleton_attr,
5202                        )
5203                    },
5204                    CharacterState::Shockwave(s) => {
5205                        let stage_time = s.timer.as_secs_f32();
5206                        let stage_progress = match s.stage_section {
5207                            StageSection::Buildup => {
5208                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5209                            },
5210                            StageSection::Action => {
5211                                stage_time / s.static_data.swing_duration.as_secs_f32()
5212                            },
5213                            StageSection::Recover => {
5214                                stage_time / s.static_data.recover_duration.as_secs_f32()
5215                            },
5216                            _ => 0.0,
5217                        };
5218                        anim::bird_large::ShockwaveAnimation::update_skeleton(
5219                            &target_base,
5220                            (Some(s.stage_section), physics.on_ground.is_some()),
5221                            stage_progress,
5222                            &mut state_animation_rate,
5223                            skeleton_attr,
5224                        )
5225                    },
5226                    CharacterState::BasicAura(s) => {
5227                        let stage_time = s.timer.as_secs_f32();
5228                        let stage_progress = match s.stage_section {
5229                            StageSection::Buildup => {
5230                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5231                            },
5232                            StageSection::Action => {
5233                                stage_time / s.static_data.cast_duration.as_secs_f32()
5234                            },
5235                            StageSection::Recover => {
5236                                stage_time / s.static_data.recover_duration.as_secs_f32()
5237                            },
5238                            _ => 0.0,
5239                        };
5240                        anim::bird_large::AuraAnimation::update_skeleton(
5241                            &target_base,
5242                            (Some(s.stage_section), physics.on_ground.is_some()),
5243                            stage_progress,
5244                            &mut state_animation_rate,
5245                            skeleton_attr,
5246                        )
5247                    },
5248                    CharacterState::SelfBuff(s) => {
5249                        let stage_time = s.timer.as_secs_f32();
5250                        let stage_progress = match s.stage_section {
5251                            StageSection::Buildup => {
5252                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5253                            },
5254                            StageSection::Action => {
5255                                stage_time / s.static_data.cast_duration.as_secs_f32()
5256                            },
5257                            StageSection::Recover => {
5258                                stage_time / s.static_data.recover_duration.as_secs_f32()
5259                            },
5260                            _ => 0.0,
5261                        };
5262                        anim::bird_large::SelfBuffAnimation::update_skeleton(
5263                            &target_base,
5264                            (Some(s.stage_section), physics.on_ground.is_some()),
5265                            stage_progress,
5266                            &mut state_animation_rate,
5267                            skeleton_attr,
5268                        )
5269                    },
5270                    CharacterState::BasicSummon(s) => {
5271                        let stage_time = s.timer.as_secs_f32();
5272                        let stage_progress = match s.stage_section {
5273                            StageSection::Buildup => {
5274                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5275                            },
5276
5277                            StageSection::Action => {
5278                                stage_time / s.static_data.cast_duration.as_secs_f32()
5279                            },
5280                            StageSection::Recover => {
5281                                stage_time / s.static_data.recover_duration.as_secs_f32()
5282                            },
5283                            _ => 0.0,
5284                        };
5285
5286                        anim::bird_large::SummonAnimation::update_skeleton(
5287                            &target_base,
5288                            (
5289                                time,
5290                                Some(s.stage_section),
5291                                state.state_time,
5292                                look_dir,
5293                                physics.on_ground.is_some(),
5294                            ),
5295                            stage_progress,
5296                            &mut state_animation_rate,
5297                            skeleton_attr,
5298                        )
5299                    },
5300                    CharacterState::DashMelee(s) => {
5301                        let stage_time = s.timer.as_secs_f32();
5302                        let stage_progress = match s.stage_section {
5303                            StageSection::Buildup => {
5304                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5305                            },
5306                            StageSection::Charge => {
5307                                stage_time / s.static_data.charge_duration.as_secs_f32()
5308                            },
5309                            StageSection::Action => {
5310                                stage_time / s.static_data.swing_duration.as_secs_f32()
5311                            },
5312                            StageSection::Recover => {
5313                                stage_time / s.static_data.recover_duration.as_secs_f32()
5314                            },
5315                            _ => 0.0,
5316                        };
5317                        anim::bird_large::DashAnimation::update_skeleton(
5318                            &target_base,
5319                            (
5320                                rel_vel,
5321                                // TODO: Update to use the quaternion.
5322                                ori * anim::vek::Vec3::<f32>::unit_y(),
5323                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5324                                state.acc_vel,
5325                                Some(s.stage_section),
5326                                time,
5327                                state.state_time,
5328                            ),
5329                            stage_progress,
5330                            &mut state_animation_rate,
5331                            skeleton_attr,
5332                        )
5333                    },
5334                    CharacterState::Stunned(s) => {
5335                        let stage_time = s.timer.as_secs_f32();
5336                        let stage_progress = match s.stage_section {
5337                            StageSection::Buildup => {
5338                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5339                            },
5340                            StageSection::Recover => {
5341                                stage_time / s.static_data.recover_duration.as_secs_f32()
5342                            },
5343                            _ => 0.0,
5344                        };
5345                        match s.static_data.poise_state {
5346                            PoiseState::Normal
5347                            | PoiseState::Interrupted
5348                            | PoiseState::Stunned
5349                            | PoiseState::Dazed
5350                            | PoiseState::KnockedDown => {
5351                                anim::bird_large::StunnedAnimation::update_skeleton(
5352                                    &target_base,
5353                                    (time, Some(s.stage_section), state.state_time),
5354                                    stage_progress,
5355                                    &mut state_animation_rate,
5356                                    skeleton_attr,
5357                                )
5358                            },
5359                        }
5360                    },
5361                    // TODO!
5362                    _ => target_base,
5363                };
5364
5365                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
5366                state.update(
5367                    renderer,
5368                    trail_mgr,
5369                    update_buf,
5370                    &common_params,
5371                    state_animation_rate,
5372                    model,
5373                    body,
5374                );
5375            },
5376            Body::FishSmall(body) => {
5377                let (model, skeleton_attr) = self.fish_small_model_cache.get_or_create_model(
5378                    renderer,
5379                    &mut self.atlas,
5380                    body,
5381                    inventory,
5382                    (),
5383                    tick,
5384                    viewpoint_camera_mode,
5385                    viewpoint_character_state,
5386                    slow_jobs,
5387                    None,
5388                );
5389
5390                let state = self
5391                    .states
5392                    .fish_small_states
5393                    .entry(entity)
5394                    .or_insert_with(|| {
5395                        FigureState::new(renderer, FishSmallSkeleton::default(), body)
5396                    });
5397
5398                // Average velocity relative to the current ground
5399                let rel_avg_vel = state.avg_vel - physics.ground_vel;
5400
5401                let (character, last_character) = match (character, last_character) {
5402                    (Some(c), Some(l)) => (c, l),
5403                    _ => return,
5404                };
5405
5406                if !character.same_variant(&last_character.0) {
5407                    state.state_time = 0.0;
5408                }
5409
5410                let target_base = match (
5411                    physics.on_ground.is_some(),
5412                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
5413                    physics.in_liquid().is_some(),                      // In water
5414                ) {
5415                    // Idle
5416                    (_, false, _) => anim::fish_small::IdleAnimation::update_skeleton(
5417                        &FishSmallSkeleton::default(),
5418                        (
5419                            rel_vel,
5420                            // TODO: Update to use the quaternion.
5421                            ori * anim::vek::Vec3::<f32>::unit_y(),
5422                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5423                            time,
5424                            rel_avg_vel,
5425                        ),
5426                        state.state_time,
5427                        &mut state_animation_rate,
5428                        skeleton_attr,
5429                    ),
5430                    // Swim
5431                    (_, true, _) => anim::fish_small::SwimAnimation::update_skeleton(
5432                        &FishSmallSkeleton::default(),
5433                        (
5434                            rel_vel,
5435                            // TODO: Update to use the quaternion.
5436                            ori * anim::vek::Vec3::<f32>::unit_y(),
5437                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5438                            time,
5439                            rel_avg_vel,
5440                            state.acc_vel,
5441                        ),
5442                        state.state_time,
5443                        &mut state_animation_rate,
5444                        skeleton_attr,
5445                    ),
5446                };
5447
5448                state.skeleton = Lerp::lerp(&state.skeleton, &target_base, dt_lerp);
5449                state.update(
5450                    renderer,
5451                    trail_mgr,
5452                    update_buf,
5453                    &common_params,
5454                    state_animation_rate,
5455                    model,
5456                    body,
5457                );
5458            },
5459            Body::BipedLarge(body) => {
5460                let (model, skeleton_attr) = self.biped_large_model_cache.get_or_create_model(
5461                    renderer,
5462                    &mut self.atlas,
5463                    body,
5464                    inventory,
5465                    (),
5466                    tick,
5467                    viewpoint_camera_mode,
5468                    viewpoint_character_state,
5469                    slow_jobs,
5470                    None,
5471                );
5472
5473                let state = self
5474                    .states
5475                    .biped_large_states
5476                    .entry(entity)
5477                    .or_insert_with(|| {
5478                        FigureState::new(renderer, BipedLargeSkeleton::default(), body)
5479                    });
5480
5481                // Average velocity relative to the current ground
5482                let rel_avg_vel = state.avg_vel - physics.ground_vel;
5483
5484                let (character, last_character) = match (character, last_character) {
5485                    (Some(c), Some(l)) => (c, l),
5486                    _ => return,
5487                };
5488
5489                if !character.same_variant(&last_character.0) {
5490                    state.state_time = 0.0;
5491                }
5492
5493                let target_base = match (
5494                    physics.on_ground.is_some(),
5495                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
5496                    physics.in_liquid().is_some(),                      // In water
5497                ) {
5498                    // Running
5499                    (true, true, false) => anim::biped_large::RunAnimation::update_skeleton(
5500                        &BipedLargeSkeleton::default(),
5501                        (
5502                            active_tool_kind,
5503                            second_tool_kind,
5504                            rel_vel,
5505                            // TODO: Update to use the quaternion.
5506                            ori * anim::vek::Vec3::<f32>::unit_y(),
5507                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5508                            time,
5509                            rel_avg_vel,
5510                            state.acc_vel,
5511                        ),
5512                        state.state_time,
5513                        &mut state_animation_rate,
5514                        skeleton_attr,
5515                    ),
5516                    // In air
5517                    (false, _, false) => anim::biped_large::JumpAnimation::update_skeleton(
5518                        &BipedLargeSkeleton::default(),
5519                        (active_tool_kind, second_tool_kind, time),
5520                        state.state_time,
5521                        &mut state_animation_rate,
5522                        skeleton_attr,
5523                    ),
5524                    _ => anim::biped_large::IdleAnimation::update_skeleton(
5525                        &BipedLargeSkeleton::default(),
5526                        (active_tool_kind, second_tool_kind, time),
5527                        state.state_time,
5528                        &mut state_animation_rate,
5529                        skeleton_attr,
5530                    ),
5531                };
5532                let target_bones = match &character {
5533                    CharacterState::Equipping { .. } => {
5534                        anim::biped_large::EquipAnimation::update_skeleton(
5535                            &target_base,
5536                            (
5537                                active_tool_kind,
5538                                second_tool_kind,
5539                                rel_vel.magnitude(),
5540                                time,
5541                            ),
5542                            state.state_time,
5543                            &mut state_animation_rate,
5544                            skeleton_attr,
5545                        )
5546                    },
5547                    CharacterState::Wielding { .. } => {
5548                        anim::biped_large::WieldAnimation::update_skeleton(
5549                            &target_base,
5550                            (
5551                                (active_tool_kind, active_tool_spec),
5552                                (second_tool_kind, second_tool_spec),
5553                                rel_vel,
5554                                time,
5555                                state.acc_vel,
5556                            ),
5557                            state.state_time,
5558                            &mut state_animation_rate,
5559                            skeleton_attr,
5560                        )
5561                    },
5562                    CharacterState::ChargedMelee(s) => {
5563                        let stage_time = s.timer.as_secs_f32();
5564
5565                        let stage_progress = match s.stage_section {
5566                            StageSection::Buildup => {
5567                                if let Some((dur, _)) = s.static_data.buildup_strike {
5568                                    stage_time / dur.as_secs_f32()
5569                                } else {
5570                                    stage_time
5571                                }
5572                            },
5573                            StageSection::Charge => {
5574                                stage_time / s.static_data.charge_duration.as_secs_f32()
5575                            },
5576                            StageSection::Action => {
5577                                stage_time / s.static_data.swing_duration.as_secs_f32()
5578                            },
5579                            StageSection::Recover => {
5580                                stage_time / s.static_data.recover_duration.as_secs_f32()
5581                            },
5582                            _ => 0.0,
5583                        };
5584
5585                        anim::biped_large::ChargeMeleeAnimation::update_skeleton(
5586                            &target_base,
5587                            (
5588                                active_tool_kind,
5589                                (second_tool_kind, second_tool_spec),
5590                                rel_vel,
5591                                time,
5592                                Some(s.stage_section),
5593                                state.acc_vel,
5594                                ability_id,
5595                            ),
5596                            stage_progress,
5597                            &mut state_animation_rate,
5598                            skeleton_attr,
5599                        )
5600                    },
5601                    CharacterState::SelfBuff(s) => {
5602                        let stage_time = s.timer.as_secs_f32();
5603
5604                        let stage_progress = match s.stage_section {
5605                            StageSection::Buildup => {
5606                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5607                            },
5608                            StageSection::Action => {
5609                                stage_time / s.static_data.cast_duration.as_secs_f32()
5610                            },
5611                            StageSection::Recover => {
5612                                stage_time / s.static_data.recover_duration.as_secs_f32()
5613                            },
5614                            _ => 0.0,
5615                        };
5616
5617                        anim::biped_large::SelfBuffAnimation::update_skeleton(
5618                            &target_base,
5619                            (
5620                                (active_tool_kind, active_tool_spec),
5621                                (second_tool_kind, second_tool_spec),
5622                                rel_vel,
5623                                time,
5624                                Some(s.stage_section),
5625                                state.acc_vel,
5626                            ),
5627                            stage_progress,
5628                            &mut state_animation_rate,
5629                            skeleton_attr,
5630                        )
5631                    },
5632                    CharacterState::BasicMelee(s) => {
5633                        let stage_time = s.timer.as_secs_f32();
5634
5635                        let stage_progress = match s.stage_section {
5636                            StageSection::Buildup => {
5637                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5638                            },
5639                            StageSection::Action => {
5640                                stage_time / s.static_data.swing_duration.as_secs_f32()
5641                            },
5642                            StageSection::Recover => {
5643                                stage_time / s.static_data.recover_duration.as_secs_f32()
5644                            },
5645                            _ => 0.0,
5646                        };
5647
5648                        anim::biped_large::AlphaAnimation::update_skeleton(
5649                            &target_base,
5650                            (
5651                                active_tool_kind,
5652                                (second_tool_kind, second_tool_spec),
5653                                rel_vel,
5654                                time,
5655                                Some(s.stage_section),
5656                                state.acc_vel,
5657                                state.state_time,
5658                                ability_id,
5659                            ),
5660                            stage_progress,
5661                            &mut state_animation_rate,
5662                            skeleton_attr,
5663                        )
5664                    },
5665                    CharacterState::ComboMelee2(s) => {
5666                        let timer = s.timer.as_secs_f32();
5667                        let current_strike = s.completed_strikes % s.static_data.strikes.len();
5668                        let strike_data = s.static_data.strikes[current_strike];
5669                        let progress = match s.stage_section {
5670                            StageSection::Buildup => {
5671                                timer / strike_data.buildup_duration.as_secs_f32()
5672                            },
5673                            StageSection::Action => {
5674                                timer / strike_data.swing_duration.as_secs_f32()
5675                            },
5676                            StageSection::Recover => {
5677                                timer / strike_data.recover_duration.as_secs_f32()
5678                            },
5679                            _ => 0.0,
5680                        };
5681
5682                        anim::biped_large::ComboAnimation::update_skeleton(
5683                            &target_base,
5684                            (
5685                                ability_id,
5686                                Some(s.stage_section),
5687                                Some(s.static_data.ability_info),
5688                                current_strike,
5689                                move_dir,
5690                                rel_vel,
5691                                state.acc_vel,
5692                            ),
5693                            progress,
5694                            &mut state_animation_rate,
5695                            skeleton_attr,
5696                        )
5697                    },
5698                    CharacterState::BasicRanged(s) => {
5699                        let stage_time = s.timer.as_secs_f32();
5700
5701                        let stage_progress = match s.stage_section {
5702                            StageSection::Buildup => {
5703                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5704                            },
5705                            StageSection::Recover => {
5706                                stage_time / s.static_data.recover_duration.as_secs_f32()
5707                            },
5708
5709                            _ => 0.0,
5710                        };
5711
5712                        anim::biped_large::ShootAnimation::update_skeleton(
5713                            &target_base,
5714                            (
5715                                active_tool_kind,
5716                                (second_tool_kind, second_tool_spec),
5717                                rel_vel,
5718                                // TODO: Update to use the quaternion.
5719                                ori * anim::vek::Vec3::<f32>::unit_y(),
5720                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5721                                time,
5722                                Some(s.stage_section),
5723                                state.acc_vel,
5724                                ability_id,
5725                            ),
5726                            stage_progress,
5727                            &mut state_animation_rate,
5728                            skeleton_attr,
5729                        )
5730                    },
5731                    CharacterState::RepeaterRanged(s) => {
5732                        let stage_time = s.timer.as_secs_f32();
5733
5734                        let stage_progress = match s.stage_section {
5735                            StageSection::Buildup => {
5736                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5737                            },
5738                            StageSection::Recover => {
5739                                stage_time / s.static_data.recover_duration.as_secs_f32()
5740                            },
5741
5742                            _ => 0.0,
5743                        };
5744                        anim::biped_large::ShootAnimation::update_skeleton(
5745                            &target_base,
5746                            (
5747                                active_tool_kind,
5748                                (second_tool_kind, second_tool_spec),
5749                                rel_vel,
5750                                // TODO: Update to use the quaternion.
5751                                ori * anim::vek::Vec3::<f32>::unit_y(),
5752                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5753                                time,
5754                                Some(s.stage_section),
5755                                state.acc_vel,
5756                                ability_id,
5757                            ),
5758                            stage_progress,
5759                            &mut state_animation_rate,
5760                            skeleton_attr,
5761                        )
5762                    },
5763                    CharacterState::Stunned(s) => {
5764                        let stage_time = s.timer.as_secs_f32();
5765                        let stage_progress = match s.stage_section {
5766                            StageSection::Buildup => {
5767                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5768                            },
5769                            StageSection::Recover => {
5770                                stage_time / s.static_data.recover_duration.as_secs_f32()
5771                            },
5772                            _ => 0.0,
5773                        };
5774                        match s.static_data.poise_state {
5775                            PoiseState::Normal
5776                            | PoiseState::Interrupted
5777                            | PoiseState::Stunned
5778                            | PoiseState::Dazed
5779                            | PoiseState::KnockedDown => {
5780                                anim::biped_large::StunnedAnimation::update_skeleton(
5781                                    &target_base,
5782                                    (
5783                                        (active_tool_kind, active_tool_spec),
5784                                        rel_vel,
5785                                        state.acc_vel,
5786                                        Some(s.stage_section),
5787                                    ),
5788                                    stage_progress,
5789                                    &mut state_animation_rate,
5790                                    skeleton_attr,
5791                                )
5792                            },
5793                        }
5794                    },
5795                    CharacterState::Blink(s) => {
5796                        let stage_time = s.timer.as_secs_f32();
5797
5798                        let stage_progress = match s.stage_section {
5799                            StageSection::Buildup => {
5800                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5801                            },
5802                            StageSection::Recover => {
5803                                stage_time / s.static_data.recover_duration.as_secs_f32()
5804                            },
5805
5806                            _ => 0.0,
5807                        };
5808
5809                        anim::biped_large::BlinkAnimation::update_skeleton(
5810                            &target_base,
5811                            (
5812                                active_tool_kind,
5813                                second_tool_kind,
5814                                rel_vel,
5815                                time,
5816                                Some(s.stage_section),
5817                                state.acc_vel,
5818                            ),
5819                            stage_progress,
5820                            &mut state_animation_rate,
5821                            skeleton_attr,
5822                        )
5823                    },
5824                    CharacterState::ChargedRanged(s) => {
5825                        let stage_time = s.timer.as_secs_f32();
5826
5827                        let stage_progress = match s.stage_section {
5828                            StageSection::Buildup => {
5829                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5830                            },
5831                            StageSection::Recover => {
5832                                stage_time / s.static_data.recover_duration.as_secs_f32()
5833                            },
5834
5835                            _ => 0.0,
5836                        };
5837
5838                        anim::biped_large::ShootAnimation::update_skeleton(
5839                            &target_base,
5840                            (
5841                                active_tool_kind,
5842                                (second_tool_kind, second_tool_spec),
5843                                rel_vel,
5844                                // TODO: Update to use the quaternion.
5845                                ori * anim::vek::Vec3::<f32>::unit_y(),
5846                                state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
5847                                time,
5848                                Some(s.stage_section),
5849                                state.acc_vel,
5850                                ability_id,
5851                            ),
5852                            stage_progress,
5853                            &mut state_animation_rate,
5854                            skeleton_attr,
5855                        )
5856                    },
5857                    CharacterState::DashMelee(s) => {
5858                        let stage_time = s.timer.as_secs_f32();
5859                        let stage_progress = match s.stage_section {
5860                            StageSection::Buildup => {
5861                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5862                            },
5863                            StageSection::Charge => {
5864                                stage_time / s.static_data.charge_duration.as_secs_f32()
5865                            },
5866                            StageSection::Action => {
5867                                stage_time / s.static_data.swing_duration.as_secs_f32()
5868                            },
5869                            StageSection::Recover => {
5870                                stage_time / s.static_data.recover_duration.as_secs_f32()
5871                            },
5872                            _ => 0.0,
5873                        };
5874                        anim::biped_large::DashAnimation::update_skeleton(
5875                            &target_base,
5876                            (
5877                                active_tool_kind,
5878                                (second_tool_kind, second_tool_spec),
5879                                rel_vel,
5880                                time,
5881                                Some(s.stage_section),
5882                                state.acc_vel,
5883                                ability_id,
5884                            ),
5885                            stage_progress,
5886                            &mut state_animation_rate,
5887                            skeleton_attr,
5888                        )
5889                    },
5890                    CharacterState::RapidMelee(s) => {
5891                        let stage_time = s.timer.as_secs_f32();
5892                        let stage_progress = match s.stage_section {
5893                            StageSection::Buildup => {
5894                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5895                            },
5896
5897                            StageSection::Action => {
5898                                stage_time / s.static_data.swing_duration.as_secs_f32()
5899                            },
5900                            StageSection::Recover => {
5901                                stage_time / s.static_data.recover_duration.as_secs_f32()
5902                            },
5903                            _ => 0.0,
5904                        };
5905
5906                        anim::biped_large::RapidMeleeAnimation::update_skeleton(
5907                            &target_base,
5908                            (
5909                                active_tool_kind,
5910                                second_tool_kind,
5911                                rel_vel,
5912                                time,
5913                                Some(s.stage_section),
5914                                state.acc_vel,
5915                                ability_id,
5916                            ),
5917                            stage_progress,
5918                            &mut state_animation_rate,
5919                            skeleton_attr,
5920                        )
5921                    },
5922                    CharacterState::BasicSummon(s) => {
5923                        let stage_time = s.timer.as_secs_f32();
5924                        let stage_progress = match s.stage_section {
5925                            StageSection::Buildup => {
5926                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5927                            },
5928
5929                            StageSection::Action => {
5930                                stage_time / s.static_data.cast_duration.as_secs_f32()
5931                            },
5932                            StageSection::Recover => {
5933                                stage_time / s.static_data.recover_duration.as_secs_f32()
5934                            },
5935                            _ => 0.0,
5936                        };
5937
5938                        anim::biped_large::SummonAnimation::update_skeleton(
5939                            &target_base,
5940                            (
5941                                active_tool_kind,
5942                                (second_tool_kind, second_tool_spec),
5943                                rel_vel,
5944                                time,
5945                                Some(s.stage_section),
5946                                state.acc_vel,
5947                                ability_id,
5948                            ),
5949                            stage_progress,
5950                            &mut state_animation_rate,
5951                            skeleton_attr,
5952                        )
5953                    },
5954                    CharacterState::LeapExplosionShockwave(s) => {
5955                        let stage_time = s.timer.as_secs_f32();
5956                        let stage_progress = match s.stage_section {
5957                            StageSection::Buildup => {
5958                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5959                            },
5960                            StageSection::Movement => {
5961                                stage_time / s.static_data.buildup_duration.as_secs_f32()
5962                            },
5963                            StageSection::Action => {
5964                                stage_time / s.static_data.swing_duration.as_secs_f32()
5965                            },
5966                            StageSection::Recover => {
5967                                stage_time / s.static_data.recover_duration.as_secs_f32()
5968                            },
5969                            _ => 0.0,
5970                        };
5971
5972                        anim::biped_large::LeapExplosionShockAnimation::update_skeleton(
5973                            &target_base,
5974                            (Some(s.stage_section), ability_id),
5975                            stage_progress,
5976                            &mut state_animation_rate,
5977                            skeleton_attr,
5978                        )
5979                    },
5980                    CharacterState::LeapMelee(s) => {
5981                        let stage_progress = match active_tool_kind {
5982                            Some(ToolKind::Sword | ToolKind::Axe | ToolKind::Hammer) => {
5983                                let stage_time = s.timer.as_secs_f32();
5984                                match s.stage_section {
5985                                    StageSection::Buildup => {
5986                                        stage_time / s.static_data.buildup_duration.as_secs_f32()
5987                                    },
5988                                    StageSection::Movement => {
5989                                        stage_time / s.static_data.movement_duration.as_secs_f32()
5990                                    },
5991                                    StageSection::Action => {
5992                                        stage_time / s.static_data.swing_duration.as_secs_f32()
5993                                    },
5994                                    StageSection::Recover => {
5995                                        stage_time / s.static_data.recover_duration.as_secs_f32()
5996                                    },
5997                                    _ => 0.0,
5998                                }
5999                            },
6000                            _ => state.state_time,
6001                        };
6002
6003                        anim::biped_large::LeapAnimation::update_skeleton(
6004                            &target_base,
6005                            (
6006                                active_tool_kind,
6007                                second_tool_kind,
6008                                rel_vel,
6009                                time,
6010                                Some(s.stage_section),
6011                                ability_id,
6012                            ),
6013                            stage_progress,
6014                            &mut state_animation_rate,
6015                            skeleton_attr,
6016                        )
6017                    },
6018                    CharacterState::LeapShockwave(s) => {
6019                        let stage_time = s.timer.as_secs_f32();
6020                        let stage_progress = match s.stage_section {
6021                            StageSection::Buildup => {
6022                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6023                            },
6024                            StageSection::Movement => {
6025                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6026                            },
6027                            StageSection::Action => {
6028                                stage_time / s.static_data.swing_duration.as_secs_f32()
6029                            },
6030                            StageSection::Recover => {
6031                                stage_time / s.static_data.recover_duration.as_secs_f32()
6032                            },
6033                            _ => 0.0,
6034                        };
6035
6036                        anim::biped_large::LeapShockAnimation::update_skeleton(
6037                            &target_base,
6038                            (
6039                                active_tool_kind,
6040                                second_tool_kind,
6041                                rel_vel,
6042                                time,
6043                                Some(s.stage_section),
6044                            ),
6045                            stage_progress,
6046                            &mut state_animation_rate,
6047                            skeleton_attr,
6048                        )
6049                    },
6050                    CharacterState::Shockwave(s) => {
6051                        let stage_time = s.timer.as_secs_f32();
6052                        let stage_progress = match s.stage_section {
6053                            StageSection::Buildup => {
6054                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6055                            },
6056                            StageSection::Action => {
6057                                stage_time / s.static_data.swing_duration.as_secs_f32()
6058                            },
6059                            StageSection::Recover => {
6060                                stage_time / s.static_data.recover_duration.as_secs_f32()
6061                            },
6062                            _ => 0.0,
6063                        };
6064                        anim::biped_large::ShockwaveAnimation::update_skeleton(
6065                            &target_base,
6066                            (
6067                                active_tool_kind,
6068                                (second_tool_kind, second_tool_spec),
6069                                time,
6070                                rel_vel.magnitude(),
6071                                Some(s.stage_section),
6072                                ability_id,
6073                            ),
6074                            stage_progress,
6075                            &mut state_animation_rate,
6076                            skeleton_attr,
6077                        )
6078                    },
6079                    CharacterState::Explosion(s) => {
6080                        let stage_time = s.timer.as_secs_f32();
6081                        let stage_progress = match s.stage_section {
6082                            StageSection::Buildup => {
6083                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6084                            },
6085                            StageSection::Action => {
6086                                stage_time / s.static_data.action_duration.as_secs_f32()
6087                            },
6088                            StageSection::Recover => {
6089                                stage_time / s.static_data.recover_duration.as_secs_f32()
6090                            },
6091                            _ => 0.0,
6092                        };
6093
6094                        anim::biped_large::ExplosionAnimation::update_skeleton(
6095                            &target_base,
6096                            (rel_vel, state.acc_vel, Some(s.stage_section), ability_id),
6097                            stage_progress,
6098                            &mut state_animation_rate,
6099                            skeleton_attr,
6100                        )
6101                    },
6102                    CharacterState::BasicBeam(s) => {
6103                        let stage_time = s.timer.as_secs_f32();
6104                        let stage_progress = match s.stage_section {
6105                            StageSection::Buildup => {
6106                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6107                            },
6108                            StageSection::Action => s.timer.as_secs_f32(),
6109                            StageSection::Recover => {
6110                                stage_time / s.static_data.recover_duration.as_secs_f32()
6111                            },
6112                            _ => 0.0,
6113                        };
6114                        anim::biped_large::BeamAnimation::update_skeleton(
6115                            &target_base,
6116                            (
6117                                active_tool_kind,
6118                                (second_tool_kind, second_tool_spec),
6119                                time,
6120                                rel_vel,
6121                                Some(s.stage_section),
6122                                state.acc_vel,
6123                                state.state_time,
6124                                ability_id,
6125                            ),
6126                            stage_progress,
6127                            &mut state_animation_rate,
6128                            skeleton_attr,
6129                        )
6130                    },
6131                    CharacterState::SpriteSummon(s) => {
6132                        let stage_time = s.timer.as_secs_f32();
6133                        let stage_progress = match s.stage_section {
6134                            StageSection::Buildup => {
6135                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6136                            },
6137                            StageSection::Action => {
6138                                stage_time / s.static_data.cast_duration.as_secs_f32()
6139                            },
6140                            StageSection::Recover => {
6141                                stage_time / s.static_data.recover_duration.as_secs_f32()
6142                            },
6143                            _ => 0.0,
6144                        };
6145                        anim::biped_large::SpriteSummonAnimation::update_skeleton(
6146                            &target_base,
6147                            (
6148                                active_tool_kind,
6149                                (second_tool_kind, second_tool_spec),
6150                                time,
6151                                rel_vel.magnitude(),
6152                                Some(s.stage_section),
6153                                ability_id,
6154                            ),
6155                            stage_progress,
6156                            &mut state_animation_rate,
6157                            skeleton_attr,
6158                        )
6159                    },
6160                    // TODO!
6161                    _ => target_base,
6162                };
6163
6164                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
6165                state.update(
6166                    renderer,
6167                    trail_mgr,
6168                    update_buf,
6169                    &common_params,
6170                    state_animation_rate,
6171                    model,
6172                    body,
6173                );
6174            },
6175            Body::Golem(body) => {
6176                let (model, skeleton_attr) = self.golem_model_cache.get_or_create_model(
6177                    renderer,
6178                    &mut self.atlas,
6179                    body,
6180                    inventory,
6181                    (),
6182                    tick,
6183                    viewpoint_camera_mode,
6184                    viewpoint_character_state,
6185                    slow_jobs,
6186                    None,
6187                );
6188
6189                let state =
6190                    self.states.golem_states.entry(entity).or_insert_with(|| {
6191                        FigureState::new(renderer, GolemSkeleton::default(), body)
6192                    });
6193
6194                // Average velocity relative to the current ground
6195                let _rel_avg_vel = state.avg_vel - physics.ground_vel;
6196
6197                let (character, last_character) = match (character, last_character) {
6198                    (Some(c), Some(l)) => (c, l),
6199                    _ => return,
6200                };
6201
6202                if !character.same_variant(&last_character.0) {
6203                    state.state_time = 0.0;
6204                }
6205
6206                let target_base = match (
6207                    physics.on_ground.is_some(),
6208                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
6209                    physics.in_liquid().is_some(),                      // In water
6210                ) {
6211                    // Standing
6212                    (true, false, false) => anim::golem::IdleAnimation::update_skeleton(
6213                        &GolemSkeleton::default(),
6214                        time,
6215                        state.state_time,
6216                        &mut state_animation_rate,
6217                        skeleton_attr,
6218                    ),
6219                    // Running
6220                    (true, true, false) => anim::golem::RunAnimation::update_skeleton(
6221                        &GolemSkeleton::default(),
6222                        (
6223                            rel_vel,
6224                            // TODO: Update to use the quaternion.
6225                            ori * anim::vek::Vec3::<f32>::unit_y(),
6226                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
6227                            time,
6228                            state.acc_vel,
6229                        ),
6230                        state.state_time,
6231                        &mut state_animation_rate,
6232                        skeleton_attr,
6233                    ),
6234                    // In air
6235                    (false, _, false) => anim::golem::RunAnimation::update_skeleton(
6236                        &GolemSkeleton::default(),
6237                        (
6238                            rel_vel,
6239                            ori * anim::vek::Vec3::<f32>::unit_y(),
6240                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
6241                            time,
6242                            state.acc_vel,
6243                        ),
6244                        state.state_time,
6245                        &mut state_animation_rate,
6246                        skeleton_attr,
6247                    ),
6248
6249                    _ => anim::golem::IdleAnimation::update_skeleton(
6250                        &GolemSkeleton::default(),
6251                        time,
6252                        state.state_time,
6253                        &mut state_animation_rate,
6254                        skeleton_attr,
6255                    ),
6256                };
6257                let target_bones = match &character {
6258                    CharacterState::BasicRanged(s) => {
6259                        let stage_time = s.timer.as_secs_f32();
6260
6261                        let stage_progress = match s.stage_section {
6262                            StageSection::Buildup => {
6263                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6264                            },
6265                            StageSection::Recover => {
6266                                stage_time / s.static_data.recover_duration.as_secs_f32()
6267                            },
6268
6269                            _ => 0.0,
6270                        };
6271
6272                        anim::golem::ShootAnimation::update_skeleton(
6273                            &target_base,
6274                            (
6275                                Some(s.stage_section),
6276                                time,
6277                                state.state_time,
6278                                look_dir,
6279                                ability_id,
6280                            ),
6281                            stage_progress,
6282                            &mut state_animation_rate,
6283                            skeleton_attr,
6284                        )
6285                    },
6286                    CharacterState::BasicBeam(s) => {
6287                        let stage_time = s.timer.as_secs_f32();
6288
6289                        let stage_progress = match s.stage_section {
6290                            StageSection::Buildup => {
6291                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6292                            },
6293                            StageSection::Action => s.timer.as_secs_f32(),
6294                            StageSection::Recover => {
6295                                stage_time / s.static_data.recover_duration.as_secs_f32()
6296                            },
6297
6298                            _ => 0.0,
6299                        };
6300
6301                        anim::golem::BeamAnimation::update_skeleton(
6302                            &target_base,
6303                            (
6304                                Some(s.stage_section),
6305                                time,
6306                                state.state_time,
6307                                look_dir,
6308                                ability_id,
6309                            ),
6310                            stage_progress,
6311                            &mut state_animation_rate,
6312                            skeleton_attr,
6313                        )
6314                    },
6315                    CharacterState::BasicMelee(s) => {
6316                        let stage_time = s.timer.as_secs_f32();
6317                        let stage_progress = match s.stage_section {
6318                            StageSection::Buildup => {
6319                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6320                            },
6321                            StageSection::Action => {
6322                                stage_time / s.static_data.swing_duration.as_secs_f32()
6323                            },
6324                            StageSection::Recover => {
6325                                stage_time / s.static_data.recover_duration.as_secs_f32()
6326                            },
6327                            _ => 0.0,
6328                        };
6329
6330                        anim::golem::AlphaAnimation::update_skeleton(
6331                            &target_base,
6332                            (Some(s.stage_section), time, state.state_time, ability_id),
6333                            stage_progress,
6334                            &mut state_animation_rate,
6335                            skeleton_attr,
6336                        )
6337                    },
6338                    CharacterState::ComboMelee2(s) => {
6339                        let timer = s.timer.as_secs_f32();
6340                        let current_strike = s.completed_strikes % s.static_data.strikes.len();
6341                        let strike_data = s.static_data.strikes[current_strike];
6342                        let progress = match s.stage_section {
6343                            StageSection::Buildup => {
6344                                timer / strike_data.buildup_duration.as_secs_f32()
6345                            },
6346                            StageSection::Action => {
6347                                timer / strike_data.swing_duration.as_secs_f32()
6348                            },
6349                            StageSection::Recover => {
6350                                timer / strike_data.recover_duration.as_secs_f32()
6351                            },
6352                            _ => 0.0,
6353                        };
6354
6355                        anim::golem::ComboAnimation::update_skeleton(
6356                            &target_base,
6357                            (
6358                                ability_id,
6359                                Some(s.stage_section),
6360                                Some(s.static_data.ability_info),
6361                                current_strike,
6362                                move_dir,
6363                            ),
6364                            progress,
6365                            &mut state_animation_rate,
6366                            skeleton_attr,
6367                        )
6368                    },
6369                    CharacterState::Shockwave(s) => {
6370                        let stage_time = s.timer.as_secs_f32();
6371                        let stage_progress = match s.stage_section {
6372                            StageSection::Buildup => {
6373                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6374                            },
6375                            StageSection::Action => {
6376                                stage_time / s.static_data.swing_duration.as_secs_f32()
6377                            },
6378                            StageSection::Recover => {
6379                                stage_time / s.static_data.recover_duration.as_secs_f32()
6380                            },
6381                            _ => 0.0,
6382                        };
6383                        anim::golem::ShockwaveAnimation::update_skeleton(
6384                            &target_base,
6385                            (Some(s.stage_section), rel_vel.magnitude(), time),
6386                            stage_progress,
6387                            &mut state_animation_rate,
6388                            skeleton_attr,
6389                        )
6390                    },
6391                    // TODO!
6392                    _ => target_base,
6393                };
6394
6395                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
6396                state.update(
6397                    renderer,
6398                    trail_mgr,
6399                    update_buf,
6400                    &common_params,
6401                    state_animation_rate,
6402                    model,
6403                    body,
6404                );
6405            },
6406            Body::Object(body) => {
6407                let (model, skeleton_attr) = self.object_model_cache.get_or_create_model(
6408                    renderer,
6409                    &mut self.atlas,
6410                    body,
6411                    inventory,
6412                    (),
6413                    tick,
6414                    viewpoint_camera_mode,
6415                    viewpoint_character_state,
6416                    slow_jobs,
6417                    None,
6418                );
6419
6420                let state =
6421                    self.states.object_states.entry(entity).or_insert_with(|| {
6422                        FigureState::new(renderer, ObjectSkeleton::default(), body)
6423                    });
6424
6425                // Average velocity relative to the current ground
6426                let _rel_avg_vel = state.avg_vel - physics.ground_vel;
6427
6428                let idlestate = CharacterState::Idle(idle::Data::default());
6429                let last = Last(idlestate.clone());
6430                let (character, last_character) = match (character, last_character) {
6431                    (Some(c), Some(l)) => (c, l),
6432                    _ => (&idlestate, &last),
6433                };
6434
6435                if !character.same_variant(&last_character.0) {
6436                    state.state_time = 0.0;
6437                }
6438
6439                let target_base = match (
6440                    physics.on_ground.is_some(),
6441                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
6442                    physics.in_liquid().is_some(),                      // In water
6443                ) {
6444                    // Standing
6445                    (true, false, false) => anim::object::IdleAnimation::update_skeleton(
6446                        &ObjectSkeleton::default(),
6447                        (active_tool_kind, second_tool_kind, time),
6448                        state.state_time,
6449                        &mut state_animation_rate,
6450                        skeleton_attr,
6451                    ),
6452                    _ => anim::object::IdleAnimation::update_skeleton(
6453                        &ObjectSkeleton::default(),
6454                        (active_tool_kind, second_tool_kind, time),
6455                        state.state_time,
6456                        &mut state_animation_rate,
6457                        skeleton_attr,
6458                    ),
6459                };
6460
6461                let target_bones = match &character {
6462                    CharacterState::BasicRanged(s) => {
6463                        let stage_time = s.timer.as_secs_f32();
6464
6465                        let stage_progress = match s.stage_section {
6466                            StageSection::Buildup => {
6467                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6468                            },
6469                            StageSection::Recover => {
6470                                stage_time / s.static_data.recover_duration.as_secs_f32()
6471                            },
6472
6473                            _ => 0.0,
6474                        };
6475                        anim::object::ShootAnimation::update_skeleton(
6476                            &target_base,
6477                            (
6478                                active_tool_kind,
6479                                second_tool_kind,
6480                                Some(s.stage_section),
6481                                body,
6482                            ),
6483                            stage_progress,
6484                            &mut state_animation_rate,
6485                            skeleton_attr,
6486                        )
6487                    },
6488                    CharacterState::BasicBeam(s) => {
6489                        let stage_time = s.timer.as_secs_f32();
6490                        let stage_progress = match s.stage_section {
6491                            StageSection::Buildup => {
6492                                stage_time / s.static_data.buildup_duration.as_secs_f32()
6493                            },
6494                            StageSection::Action => s.timer.as_secs_f32(),
6495                            StageSection::Recover => {
6496                                stage_time / s.static_data.recover_duration.as_secs_f32()
6497                            },
6498                            _ => 0.0,
6499                        };
6500                        anim::object::BeamAnimation::update_skeleton(
6501                            &target_base,
6502                            (
6503                                active_tool_kind,
6504                                second_tool_kind,
6505                                Some(s.stage_section),
6506                                body,
6507                            ),
6508                            stage_progress,
6509                            &mut state_animation_rate,
6510                            skeleton_attr,
6511                        )
6512                    },
6513                    // TODO!
6514                    _ => target_base,
6515                };
6516
6517                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
6518                state.update(
6519                    renderer,
6520                    trail_mgr,
6521                    update_buf,
6522                    &common_params,
6523                    state_animation_rate,
6524                    model,
6525                    body,
6526                );
6527            },
6528            Body::Item(body) => {
6529                let item_key = match body {
6530                    body::item::Body::Thrown(_) => {
6531                        thrown_item.map(|thrown_item| ItemKey::from(&thrown_item.0))
6532                    },
6533                    _ => item.map(|item| ItemKey::from(item.item())),
6534                };
6535
6536                let (model, skeleton_attr) = self.item_model_cache.get_or_create_model(
6537                    renderer,
6538                    &mut self.atlas,
6539                    body,
6540                    inventory,
6541                    (),
6542                    tick,
6543                    viewpoint_camera_mode,
6544                    viewpoint_character_state,
6545                    slow_jobs,
6546                    item_key,
6547                );
6548
6549                let state =
6550                    self.states.item_states.entry(entity).or_insert_with(|| {
6551                        FigureState::new(renderer, ItemSkeleton::default(), body)
6552                    });
6553
6554                // Average velocity relative to the current ground
6555                let _rel_avg_vel = state.avg_vel - physics.ground_vel;
6556
6557                let idle_state = CharacterState::Idle(idle::Data::default());
6558                let last = Last(idle_state.clone());
6559                let (character, last_character) = match (character, last_character) {
6560                    (Some(c), Some(l)) => (c, l),
6561                    _ => (&idle_state, &last),
6562                };
6563
6564                if !character.same_variant(&last_character.0) {
6565                    state.state_time = 0.0;
6566                }
6567
6568                let target_bones = anim::item::IdleAnimation::update_skeleton(
6569                    &ItemSkeleton::default(),
6570                    time,
6571                    state.state_time,
6572                    &mut state_animation_rate,
6573                    skeleton_attr,
6574                );
6575
6576                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
6577                state.update(
6578                    renderer,
6579                    trail_mgr,
6580                    update_buf,
6581                    &common_params,
6582                    state_animation_rate,
6583                    model,
6584                    body,
6585                );
6586            },
6587            Body::Ship(body) => {
6588                let Some(terrain) = data.terrain else {
6589                    return;
6590                };
6591                let (model, skeleton_attr) = if let Some(Collider::Volume(vol)) = collider {
6592                    let vk = VolumeKey {
6593                        entity,
6594                        mut_count: vol.mut_count,
6595                    };
6596                    let (model, _skeleton_attr) =
6597                        self.volume_model_cache.get_or_create_terrain_model(
6598                            renderer,
6599                            &mut self.atlas,
6600                            vk,
6601                            Arc::clone(vol),
6602                            tick,
6603                            slow_jobs,
6604                            &terrain.sprite_render_state,
6605                        );
6606
6607                    let state = self
6608                        .states
6609                        .volume_states
6610                        .entry(entity)
6611                        .or_insert_with(|| FigureState::new(renderer, vk, vk));
6612
6613                    state.update(
6614                        renderer,
6615                        trail_mgr,
6616                        update_buf,
6617                        &common_params,
6618                        state_animation_rate,
6619                        model,
6620                        vk,
6621                    );
6622                    return;
6623                } else if body.manifest_entry().is_some() {
6624                    self.ship_model_cache.get_or_create_terrain_model(
6625                        renderer,
6626                        &mut self.atlas,
6627                        body,
6628                        (),
6629                        tick,
6630                        slow_jobs,
6631                        &terrain.sprite_render_state,
6632                    )
6633                } else {
6634                    // No way to determine model (this is okay, we might just not have received
6635                    // the `Collider` for the entity yet. Wait until the
6636                    // next tick.
6637                    return;
6638                };
6639
6640                let state =
6641                    self.states.ship_states.entry(entity).or_insert_with(|| {
6642                        FigureState::new(renderer, ShipSkeleton::default(), body)
6643                    });
6644
6645                // Average velocity relative to the current ground
6646                let _rel_avg_vel = state.avg_vel - physics.ground_vel;
6647
6648                let idlestate = CharacterState::Idle(idle::Data::default());
6649                let last = Last(idlestate.clone());
6650                let (character, last_character) = match (character, last_character) {
6651                    (Some(c), Some(l)) => (c, l),
6652                    _ => (&idlestate, &last),
6653                };
6654
6655                if !character.same_variant(&last_character.0) {
6656                    state.state_time = 0.0;
6657                }
6658
6659                let target_base = match (
6660                    physics.on_ground.is_some(),
6661                    rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
6662                    physics.in_liquid().is_some(),                      // In water
6663                ) {
6664                    // Standing
6665                    (true, false, false) => anim::ship::IdleAnimation::update_skeleton(
6666                        &ShipSkeleton::default(),
6667                        (
6668                            active_tool_kind,
6669                            second_tool_kind,
6670                            time,
6671                            state.acc_vel,
6672                            ori * anim::vek::Vec3::<f32>::unit_y(),
6673                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
6674                        ),
6675                        state.state_time,
6676                        &mut state_animation_rate,
6677                        skeleton_attr,
6678                    ),
6679                    _ => anim::ship::IdleAnimation::update_skeleton(
6680                        &ShipSkeleton::default(),
6681                        (
6682                            active_tool_kind,
6683                            second_tool_kind,
6684                            time,
6685                            state.acc_vel,
6686                            ori * anim::vek::Vec3::<f32>::unit_y(),
6687                            state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
6688                        ),
6689                        state.state_time,
6690                        &mut state_animation_rate,
6691                        skeleton_attr,
6692                    ),
6693                };
6694
6695                let target_bones = target_base;
6696                state.skeleton = Lerp::lerp(&state.skeleton, &target_bones, dt_lerp);
6697                state.update(
6698                    renderer,
6699                    trail_mgr,
6700                    update_buf,
6701                    &common_params,
6702                    state_animation_rate,
6703                    model,
6704                    body,
6705                );
6706            },
6707            Body::Plugin(body) => {
6708                #[cfg(feature = "plugins")]
6709                {
6710                    let (model, _skeleton_attr) = self.plugin_model_cache.get_or_create_model(
6711                        renderer,
6712                        &mut self.atlas,
6713                        body,
6714                        inventory,
6715                        (),
6716                        tick,
6717                        viewpoint_camera_mode,
6718                        viewpoint_character_state,
6719                        slow_jobs,
6720                        None,
6721                    );
6722
6723                    let state = self.states.plugin_states.entry(entity).or_insert_with(|| {
6724                        FigureState::new(renderer, PluginSkeleton::default(), body)
6725                    });
6726
6727                    // Average velocity relative to the current ground
6728                    let rel_avg_vel = state.avg_vel - physics.ground_vel;
6729
6730                    let idle_state = CharacterState::Idle(idle::Data::default());
6731                    let last = Last(idle_state.clone());
6732                    let (character, last_character) = match (character, last_character) {
6733                        (Some(c), Some(l)) => (c, l),
6734                        _ => (&idle_state, &last),
6735                    };
6736
6737                    if !character.same_variant(&last_character.0) {
6738                        state.state_time = 0.0;
6739                    }
6740
6741                    let char_state = match character {
6742                        CharacterState::BasicMelee(_) => {
6743                            common_state::plugin::module::CharacterState::Melee
6744                        },
6745                        CharacterState::Sit => common_state::plugin::module::CharacterState::Feed,
6746                        CharacterState::Stunned(_) => {
6747                            common_state::plugin::module::CharacterState::Stunned
6748                        },
6749                        _ if physics.on_ground.is_none() => {
6750                            common_state::plugin::module::CharacterState::Jump
6751                        },
6752                        _ if physics.in_liquid().is_some() => {
6753                            common_state::plugin::module::CharacterState::Swim
6754                        },
6755                        _ if rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR => {
6756                            common_state::plugin::module::CharacterState::Run
6757                        },
6758                        _ => common_state::plugin::module::CharacterState::Idle,
6759                    };
6760
6761                    if let Some(bodyobj) = data.plugins.create_body("lizard") {
6762                        let dep = common_state::plugin::module::Dependency {
6763                            velocity: state.avg_vel.into_tuple(),
6764                            ori: ori.into_vec4().into_tuple(),
6765                            last_ori: state.last_ori.into_vec4().into_tuple(),
6766                            global_time: time,
6767                            avg_vel: rel_avg_vel.into_tuple(),
6768                            state: char_state,
6769                        };
6770
6771                        if let Some(target_bones) =
6772                            data.plugins.update_skeleton(&bodyobj, &dep, time)
6773                        {
6774                            state.skeleton = Lerp::lerp(
6775                                &state.skeleton,
6776                                &PluginSkeleton::from_module(target_bones),
6777                                dt_lerp,
6778                            );
6779                            state.update(
6780                                renderer,
6781                                trail_mgr,
6782                                update_buf,
6783                                &common_params,
6784                                state_animation_rate,
6785                                model,
6786                                body,
6787                            );
6788                        }
6789                    }
6790                }
6791                #[cfg(not(feature = "plugins"))]
6792                let _ = body;
6793            },
6794        }
6795    }
6796
6797    fn render_shadow_mapping<'a>(
6798        &'a self,
6799        drawer: &mut FigureShadowDrawer<'_, 'a>,
6800        state: &State,
6801        tick: u64,
6802        (camera, figure_lod_render_distance): CameraData,
6803        filter_state: impl Fn(&FigureStateMeta) -> bool,
6804    ) {
6805        let ecs = state.ecs();
6806        let time = ecs.read_resource::<Time>();
6807        let items = ecs.read_storage::<PickupItem>();
6808        let thrown_items = ecs.read_storage::<ThrownItem>();
6809        (
6810                &ecs.entities(),
6811                &ecs.read_storage::<Pos>(),
6812                ecs.read_storage::<Ori>().maybe(),
6813                &ecs.read_storage::<Body>(),
6814                ecs.read_storage::<Health>().maybe(),
6815                ecs.read_storage::<Inventory>().maybe(),
6816                ecs.read_storage::<Scale>().maybe(),
6817                ecs.read_storage::<Collider>().maybe(),
6818                ecs.read_storage::<Object>().maybe(),
6819            )
6820            .join()
6821            // Don't render dead entities
6822            .filter(|(_, _, _, _, health, _, _, _, _)| health.is_none_or(|h| !h.is_dead))
6823            .filter(|(_, _, _, _, _, _, _, _, obj)| !self.should_flicker(*time, *obj))
6824            .for_each(|(entity, pos, _, body, _, inventory, scale, collider, _)| {
6825                if let Some((bound, model, _)) = self.get_model_for_render(
6826                    tick,
6827                    camera,
6828                    None,
6829                    entity,
6830                    body,
6831                    scale.copied(),
6832                    inventory,
6833                    false,
6834                    pos.0,
6835                    figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
6836                    match collider {
6837                        Some(Collider::Volume(vol)) => vol.mut_count,
6838                        _ => 0,
6839                    },
6840                    &filter_state,
6841                    match body {
6842                        Body::Item(body) => match body {
6843                            body::item::Body::Thrown(_) => thrown_items
6844                                .get(entity)
6845                                .map(|thrown_item| ItemKey::from(&thrown_item.0)),
6846                            _ => items.get(entity).map(|item| ItemKey::from(item.item())),
6847                        },
6848                        _ => None,
6849                    }
6850                ) {
6851                    drawer.draw(model, bound);
6852                }
6853            });
6854    }
6855
6856    pub fn render_shadows<'a>(
6857        &'a self,
6858        drawer: &mut FigureShadowDrawer<'_, 'a>,
6859        state: &State,
6860        tick: u64,
6861        camera_data: CameraData,
6862    ) {
6863        span!(_guard, "render_shadows", "FigureManager::render_shadows");
6864        self.render_shadow_mapping(drawer, state, tick, camera_data, |state| {
6865            state.can_shadow_sun()
6866        })
6867    }
6868
6869    pub fn render_rain_occlusion<'a>(
6870        &'a self,
6871        drawer: &mut FigureShadowDrawer<'_, 'a>,
6872        state: &State,
6873        tick: u64,
6874        camera_data: CameraData,
6875    ) {
6876        span!(
6877            _guard,
6878            "render_rain_occlusion",
6879            "FigureManager::render_rain_occlusion"
6880        );
6881        self.render_shadow_mapping(drawer, state, tick, camera_data, |state| {
6882            state.can_occlude_rain()
6883        })
6884    }
6885
6886    pub fn render_sprites<'a>(
6887        &'a self,
6888        drawer: &mut SpriteDrawer<'_, 'a>,
6889        state: &State,
6890        cam_pos: Vec3<f32>,
6891        sprite_render_distance: f32,
6892    ) {
6893        span!(_guard, "render", "FigureManager::render_sprites");
6894        let ecs = state.ecs();
6895        let sprite_low_detail_distance = sprite_render_distance * 0.75;
6896        let sprite_mid_detail_distance = sprite_render_distance * 0.5;
6897        let sprite_hid_detail_distance = sprite_render_distance * 0.35;
6898        let sprite_high_detail_distance = sprite_render_distance * 0.15;
6899
6900        let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
6901
6902        for (entity, pos, ori, body, _, collider) in (
6903            &ecs.entities(),
6904            &ecs.read_storage::<Pos>(),
6905            &ecs.read_storage::<Ori>(),
6906            &ecs.read_storage::<Body>(),
6907            ecs.read_storage::<Health>().maybe(),
6908            ecs.read_storage::<Collider>().maybe(),
6909        )
6910            .join()
6911        // Don't render dead entities
6912        .filter(|(_, _, _, _, health, _)| health.is_none_or(|h| !h.is_dead))
6913        {
6914            if let Some((sprite_instances, data)) = self
6915                .get_sprite_instances(entity, body, collider)
6916                .zip(self.states.get_terrain_locals(body, &entity))
6917            {
6918                let dist = collider
6919                    .and_then(|collider| {
6920                        let vol = collider.get_vol(&voxel_colliders_manifest)?;
6921
6922                        let mat = Mat4::from(ori.to_quat()).translated_3d(pos.0)
6923                            * Mat4::translation_3d(vol.translation);
6924
6925                        let p = mat.inverted().mul_point(cam_pos);
6926                        let aabb = Aabb {
6927                            min: Vec3::zero(),
6928                            max: vol.volume().sz.as_(),
6929                        };
6930                        Some(if aabb.contains_point(p) {
6931                            0.0
6932                        } else {
6933                            aabb.distance_to_point(p)
6934                        })
6935                    })
6936                    .unwrap_or_else(|| pos.0.distance(cam_pos));
6937
6938                if dist < sprite_render_distance {
6939                    let lod_level = if dist < sprite_high_detail_distance {
6940                        0
6941                    } else if dist < sprite_hid_detail_distance {
6942                        1
6943                    } else if dist < sprite_mid_detail_distance {
6944                        2
6945                    } else if dist < sprite_low_detail_distance {
6946                        3
6947                    } else {
6948                        4
6949                    };
6950
6951                    drawer.draw(
6952                        data,
6953                        &sprite_instances[lod_level],
6954                        &AltIndices {
6955                            deep_end: 0,
6956                            underground_end: 0,
6957                        },
6958                        CullingMode::None,
6959                    )
6960                }
6961            }
6962        }
6963    }
6964
6965    // Returns `true` if an object should flicker because it's about to vanish
6966    fn should_flicker(&self, time: Time, obj: Option<&Object>) -> bool {
6967        if let Some(Object::DeleteAfter {
6968            spawned_at,
6969            timeout,
6970        }) = obj
6971        {
6972            time.0 > spawned_at.0 + timeout.as_secs_f64() - 10.0 && (time.0 * 8.0).fract() < 0.5
6973        } else {
6974            false
6975        }
6976    }
6977
6978    pub fn render<'a>(
6979        &'a self,
6980        drawer: &mut FigureDrawer<'_, 'a>,
6981        state: &State,
6982        viewpoint_entity: EcsEntity,
6983        tick: u64,
6984        (camera, figure_lod_render_distance): CameraData,
6985    ) {
6986        span!(_guard, "render", "FigureManager::render");
6987        let ecs = state.ecs();
6988
6989        let time = ecs.read_resource::<Time>();
6990        let character_state_storage = state.read_storage::<CharacterState>();
6991        let character_state = character_state_storage.get(viewpoint_entity);
6992        let items = ecs.read_storage::<PickupItem>();
6993        let thrown_items = ecs.read_storage::<ThrownItem>();
6994        for (entity, pos, body, _, inventory, scale, collider, _) in (
6995            &ecs.entities(),
6996            &ecs.read_storage::<Pos>(),
6997            &ecs.read_storage::<Body>(),
6998            ecs.read_storage::<Health>().maybe(),
6999            ecs.read_storage::<Inventory>().maybe(),
7000            ecs.read_storage::<Scale>().maybe(),
7001            ecs.read_storage::<Collider>().maybe(),
7002            ecs.read_storage::<Object>().maybe(),
7003        )
7004            .join()
7005        // Don't render dead entities
7006        .filter(|(_, _, _, health, _, _, _, _)| health.is_none_or(|h| !h.is_dead))
7007        // Don't render player
7008        .filter(|(entity, _, _, _, _, _, _, _)| *entity != viewpoint_entity)
7009        .filter(|(_, _, _, _, _, _, _, obj)| !self.should_flicker(*time, *obj))
7010        {
7011            if let Some((bound, model, atlas)) = self.get_model_for_render(
7012                tick,
7013                camera,
7014                character_state,
7015                entity,
7016                body,
7017                scale.copied(),
7018                inventory,
7019                false,
7020                pos.0,
7021                figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
7022                match collider {
7023                    Some(Collider::Volume(vol)) => vol.mut_count,
7024                    _ => 0,
7025                },
7026                |state| state.visible(),
7027                match body {
7028                    Body::Item(body) => match body {
7029                        body::item::Body::Thrown(_) => thrown_items
7030                            .get(entity)
7031                            .map(|thrown_item| ItemKey::from(&thrown_item.0)),
7032                        _ => items.get(entity).map(|item| ItemKey::from(item.item())),
7033                    },
7034                    _ => None,
7035                },
7036            ) {
7037                drawer.draw(model, bound, atlas);
7038            }
7039        }
7040    }
7041
7042    pub fn render_viewpoint<'a>(
7043        &'a self,
7044        drawer: &mut FigureDrawer<'_, 'a>,
7045        state: &State,
7046        viewpoint_entity: EcsEntity,
7047        tick: u64,
7048        (camera, figure_lod_render_distance): CameraData,
7049    ) {
7050        span!(_guard, "render_player", "FigureManager::render_player");
7051        let ecs = state.ecs();
7052
7053        let character_state_storage = state.read_storage::<CharacterState>();
7054        let character_state = character_state_storage.get(viewpoint_entity);
7055        let items = ecs.read_storage::<PickupItem>();
7056        let thrown_items = ecs.read_storage::<ThrownItem>();
7057
7058        if let (Some(pos), Some(body), scale) = (
7059            ecs.read_storage::<Pos>().get(viewpoint_entity),
7060            ecs.read_storage::<Body>().get(viewpoint_entity),
7061            ecs.read_storage::<Scale>().get(viewpoint_entity),
7062        ) {
7063            let healths = state.read_storage::<Health>();
7064            let health = healths.get(viewpoint_entity);
7065            if health.is_some_and(|h| h.is_dead) {
7066                return;
7067            }
7068
7069            let inventory_storage = ecs.read_storage::<Inventory>();
7070            let inventory = inventory_storage.get(viewpoint_entity);
7071
7072            if let Some((bound, model, atlas)) = self.get_model_for_render(
7073                tick,
7074                camera,
7075                character_state,
7076                viewpoint_entity,
7077                body,
7078                scale.copied(),
7079                inventory,
7080                true,
7081                pos.0,
7082                figure_lod_render_distance,
7083                0,
7084                |state| state.visible(),
7085                match body {
7086                    Body::Item(body) => match body {
7087                        body::item::Body::Thrown(_) => thrown_items
7088                            .get(viewpoint_entity)
7089                            .map(|thrown_item| ItemKey::from(&thrown_item.0)),
7090                        _ => items
7091                            .get(viewpoint_entity)
7092                            .map(|item| ItemKey::from(item.item())),
7093                    },
7094                    _ => None,
7095                },
7096            ) {
7097                drawer.draw(model, bound, atlas);
7098                /*renderer.render_player_shadow(
7099                    model,
7100                    &atlas,
7101                    global,
7102                    bone_consts,
7103                    lod,
7104                    &global.shadow_mats,
7105                );*/
7106            }
7107        }
7108    }
7109
7110    fn get_model_for_render(
7111        &self,
7112        tick: u64,
7113        camera: &Camera,
7114        character_state: Option<&CharacterState>,
7115        entity: EcsEntity,
7116        body: &Body,
7117        scale: Option<Scale>,
7118        inventory: Option<&Inventory>,
7119        is_viewpoint: bool,
7120        pos: Vec3<f32>,
7121        figure_lod_render_distance: f32,
7122        mut_count: usize,
7123        filter_state: impl Fn(&FigureStateMeta) -> bool,
7124        item_key: Option<ItemKey>,
7125    ) -> Option<FigureModelRef<'_>> {
7126        let body = *body;
7127
7128        let viewpoint_camera_mode = if is_viewpoint {
7129            camera.get_mode()
7130        } else {
7131            CameraMode::default()
7132        };
7133        let focus_pos = camera.get_focus_pos();
7134        let cam_pos = camera.dependents().cam_pos + focus_pos.map(|e| e.trunc());
7135        let character_state = if is_viewpoint { character_state } else { None };
7136
7137        let FigureMgr {
7138            atlas: atlas_,
7139            character_model_cache: model_cache,
7140            theropod_model_cache,
7141            quadruped_small_model_cache,
7142            quadruped_medium_model_cache,
7143            quadruped_low_model_cache,
7144            bird_medium_model_cache,
7145            bird_large_model_cache,
7146            dragon_model_cache,
7147            fish_medium_model_cache,
7148            fish_small_model_cache,
7149            biped_large_model_cache,
7150            biped_small_model_cache,
7151            object_model_cache,
7152            item_model_cache,
7153            ship_model_cache,
7154            golem_model_cache,
7155            volume_model_cache,
7156            arthropod_model_cache,
7157            crustacean_model_cache,
7158            #[cfg(feature = "plugins")]
7159            plugin_model_cache,
7160            states:
7161                FigureMgrStates {
7162                    character_states,
7163                    quadruped_small_states,
7164                    quadruped_medium_states,
7165                    quadruped_low_states,
7166                    bird_medium_states,
7167                    fish_medium_states,
7168                    theropod_states,
7169                    dragon_states,
7170                    bird_large_states,
7171                    fish_small_states,
7172                    biped_large_states,
7173                    biped_small_states,
7174                    golem_states,
7175                    object_states,
7176                    item_states,
7177                    ship_states,
7178                    volume_states,
7179                    arthropod_states,
7180                    crustacean_states,
7181                    #[cfg(feature = "plugins")]
7182                    plugin_states,
7183                },
7184        } = self;
7185        let atlas = atlas_;
7186        if let Some((bound, model_entry)) = match body {
7187            Body::Humanoid(body) => character_states
7188                .get(&entity)
7189                .filter(|state| filter_state(state))
7190                .map(move |state| {
7191                    (
7192                        state.bound(),
7193                        model_cache
7194                            .get_model(
7195                                atlas,
7196                                body,
7197                                inventory,
7198                                tick,
7199                                viewpoint_camera_mode,
7200                                character_state,
7201                                None,
7202                            )
7203                            .map(ModelEntryRef::Figure),
7204                    )
7205                }),
7206            Body::QuadrupedSmall(body) => quadruped_small_states
7207                .get(&entity)
7208                .filter(|state| filter_state(state))
7209                .map(move |state| {
7210                    (
7211                        state.bound(),
7212                        quadruped_small_model_cache
7213                            .get_model(
7214                                atlas,
7215                                body,
7216                                inventory,
7217                                tick,
7218                                viewpoint_camera_mode,
7219                                character_state,
7220                                None,
7221                            )
7222                            .map(ModelEntryRef::Figure),
7223                    )
7224                }),
7225            Body::QuadrupedMedium(body) => quadruped_medium_states
7226                .get(&entity)
7227                .filter(|state| filter_state(state))
7228                .map(move |state| {
7229                    (
7230                        state.bound(),
7231                        quadruped_medium_model_cache
7232                            .get_model(
7233                                atlas,
7234                                body,
7235                                inventory,
7236                                tick,
7237                                viewpoint_camera_mode,
7238                                character_state,
7239                                None,
7240                            )
7241                            .map(ModelEntryRef::Figure),
7242                    )
7243                }),
7244            Body::QuadrupedLow(body) => quadruped_low_states
7245                .get(&entity)
7246                .filter(|state| filter_state(state))
7247                .map(move |state| {
7248                    (
7249                        state.bound(),
7250                        quadruped_low_model_cache
7251                            .get_model(
7252                                atlas,
7253                                body,
7254                                inventory,
7255                                tick,
7256                                viewpoint_camera_mode,
7257                                character_state,
7258                                None,
7259                            )
7260                            .map(ModelEntryRef::Figure),
7261                    )
7262                }),
7263            Body::BirdMedium(body) => bird_medium_states
7264                .get(&entity)
7265                .filter(|state| filter_state(state))
7266                .map(move |state| {
7267                    (
7268                        state.bound(),
7269                        bird_medium_model_cache
7270                            .get_model(
7271                                atlas,
7272                                body,
7273                                inventory,
7274                                tick,
7275                                viewpoint_camera_mode,
7276                                character_state,
7277                                None,
7278                            )
7279                            .map(ModelEntryRef::Figure),
7280                    )
7281                }),
7282            Body::FishMedium(body) => fish_medium_states
7283                .get(&entity)
7284                .filter(|state| filter_state(state))
7285                .map(move |state| {
7286                    (
7287                        state.bound(),
7288                        fish_medium_model_cache
7289                            .get_model(
7290                                atlas,
7291                                body,
7292                                inventory,
7293                                tick,
7294                                viewpoint_camera_mode,
7295                                character_state,
7296                                None,
7297                            )
7298                            .map(ModelEntryRef::Figure),
7299                    )
7300                }),
7301            Body::Theropod(body) => theropod_states
7302                .get(&entity)
7303                .filter(|state| filter_state(state))
7304                .map(move |state| {
7305                    (
7306                        state.bound(),
7307                        theropod_model_cache
7308                            .get_model(
7309                                atlas,
7310                                body,
7311                                inventory,
7312                                tick,
7313                                viewpoint_camera_mode,
7314                                character_state,
7315                                None,
7316                            )
7317                            .map(ModelEntryRef::Figure),
7318                    )
7319                }),
7320            Body::Dragon(body) => dragon_states
7321                .get(&entity)
7322                .filter(|state| filter_state(state))
7323                .map(move |state| {
7324                    (
7325                        state.bound(),
7326                        dragon_model_cache
7327                            .get_model(
7328                                atlas,
7329                                body,
7330                                inventory,
7331                                tick,
7332                                viewpoint_camera_mode,
7333                                character_state,
7334                                None,
7335                            )
7336                            .map(ModelEntryRef::Figure),
7337                    )
7338                }),
7339            Body::BirdLarge(body) => bird_large_states
7340                .get(&entity)
7341                .filter(|state| filter_state(state))
7342                .map(move |state| {
7343                    (
7344                        state.bound(),
7345                        bird_large_model_cache
7346                            .get_model(
7347                                atlas,
7348                                body,
7349                                inventory,
7350                                tick,
7351                                viewpoint_camera_mode,
7352                                character_state,
7353                                None,
7354                            )
7355                            .map(ModelEntryRef::Figure),
7356                    )
7357                }),
7358            Body::FishSmall(body) => fish_small_states
7359                .get(&entity)
7360                .filter(|state| filter_state(state))
7361                .map(move |state| {
7362                    (
7363                        state.bound(),
7364                        fish_small_model_cache
7365                            .get_model(
7366                                atlas,
7367                                body,
7368                                inventory,
7369                                tick,
7370                                viewpoint_camera_mode,
7371                                character_state,
7372                                None,
7373                            )
7374                            .map(ModelEntryRef::Figure),
7375                    )
7376                }),
7377            Body::BipedLarge(body) => biped_large_states
7378                .get(&entity)
7379                .filter(|state| filter_state(state))
7380                .map(move |state| {
7381                    (
7382                        state.bound(),
7383                        biped_large_model_cache
7384                            .get_model(
7385                                atlas,
7386                                body,
7387                                inventory,
7388                                tick,
7389                                viewpoint_camera_mode,
7390                                character_state,
7391                                None,
7392                            )
7393                            .map(ModelEntryRef::Figure),
7394                    )
7395                }),
7396            Body::BipedSmall(body) => biped_small_states
7397                .get(&entity)
7398                .filter(|state| filter_state(state))
7399                .map(move |state| {
7400                    (
7401                        state.bound(),
7402                        biped_small_model_cache
7403                            .get_model(
7404                                atlas,
7405                                body,
7406                                inventory,
7407                                tick,
7408                                viewpoint_camera_mode,
7409                                character_state,
7410                                None,
7411                            )
7412                            .map(ModelEntryRef::Figure),
7413                    )
7414                }),
7415            Body::Golem(body) => golem_states
7416                .get(&entity)
7417                .filter(|state| filter_state(state))
7418                .map(move |state| {
7419                    (
7420                        state.bound(),
7421                        golem_model_cache
7422                            .get_model(
7423                                atlas,
7424                                body,
7425                                inventory,
7426                                tick,
7427                                viewpoint_camera_mode,
7428                                character_state,
7429                                None,
7430                            )
7431                            .map(ModelEntryRef::Figure),
7432                    )
7433                }),
7434            Body::Arthropod(body) => arthropod_states
7435                .get(&entity)
7436                .filter(|state| filter_state(state))
7437                .map(move |state| {
7438                    (
7439                        state.bound(),
7440                        arthropod_model_cache
7441                            .get_model(
7442                                atlas,
7443                                body,
7444                                inventory,
7445                                tick,
7446                                viewpoint_camera_mode,
7447                                character_state,
7448                                None,
7449                            )
7450                            .map(ModelEntryRef::Figure),
7451                    )
7452                }),
7453            Body::Crustacean(body) => crustacean_states
7454                .get(&entity)
7455                .filter(|state| filter_state(state))
7456                .map(move |state| {
7457                    (
7458                        state.bound(),
7459                        crustacean_model_cache
7460                            .get_model(
7461                                atlas,
7462                                body,
7463                                inventory,
7464                                tick,
7465                                viewpoint_camera_mode,
7466                                character_state,
7467                                None,
7468                            )
7469                            .map(ModelEntryRef::Figure),
7470                    )
7471                }),
7472            Body::Object(body) => object_states
7473                .get(&entity)
7474                .filter(|state| filter_state(state))
7475                .map(move |state| {
7476                    (
7477                        state.bound(),
7478                        object_model_cache
7479                            .get_model(
7480                                atlas,
7481                                body,
7482                                inventory,
7483                                tick,
7484                                viewpoint_camera_mode,
7485                                character_state,
7486                                None,
7487                            )
7488                            .map(ModelEntryRef::Figure),
7489                    )
7490                }),
7491            Body::Item(body) => item_states
7492                .get(&entity)
7493                .filter(|state| filter_state(state))
7494                .map(move |state| {
7495                    (
7496                        state.bound(),
7497                        item_model_cache
7498                            .get_model(
7499                                atlas,
7500                                body,
7501                                inventory,
7502                                tick,
7503                                viewpoint_camera_mode,
7504                                character_state,
7505                                item_key,
7506                            )
7507                            .map(ModelEntryRef::Figure),
7508                    )
7509                }),
7510            Body::Ship(body) => {
7511                if matches!(body, ship::Body::Volume) {
7512                    volume_states
7513                        .get(&entity)
7514                        .filter(|state| filter_state(state))
7515                        .map(move |state| {
7516                            (
7517                                state.bound(),
7518                                volume_model_cache
7519                                    .get_model(
7520                                        atlas,
7521                                        VolumeKey { entity, mut_count },
7522                                        None,
7523                                        tick,
7524                                        CameraMode::default(),
7525                                        None,
7526                                        None,
7527                                    )
7528                                    .map(ModelEntryRef::Terrain),
7529                            )
7530                        })
7531                } else if body.manifest_entry().is_some() {
7532                    ship_states
7533                        .get(&entity)
7534                        .filter(|state| filter_state(state))
7535                        .map(move |state| {
7536                            (
7537                                state.bound(),
7538                                ship_model_cache
7539                                    .get_model(
7540                                        atlas,
7541                                        body,
7542                                        None,
7543                                        tick,
7544                                        CameraMode::default(),
7545                                        None,
7546                                        None,
7547                                    )
7548                                    .map(ModelEntryRef::Terrain),
7549                            )
7550                        })
7551                } else {
7552                    None
7553                }
7554            },
7555            Body::Plugin(body) => {
7556                #[cfg(not(feature = "plugins"))]
7557                {
7558                    let _ = body;
7559                    unreachable!("Plugins require feature");
7560                }
7561                #[cfg(feature = "plugins")]
7562                {
7563                    plugin_states
7564                        .get(&entity)
7565                        .filter(|state| filter_state(state))
7566                        .map(move |state| {
7567                            (
7568                                state.bound(),
7569                                plugin_model_cache
7570                                    .get_model(
7571                                        atlas,
7572                                        body,
7573                                        inventory,
7574                                        tick,
7575                                        viewpoint_camera_mode,
7576                                        character_state,
7577                                        item_key,
7578                                    )
7579                                    .map(ModelEntryRef::Figure),
7580                            )
7581                        })
7582                }
7583            },
7584        } {
7585            let model_entry = model_entry?;
7586
7587            let figure_low_detail_distance = figure_lod_render_distance
7588                * if matches!(body, Body::Ship(_)) {
7589                    ship::AIRSHIP_SCALE
7590                } else {
7591                    1.0
7592                }
7593                * scale.map_or(1.0, |s| s.0)
7594                * 0.75;
7595            let figure_mid_detail_distance = figure_lod_render_distance
7596                * if matches!(body, Body::Ship(_)) {
7597                    ship::AIRSHIP_SCALE
7598                } else {
7599                    1.0
7600                }
7601                * scale.map_or(1.0, |s| s.0)
7602                * 0.5;
7603
7604            let model = if pos.distance_squared(cam_pos) > figure_low_detail_distance.powi(2) {
7605                model_entry.lod_model(2)
7606            } else if pos.distance_squared(cam_pos) > figure_mid_detail_distance.powi(2) {
7607                model_entry.lod_model(1)
7608            } else {
7609                model_entry.lod_model(0)
7610            };
7611
7612            Some((bound, model?, atlas_.texture(model_entry)))
7613        } else {
7614            // trace!("Body has no saved figure");
7615            None
7616        }
7617    }
7618
7619    fn get_sprite_instances<'a>(
7620        &'a self,
7621        entity: EcsEntity,
7622        body: &Body,
7623        collider: Option<&Collider>,
7624    ) -> Option<&'a [Instances<SpriteInstance>; SPRITE_LOD_LEVELS]> {
7625        match body {
7626            Body::Ship(body) => {
7627                if let Some(Collider::Volume(vol)) = collider {
7628                    let vk = VolumeKey {
7629                        entity,
7630                        mut_count: vol.mut_count,
7631                    };
7632                    self.volume_model_cache.get_sprites(vk)
7633                } else if body.manifest_entry().is_some() {
7634                    self.ship_model_cache.get_sprites(*body)
7635                } else {
7636                    None
7637                }
7638            },
7639            _ => None,
7640        }
7641    }
7642
7643    pub fn get_blocks_of_interest<'a>(
7644        &'a self,
7645        entity: EcsEntity,
7646        body: &Body,
7647        collider: Option<&Collider>,
7648    ) -> Option<(&'a BlocksOfInterest, Vec3<f32>)> {
7649        match body {
7650            Body::Ship(body) => {
7651                if let Some(Collider::Volume(vol)) = collider {
7652                    let vk = VolumeKey {
7653                        entity,
7654                        mut_count: vol.mut_count,
7655                    };
7656                    self.volume_model_cache.get_blocks_of_interest(vk)
7657                } else {
7658                    self.ship_model_cache.get_blocks_of_interest(*body)
7659                }
7660            },
7661            _ => None,
7662        }
7663    }
7664
7665    pub fn get_heads(
7666        &self,
7667        scene_data: &SceneData,
7668        entity: EcsEntity,
7669    ) -> Vec<anim::vek::Vec3<f32>> {
7670        scene_data
7671            .state
7672            .ecs()
7673            .read_storage::<Body>()
7674            .get(entity)
7675            .and_then(|b| match b {
7676                Body::QuadrupedLow(_) => self
7677                    .states
7678                    .quadruped_low_states
7679                    .get(&entity)
7680                    .map(|state| &state.computed_skeleton)
7681                    .map(|skeleton| {
7682                        vec![
7683                            (skeleton.head_l_upper * Vec4::unit_w()).xyz(),
7684                            (skeleton.head_c_upper * Vec4::unit_w()).xyz(),
7685                            (skeleton.head_r_upper * Vec4::unit_w()).xyz(),
7686                        ]
7687                    }),
7688                _ => None,
7689            })
7690            .unwrap_or(Vec::new())
7691    }
7692
7693    pub fn viewpoint_offset(&self, scene_data: &SceneData, entity: EcsEntity) -> Vec3<f32> {
7694        scene_data
7695            .state
7696            .ecs()
7697            .read_storage::<Body>()
7698            .get(entity)
7699            .and_then(|b| match b {
7700                Body::Humanoid(_) => self
7701                    .states
7702                    .character_states
7703                    .get(&entity)
7704                    .map(|state| &state.computed_skeleton)
7705                    .map(|skeleton| (skeleton.head * Vec4::new(0.0, 0.0, 4.0, 1.0)).xyz()),
7706                Body::QuadrupedSmall(_) => self
7707                    .states
7708                    .quadruped_small_states
7709                    .get(&entity)
7710                    .map(|state| &state.computed_skeleton)
7711                    .map(|skeleton| (skeleton.head * Vec4::new(0.0, 3.0, 0.0, 1.0)).xyz()),
7712                Body::QuadrupedMedium(b) => self
7713                    .states
7714                    .quadruped_medium_states
7715                    .get(&entity)
7716                    .map(|state| &state.computed_skeleton)
7717                    .map(|skeleton| (skeleton.head * quadruped_medium::viewpoint(b)).xyz()),
7718                Body::BirdMedium(b) => self
7719                    .states
7720                    .bird_medium_states
7721                    .get(&entity)
7722                    .map(|state| &state.computed_skeleton)
7723                    .map(|skeleton| (skeleton.head * bird_medium::viewpoint(b)).xyz()),
7724                Body::FishMedium(_) => self
7725                    .states
7726                    .fish_medium_states
7727                    .get(&entity)
7728                    .map(|state| &state.computed_skeleton)
7729                    .map(|skeleton| (skeleton.head * Vec4::new(0.0, 5.0, 0.0, 1.0)).xyz()),
7730                Body::Dragon(_) => self
7731                    .states
7732                    .dragon_states
7733                    .get(&entity)
7734                    .map(|state| &state.computed_skeleton)
7735                    .map(|skeleton| (skeleton.head_upper * Vec4::new(0.0, 8.0, 0.0, 1.0)).xyz()),
7736                Body::BirdLarge(_) => self
7737                    .states
7738                    .bird_large_states
7739                    .get(&entity)
7740                    .map(|state| &state.computed_skeleton)
7741                    .map(|skeleton| (skeleton.head * Vec4::new(0.0, 3.0, 6.0, 1.0)).xyz()),
7742                Body::FishSmall(_) => self
7743                    .states
7744                    .fish_small_states
7745                    .get(&entity)
7746                    .map(|state| &state.computed_skeleton)
7747                    .map(|skeleton| (skeleton.chest * Vec4::new(0.0, 3.0, 0.0, 1.0)).xyz()),
7748                Body::BipedLarge(_) => self
7749                    .states
7750                    .biped_large_states
7751                    .get(&entity)
7752                    .map(|state| &state.computed_skeleton)
7753                    .map(|skeleton| (skeleton.jaw * Vec4::new(0.0, 4.0, 0.0, 1.0)).xyz()),
7754                Body::BipedSmall(_) => self
7755                    .states
7756                    .biped_small_states
7757                    .get(&entity)
7758                    .map(|state| &state.computed_skeleton)
7759                    .map(|skeleton| (skeleton.head * Vec4::new(0.0, 0.0, 0.0, 1.0)).xyz()),
7760                Body::Golem(_) => self
7761                    .states
7762                    .golem_states
7763                    .get(&entity)
7764                    .map(|state| &state.computed_skeleton)
7765                    .map(|skeleton| (skeleton.head * Vec4::new(0.0, 0.0, 5.0, 1.0)).xyz()),
7766                Body::Theropod(_) => self
7767                    .states
7768                    .theropod_states
7769                    .get(&entity)
7770                    .map(|state| &state.computed_skeleton)
7771                    .map(|skeleton| (skeleton.head * Vec4::new(0.0, 2.0, 0.0, 1.0)).xyz()),
7772                Body::QuadrupedLow(_) => self
7773                    .states
7774                    .quadruped_low_states
7775                    .get(&entity)
7776                    .map(|state| &state.computed_skeleton)
7777                    .map(|skeleton| (skeleton.head_c_upper * Vec4::new(0.0, 4.0, 1.0, 1.0)).xyz()),
7778                Body::Arthropod(_) => self
7779                    .states
7780                    .arthropod_states
7781                    .get(&entity)
7782                    .map(|state| &state.computed_skeleton)
7783                    .map(|skeleton| (skeleton.head * Vec4::new(0.0, 7.0, 0.0, 1.0)).xyz()),
7784                Body::Object(_) => None,
7785                Body::Ship(_) => None,
7786                Body::Item(_) => None,
7787                Body::Crustacean(_) => self
7788                    .states
7789                    .crustacean_states
7790                    .get(&entity)
7791                    .map(|state| &state.computed_skeleton)
7792                    .map(|skeleton| (skeleton.chest * Vec4::new(0.0, 7.0, 0.0, 1.0)).xyz()),
7793                Body::Plugin(_) => {
7794                    #[cfg(not(feature = "plugins"))]
7795                    unreachable!("Plugins require feature");
7796                    #[cfg(feature = "plugins")]
7797                    {
7798                        self.states
7799                            .plugin_states
7800                            .get(&entity)
7801                            .map(|state| &state.computed_skeleton)
7802                            .map(|skeleton| (skeleton.bone0 * Vec4::new(0.0, 3.0, 0.0, 1.0)).xyz())
7803                    }
7804                },
7805            })
7806            .unwrap_or_else(Vec3::zero)
7807    }
7808
7809    pub fn lantern_mat(&self, entity: EcsEntity) -> Option<Mat4<f32>> {
7810        self.states
7811            .character_states
7812            .get(&entity)
7813            .map(|state| state.computed_skeleton.lantern)
7814    }
7815
7816    pub fn mount_transform(
7817        &self,
7818        scene_data: &SceneData,
7819        entity: EcsEntity,
7820    ) -> Option<Transform<f32, f32, f32>> {
7821        scene_data
7822            .state
7823            .ecs()
7824            .read_storage::<Body>()
7825            .get(entity)
7826            .and_then(|body| match body {
7827                Body::Humanoid(_) => self.states.character_states.get(&entity).map(|state| {
7828                    character::mount_transform(&state.computed_skeleton, &state.skeleton)
7829                }),
7830                Body::QuadrupedSmall(b) => {
7831                    self.states
7832                        .quadruped_small_states
7833                        .get(&entity)
7834                        .map(|state| {
7835                            quadruped_small::mount_transform(
7836                                b,
7837                                &state.computed_skeleton,
7838                                &state.skeleton,
7839                            )
7840                        })
7841                },
7842                Body::QuadrupedMedium(b) => {
7843                    self.states
7844                        .quadruped_medium_states
7845                        .get(&entity)
7846                        .map(|state| {
7847                            quadruped_medium::mount_transform(
7848                                b,
7849                                &state.computed_skeleton,
7850                                &state.skeleton,
7851                            )
7852                        })
7853                },
7854                Body::BirdMedium(b) => self.states.bird_medium_states.get(&entity).map(|state| {
7855                    bird_medium::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7856                }),
7857                Body::FishMedium(b) => self.states.fish_medium_states.get(&entity).map(|state| {
7858                    fish_medium::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7859                }),
7860                Body::Dragon(b) => self.states.dragon_states.get(&entity).map(|state| {
7861                    dragon::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7862                }),
7863                Body::BirdLarge(b) => self.states.bird_large_states.get(&entity).map(|state| {
7864                    bird_large::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7865                }),
7866                Body::FishSmall(b) => self.states.fish_small_states.get(&entity).map(|state| {
7867                    fish_small::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7868                }),
7869                Body::BipedLarge(b) => self.states.biped_large_states.get(&entity).map(|state| {
7870                    biped_large::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7871                }),
7872                Body::BipedSmall(b) => self.states.biped_small_states.get(&entity).map(|state| {
7873                    biped_small::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7874                }),
7875                Body::Golem(b) => self.states.golem_states.get(&entity).map(|state| {
7876                    golem::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7877                }),
7878                Body::Theropod(b) => self.states.theropod_states.get(&entity).map(|state| {
7879                    theropod::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7880                }),
7881                Body::QuadrupedLow(b) => {
7882                    self.states.quadruped_low_states.get(&entity).map(|state| {
7883                        quadruped_low::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7884                    })
7885                },
7886                Body::Arthropod(b) => self.states.arthropod_states.get(&entity).map(|state| {
7887                    arthropod::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7888                }),
7889                Body::Object(_) => None,
7890                Body::Ship(_) => None,
7891                Body::Item(_) => None,
7892                Body::Crustacean(b) => self.states.crustacean_states.get(&entity).map(|state| {
7893                    crustacean::mount_transform(b, &state.computed_skeleton, &state.skeleton)
7894                }),
7895                Body::Plugin(_) => {
7896                    #[cfg(not(feature = "plugins"))]
7897                    unreachable!("Plugins require feature");
7898                    #[cfg(feature = "plugins")]
7899                    Some(Transform {
7900                        position: body.mount_offset().into_tuple().into(),
7901                        ..Default::default()
7902                    })
7903                },
7904            })
7905    }
7906
7907    fn trail_points(
7908        &self,
7909        scene_data: &SceneData,
7910        entity: EcsEntity,
7911        main_trail: bool,
7912    ) -> Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)> {
7913        let transform_trail = |mat: Mat4<f32>, trail: (Vec3<f32>, Vec3<f32>)| {
7914            (mat.mul_point(trail.0), mat.mul_point(trail.1))
7915        };
7916
7917        scene_data
7918            .state
7919            .ecs()
7920            .read_storage::<Body>()
7921            .get(entity)
7922            .and_then(|body| match body {
7923                Body::Humanoid(_) => self.states.character_states.get(&entity).and_then(|state| {
7924                    let weapon_offsets = |slot| {
7925                        scene_data
7926                            .state
7927                            .ecs()
7928                            .read_storage::<Inventory>()
7929                            .get(entity)
7930                            .and_then(|inv| inv.equipped(slot))
7931                            .and_then(|item| {
7932                                if let ItemKind::Tool(tool) = &*item.kind() {
7933                                    Some(tool.kind)
7934                                } else {
7935                                    None
7936                                }
7937                            })
7938                            .map(|tool_kind| {
7939                                let lengths = match tool_kind {
7940                                    ToolKind::Sword => (0.0, 29.25),
7941                                    ToolKind::Axe => (10.0, 19.25),
7942                                    ToolKind::Hammer => (10.0, 19.25),
7943                                    ToolKind::Staff => (10.0, 19.25),
7944                                    ToolKind::Sceptre => (10.0, 19.25),
7945                                    _ => (0.0, 0.0),
7946                                };
7947                                (
7948                                    Vec3::new(0.0, 0.0, lengths.0),
7949                                    Vec3::new(0.0, 0.0, lengths.1),
7950                                )
7951                            })
7952                    };
7953
7954                    let weapon_trails =
7955                        state.skeleton.main_weapon_trail || state.skeleton.off_weapon_trail;
7956                    if weapon_trails {
7957                        if state.skeleton.main_weapon_trail && main_trail {
7958                            weapon_offsets(EquipSlot::ActiveMainhand).map(|weapon_offsets| {
7959                                transform_trail(state.computed_skeleton.main, weapon_offsets)
7960                            })
7961                        } else if state.skeleton.off_weapon_trail && !main_trail {
7962                            weapon_offsets(EquipSlot::ActiveOffhand).map(|weapon_offsets| {
7963                                transform_trail(state.computed_skeleton.second, weapon_offsets)
7964                            })
7965                        } else {
7966                            None
7967                        }
7968                    } else if state.skeleton.glider_trails {
7969                        // Offsets
7970                        const GLIDER_VERT: f32 = 5.0;
7971                        const GLIDER_HORIZ: f32 = 15.0;
7972                        // Trail width
7973                        const GLIDER_WIDTH: f32 = 1.0;
7974
7975                        if main_trail {
7976                            Some(transform_trail(
7977                                state.computed_skeleton.glider,
7978                                (
7979                                    Vec3::new(GLIDER_HORIZ, 0.0, GLIDER_VERT),
7980                                    Vec3::new(GLIDER_HORIZ + GLIDER_WIDTH, 0.0, GLIDER_VERT),
7981                                ),
7982                            ))
7983                        } else {
7984                            Some(transform_trail(
7985                                state.computed_skeleton.glider,
7986                                (
7987                                    Vec3::new(-GLIDER_HORIZ, 0.0, GLIDER_VERT),
7988                                    Vec3::new(-(GLIDER_HORIZ + GLIDER_WIDTH), 0.0, GLIDER_VERT),
7989                                ),
7990                            ))
7991                        }
7992                    } else {
7993                        None
7994                    }
7995                }),
7996                Body::Ship(b) => self.states.ship_states.get(&entity).and_then(|state| {
7997                    let attr = anim::ship::SkeletonAttr::from(b);
7998                    let propeller_trail = |length| {
7999                        (
8000                            Vec3::new(0.0, 0.0, length * 0.5),
8001                            Vec3::new(0.0, 0.0, length),
8002                        )
8003                    };
8004
8005                    if main_trail {
8006                        attr.bone1_prop_trail_offset.map(|length| {
8007                            transform_trail(state.computed_skeleton.bone1, propeller_trail(length))
8008                        })
8009                    } else {
8010                        attr.bone2_prop_trail_offset.map(|length| {
8011                            transform_trail(state.computed_skeleton.bone2, propeller_trail(length))
8012                        })
8013                    }
8014                }),
8015                _ => None,
8016            })
8017    }
8018
8019    pub fn figure_count(&self) -> usize { self.states.count() }
8020
8021    pub fn figure_count_visible(&self) -> usize { self.states.count_visible() }
8022}
8023
8024pub struct FigureAtlas {
8025    allocator: AtlasAllocator,
8026    // atlas_texture: Texture<ColLightFmt>,
8027}
8028
8029impl FigureAtlas {
8030    pub fn new(renderer: &mut Renderer) -> Self {
8031        let allocator =
8032            Self::make_allocator(renderer).expect("Failed to create texture atlas for figures");
8033        Self {
8034            allocator, /* atlas_texture, */
8035        }
8036    }
8037
8038    /// Find the correct texture for this model entry.
8039    pub fn texture<'a, const N: usize>(
8040        &'a self,
8041        model: ModelEntryRef<'a, N>,
8042    ) -> &'a AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData> {
8043        /* &self.atlas_texture */
8044        model.atlas_textures()
8045    }
8046
8047    /// NOTE: Panics if the opaque model's length does not fit in a u32.
8048    /// This is part of the function contract.
8049    ///
8050    /// NOTE: Panics if the vertex range bounds are not in range of the opaque
8051    /// model stored in the BoneMeshes parameter.  This is part of the
8052    /// function contract.
8053    ///
8054    /// NOTE: Panics if the provided mesh is empty. FIXME: do something else
8055    pub fn create_figure<const N: usize>(
8056        &mut self,
8057        renderer: &mut Renderer,
8058        atlas_texture_data: FigureSpriteAtlasData,
8059        atlas_size: Vec2<u16>,
8060        (opaque, bounds): (Mesh<TerrainVertex>, math::Aabb<f32>),
8061        vertex_ranges: [Range<u32>; N],
8062    ) -> FigureModelEntry<N> {
8063        span!(_guard, "create_figure", "FigureColLights::create_figure");
8064        let allocator = &mut self.allocator;
8065        let allocation = allocator
8066            .allocate(guillotiere::Size::new(
8067                atlas_size.x as i32,
8068                atlas_size.y as i32,
8069            ))
8070            .expect("Not yet implemented: allocate new atlas on allocation failure.");
8071        let [atlas_textures] = atlas_texture_data.create_textures(renderer, atlas_size);
8072        let atlas_textures = renderer.figure_bind_atlas_textures(atlas_textures);
8073        let model_len = u32::try_from(opaque.vertices().len())
8074            .expect("The model size for this figure does not fit in a u32!");
8075        let model = renderer.create_model(&opaque);
8076
8077        vertex_ranges.iter().for_each(|range| {
8078            assert!(
8079                range.start <= range.end && range.end <= model_len,
8080                "The provided vertex range for figure mesh {:?} does not fit in the model, which \
8081                 is of size {:?}!",
8082                range,
8083                model_len
8084            );
8085        });
8086
8087        FigureModelEntry {
8088            _bounds: bounds,
8089            allocation,
8090            atlas_textures,
8091            lod_vertex_ranges: vertex_ranges,
8092            model: FigureModel { opaque: model },
8093        }
8094    }
8095
8096    /// NOTE: Panics if the opaque model's length does not fit in a u32.
8097    /// This is part of the function contract.
8098    ///
8099    /// NOTE: Panics if the vertex range bounds are not in range of the opaque
8100    /// model stored in the BoneMeshes parameter.  This is part of the
8101    /// function contract.
8102    ///
8103    /// NOTE: Panics if the provided mesh is empty. FIXME: do something else
8104    pub fn create_terrain<const N: usize>(
8105        &mut self,
8106        renderer: &mut Renderer,
8107        // TODO: Use `TerrainAtlasData`
8108        atlas_texture_data: FigureSpriteAtlasData,
8109        atlas_size: Vec2<u16>,
8110        (opaque, bounds): (Mesh<TerrainVertex>, math::Aabb<f32>),
8111        vertex_ranges: [Range<u32>; N],
8112        sprite_instances: [Vec<SpriteInstance>; SPRITE_LOD_LEVELS],
8113        blocks_of_interest: BlocksOfInterest,
8114        blocks_offset: Vec3<f32>,
8115    ) -> TerrainModelEntry<N> {
8116        span!(_guard, "create_figure", "FigureColLights::create_figure");
8117        let allocator = &mut self.allocator;
8118        let allocation = allocator
8119            .allocate(guillotiere::Size::new(
8120                atlas_size.x as i32,
8121                atlas_size.y as i32,
8122            ))
8123            .expect("Not yet implemented: allocate new atlas on allocation failure.");
8124        let [col_lights] = atlas_texture_data.create_textures(renderer, atlas_size);
8125        // TODO: Use `kinds` texture for volume entities
8126        let atlas_textures = renderer.figure_bind_atlas_textures(col_lights);
8127        let model_len = u32::try_from(opaque.vertices().len())
8128            .expect("The model size for this figure does not fit in a u32!");
8129        let model = renderer.create_model(&opaque);
8130
8131        vertex_ranges.iter().for_each(|range| {
8132            assert!(
8133                range.start <= range.end && range.end <= model_len,
8134                "The provided vertex range for figure mesh {:?} does not fit in the model, which \
8135                 is of size {:?}!",
8136                range,
8137                model_len
8138            );
8139        });
8140
8141        let sprite_instances =
8142            sprite_instances.map(|instances| renderer.create_instances(&instances));
8143
8144        TerrainModelEntry {
8145            _bounds: bounds,
8146            allocation,
8147            atlas_textures,
8148            lod_vertex_ranges: vertex_ranges,
8149            model: FigureModel { opaque: model },
8150            sprite_instances,
8151            blocks_of_interest,
8152            blocks_offset,
8153        }
8154    }
8155
8156    fn make_allocator(renderer: &mut Renderer) -> Result<AtlasAllocator, RenderError> {
8157        let max_texture_size = renderer.max_texture_size();
8158        let atlas_size = guillotiere::Size::new(max_texture_size as i32, max_texture_size as i32);
8159        let allocator = AtlasAllocator::with_options(atlas_size, &guillotiere::AllocatorOptions {
8160            // TODO: Verify some good empirical constants.
8161            small_size_threshold: 32,
8162            large_size_threshold: 256,
8163            ..guillotiere::AllocatorOptions::default()
8164        });
8165        // TODO: Consider using a single texture atlas to store all figures, much like
8166        // we do for terrain chunks.  We previously avoided this due to
8167        // perceived performance degradation for the figure use case, but with a
8168        // smaller atlas size this may be less likely.
8169        /* let texture = renderer.create_texture_raw(
8170            gfx::texture::Kind::D2(
8171                max_texture_size,
8172                max_texture_size,
8173                gfx::texture::AaMode::Single,
8174            ),
8175            1 as gfx::texture::Level,
8176            gfx::memory::Bind::SHADER_RESOURCE,
8177            gfx::memory::Usage::Dynamic,
8178            (0, 0),
8179            gfx::format::Swizzle::new(),
8180            gfx::texture::SamplerInfo::new(
8181                gfx::texture::FilterMethod::Bilinear,
8182                gfx::texture::WrapMode::Clamp,
8183            ),
8184        )?;
8185        Ok((atlas, texture)) */
8186        Ok(allocator)
8187    }
8188}
8189
8190pub struct FigureStateMeta {
8191    pub primary_abs_trail_points: Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)>,
8192    pub secondary_abs_trail_points: Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)>,
8193    // Contains the position of this figure or if it is a rider it will contain the mount's
8194    // mount_world_pos
8195    // Unlike the interpolated position stored in the ecs this will be propagated along
8196    // mount chains
8197    // For use if it is mounted by another figure
8198    mount_world_pos: anim::vek::Vec3<f32>,
8199    state_time: f32,
8200    last_ori: anim::vek::Quaternion<f32>,
8201    lpindex: u8,
8202    can_shadow_sun: bool,
8203    can_occlude_rain: bool,
8204    visible: bool,
8205    last_pos: Option<anim::vek::Vec3<f32>>,
8206    avg_vel: anim::vek::Vec3<f32>,
8207    last_light: f32,
8208    last_glow: (Vec3<f32>, f32),
8209    acc_vel: f32,
8210    bound: pipelines::figure::BoundLocals,
8211}
8212
8213impl FigureStateMeta {
8214    pub fn visible(&self) -> bool { self.visible }
8215
8216    pub fn can_shadow_sun(&self) -> bool {
8217        // Either visible, or explicitly a shadow caster.
8218        self.visible || self.can_shadow_sun
8219    }
8220
8221    pub fn can_occlude_rain(&self) -> bool {
8222        // Either visible, or explicitly a rain occluder.
8223        self.visible || self.can_occlude_rain
8224    }
8225
8226    /// Due to a quirk of the way mount animations work, animation offsets do
8227    /// not always correspond to world-space offsets when mounted. This
8228    /// function allows calculating the world-space.
8229    pub fn wpos_of(&self, figure_offs: Vec3<f32>) -> Vec3<f32> {
8230        // Calculate the correct offset given a figure offset
8231        self.mount_world_pos + anim::vek::Vec3::from(figure_offs.into_array())
8232    }
8233}
8234
8235pub struct FigureState<S: Skeleton, D = ()> {
8236    meta: FigureStateMeta,
8237    pub skeleton: S,
8238    pub computed_skeleton: S::ComputedSkeleton,
8239    pub extra: D,
8240}
8241
8242impl<S: Skeleton, D> Deref for FigureState<S, D> {
8243    type Target = FigureStateMeta;
8244
8245    fn deref(&self) -> &Self::Target { &self.meta }
8246}
8247
8248impl<S: Skeleton, D> DerefMut for FigureState<S, D> {
8249    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.meta }
8250}
8251
8252/// Parameters that don't depend on the body variant or animation results and
8253/// are also not mutable
8254pub struct FigureUpdateCommonParameters<'a> {
8255    pub entity: Option<EcsEntity>,
8256    pub pos: anim::vek::Vec3<f32>,
8257    pub ori: anim::vek::Quaternion<f32>,
8258    pub scale: f32,
8259    pub mount_transform_pos: Option<(anim::vek::Transform<f32, f32, f32>, anim::vek::Vec3<f32>)>,
8260    pub body: Option<Body>,
8261    pub col: Rgba<f32>,
8262    pub dt: f32,
8263    pub is_player: bool,
8264    pub terrain: Option<&'a Terrain>,
8265    pub ground_vel: Vec3<f32>,
8266    pub primary_trail_points: Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)>,
8267    pub secondary_trail_points: Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)>,
8268}
8269
8270pub trait FigureData: Sized {
8271    fn new(renderer: &mut Renderer) -> Self;
8272
8273    fn update(&mut self, renderer: &mut Renderer, parameters: &FigureUpdateCommonParameters);
8274}
8275
8276impl FigureData for () {
8277    fn new(_renderer: &mut Renderer) {}
8278
8279    fn update(&mut self, _renderer: &mut Renderer, _parameters: &FigureUpdateCommonParameters) {}
8280}
8281
8282impl FigureData for BoundTerrainLocals {
8283    fn new(renderer: &mut Renderer) -> Self {
8284        renderer.create_terrain_bound_locals(&[TerrainLocals::new(
8285            Vec3::zero(),
8286            Quaternion::identity(),
8287            Vec2::zero(),
8288            0.0,
8289        )])
8290    }
8291
8292    fn update(&mut self, renderer: &mut Renderer, parameters: &FigureUpdateCommonParameters) {
8293        renderer.update_consts(self, &[TerrainLocals::new(
8294            parameters.pos,
8295            parameters.ori.into_vec4().into(),
8296            Vec2::zero(),
8297            0.0,
8298        )])
8299    }
8300}
8301
8302impl<S: Skeleton, D: FigureData> FigureState<S, D> {
8303    pub fn new(renderer: &mut Renderer, skeleton: S, body: S::Body) -> Self {
8304        let mut buf = [Default::default(); anim::MAX_BONE_COUNT];
8305        let computed_skeleton =
8306            anim::compute_matrices(&skeleton, anim::vek::Mat4::identity(), &mut buf, body);
8307        let bone_consts = figure_bone_data_from_anim(&buf);
8308        Self {
8309            meta: FigureStateMeta {
8310                primary_abs_trail_points: None,
8311                secondary_abs_trail_points: None,
8312                mount_world_pos: anim::vek::Vec3::zero(),
8313                state_time: 0.0,
8314                last_ori: Ori::default().into(),
8315                lpindex: 0,
8316                visible: false,
8317                can_shadow_sun: false,
8318                can_occlude_rain: false,
8319                last_pos: None,
8320                avg_vel: anim::vek::Vec3::zero(),
8321                last_light: 1.0,
8322                last_glow: (Vec3::zero(), 0.0),
8323                acc_vel: 0.0,
8324                bound: renderer.create_figure_bound_locals(&[FigureLocals::default()], bone_consts),
8325            },
8326            skeleton,
8327            computed_skeleton,
8328            extra: D::new(renderer),
8329        }
8330    }
8331
8332    pub fn update(
8333        &mut self,
8334        renderer: &mut Renderer,
8335        trail_mgr: Option<&mut TrailMgr>,
8336        buf: &mut [anim::FigureBoneData; anim::MAX_BONE_COUNT],
8337        parameters @ FigureUpdateCommonParameters {
8338            entity,
8339            pos,
8340            ori,
8341            scale,
8342            mount_transform_pos,
8343            body,
8344            col,
8345            dt,
8346            is_player,
8347            terrain,
8348            ground_vel,
8349            primary_trail_points,
8350            secondary_trail_points,
8351        }: &FigureUpdateCommonParameters,
8352        state_animation_rate: f32,
8353        model: Option<&impl ModelEntry>,
8354        // TODO: there is the potential to drop the optional body from the common params and just
8355        // use this one but we need to add a function to the skelton trait or something in order to
8356        // get the rider offset
8357        skel_body: S::Body,
8358    ) {
8359        span!(_guard, "update", "FigureState::update");
8360
8361        // NOTE: As long as update() always gets called after get_or_create_model(), and
8362        // visibility is not set again until after the model is rendered, we
8363        // know we don't pair the character model with invalid model state.
8364        //
8365        // Currently, the only exception to this during normal gameplay is in the very
8366        // first tick after a model is created (so there's no `last_character`
8367        // state).  So in theory, we could have incorrect model data during this
8368        // tick.  It is possible to resolve this in a few ways, but since
8369        // currently we don't actually use the model state for anything, we
8370        // currently ignore this potential issue.
8371        //
8372        // FIXME: Address the above at some point.
8373        let model = if let Some(model) = model {
8374            model
8375        } else {
8376            self.visible = false;
8377            return;
8378        };
8379
8380        // Approximate as a sphere with radius equal to the
8381        // largest dimension (if we were exact, it should just be half the largest
8382        // dimension, but we're not, so we double it and use size() instead of
8383        // half_size()).
8384        /* let radius = vek::Extent3::<f32>::from(model.bounds.half_size()).reduce_partial_max();
8385        let _bounds = BoundingSphere::new(pos.into_array(), scale * 0.8 * radius); */
8386
8387        self.last_ori = Lerp::lerp(self.last_ori, *ori, 15.0 * dt).normalized();
8388
8389        self.state_time += dt * state_animation_rate / scale;
8390
8391        let mat = {
8392            let scale_mat = anim::vek::Mat4::scaling_3d(anim::vek::Vec3::from(*scale));
8393            if let Some((transform, _)) = *mount_transform_pos {
8394                // Note: if we had a way to compute a "default" transform of the bones then in
8395                // the animations we could make use of the mount_offset from common by
8396                // computing what the offset of the rider is from the mounted
8397                // bone in its default position when the rider has the mount
8398                // offset in common applied to it. Since we don't have this
8399                // right now we instead need to recreate the same effect in the
8400                // animations and keep it in sync.
8401                //
8402                // Component of mounting offset specific to the rider.
8403                let rider_offset = anim::vek::Mat4::<f32>::translation_3d(
8404                    body.map_or_else(Vec3::zero, |b| b.rider_offset()),
8405                );
8406
8407                // NOTE: It is kind of a hack to use this entity's ori here if it is
8408                // mounted on another but this happens to match the ori of the
8409                // mount so it works, change this if it causes jankiness in the future.
8410                let transform = anim::vek::Transform {
8411                    orientation: *ori * transform.orientation,
8412                    ..transform
8413                };
8414                anim::vek::Mat4::from(transform) * rider_offset * scale_mat
8415            } else {
8416                let ori_mat = anim::vek::Mat4::from(*ori);
8417                ori_mat * scale_mat
8418            }
8419        };
8420
8421        let atlas_offs = model.allocation().rectangle.min;
8422
8423        let (light, glow) = terrain
8424            .map(|t| {
8425                span!(
8426                    _guard,
8427                    "light_glow",
8428                    "FigureState::update (fetch light/glow)"
8429                );
8430                // Sample the location a little above to avoid clipping into terrain
8431                // TODO: Try to make this faster? It might be fine though
8432                let wpos = Vec3::from(pos.into_array()) + Vec3::unit_z();
8433
8434                let wposi = wpos.map(|e: f32| e.floor() as i32);
8435
8436                // TODO: Fix this up enough to make it work
8437                /*
8438                let sample = |off| {
8439                    let off = off * wpos.map(|e| (e.fract() - 0.5).signum() as i32);
8440                    Vec2::new(t.light_at_wpos(wposi + off), t.glow_at_wpos(wposi + off))
8441                };
8442
8443                let s_000 = sample(Vec3::new(0, 0, 0));
8444                let s_100 = sample(Vec3::new(1, 0, 0));
8445                let s_010 = sample(Vec3::new(0, 1, 0));
8446                let s_110 = sample(Vec3::new(1, 1, 0));
8447                let s_001 = sample(Vec3::new(0, 0, 1));
8448                let s_101 = sample(Vec3::new(1, 0, 1));
8449                let s_011 = sample(Vec3::new(0, 1, 1));
8450                let s_111 = sample(Vec3::new(1, 1, 1));
8451                let s_00 = Lerp::lerp(s_000, s_001, (wpos.z.fract() - 0.5).abs() * 2.0);
8452                let s_10 = Lerp::lerp(s_100, s_101, (wpos.z.fract() - 0.5).abs() * 2.0);
8453                let s_01 = Lerp::lerp(s_010, s_011, (wpos.z.fract() - 0.5).abs() * 2.0);
8454                let s_11 = Lerp::lerp(s_110, s_111, (wpos.z.fract() - 0.5).abs() * 2.0);
8455                let s_0 = Lerp::lerp(s_00, s_01, (wpos.y.fract() - 0.5).abs() * 2.0);
8456                let s_1 = Lerp::lerp(s_10, s_11, (wpos.y.fract() - 0.5).abs() * 2.0);
8457                let s = Lerp::lerp(s_10, s_11, (wpos.x.fract() - 0.5).abs() * 2.0);
8458                */
8459
8460                (t.light_at_wpos(wposi), t.glow_normal_at_wpos(wpos))
8461            })
8462            .unwrap_or((1.0, (Vec3::zero(), 0.0)));
8463        // Fade between light and glow levels
8464        // TODO: Making this temporal rather than spatial is a bit dumb but it's a very
8465        // subtle difference
8466        self.last_light = Lerp::lerp(self.last_light, light, 16.0 * dt);
8467        self.last_glow.0 = Lerp::lerp(self.last_glow.0, glow.0, 16.0 * dt);
8468        self.last_glow.1 = Lerp::lerp(self.last_glow.1, glow.1, 16.0 * dt);
8469
8470        let pos_with_mount_offset = mount_transform_pos.map_or(*pos, |(_, pos)| pos);
8471
8472        let locals = FigureLocals::new(
8473            mat,
8474            col.rgb(),
8475            pos_with_mount_offset,
8476            Vec2::new(atlas_offs.x, atlas_offs.y),
8477            *is_player,
8478            self.last_light,
8479            self.last_glow,
8480        );
8481        renderer.update_consts(&mut self.meta.bound.0, &[locals]);
8482
8483        self.computed_skeleton = anim::compute_matrices(&self.skeleton, mat, buf, skel_body);
8484
8485        let new_bone_consts = figure_bone_data_from_anim(buf);
8486
8487        renderer.update_consts(&mut self.meta.bound.1, &new_bone_consts[0..S::BONE_COUNT]);
8488
8489        fn handle_trails(
8490            trail_mgr: &mut TrailMgr,
8491            new_rel_trail_points: Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)>,
8492            old_abs_trail_points: &mut Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)>,
8493            entity: EcsEntity,
8494            primary_trail: bool,
8495            pos: anim::vek::Vec3<f32>,
8496        ) {
8497            let new_abs_trail_points =
8498                new_rel_trail_points.map(|(start, end)| (start + pos, end + pos));
8499
8500            if let (Some((p1, p2)), Some((p4, p3))) = (&old_abs_trail_points, new_abs_trail_points)
8501            {
8502                let trail_mgr_offset = trail_mgr.offset();
8503                let quad_mesh = trail_mgr.entity_mesh_or_insert(entity, primary_trail);
8504                let vertex = |p: anim::vek::Vec3<f32>| trail::Vertex {
8505                    pos: p.into_array(),
8506                };
8507                let quad = Quad::new(vertex(*p1), vertex(*p2), vertex(p3), vertex(p4));
8508                quad_mesh.replace_quad(trail_mgr_offset * 4, quad);
8509            }
8510            *old_abs_trail_points = new_abs_trail_points;
8511        }
8512
8513        if let (Some(trail_mgr), Some(entity)) = (trail_mgr, entity) {
8514            handle_trails(
8515                trail_mgr,
8516                *primary_trail_points,
8517                &mut self.primary_abs_trail_points,
8518                *entity,
8519                true,
8520                pos_with_mount_offset,
8521            );
8522            handle_trails(
8523                trail_mgr,
8524                *secondary_trail_points,
8525                &mut self.secondary_abs_trail_points,
8526                *entity,
8527                false,
8528                pos_with_mount_offset,
8529            );
8530        }
8531
8532        // TODO: compute the mount bone only when it is needed
8533        self.mount_world_pos = pos_with_mount_offset;
8534
8535        let smoothing = (5.0 * dt).min(1.0);
8536        if let Some(last_pos) = self.last_pos {
8537            self.avg_vel = (1.0 - smoothing) * self.avg_vel + smoothing * (pos - last_pos) / *dt;
8538        }
8539        self.last_pos = Some(*pos);
8540
8541        // Can potentially overflow
8542        if self.avg_vel.magnitude_squared() != 0.0 {
8543            // Vehicle wheels can turn backwards, and don't turn if movement is lateral to
8544            // the orientation
8545            let rate = if matches!(body, Some(Body::Ship(ship)) if ship.has_wheels()) {
8546                (self.avg_vel - *ground_vel).dot(*ori * Vec3::unit_y())
8547            } else {
8548                (self.avg_vel - *ground_vel).magnitude()
8549            };
8550            self.acc_vel += rate * dt / scale;
8551        } else {
8552            self.acc_vel = 0.0;
8553        }
8554        self.extra.update(renderer, parameters);
8555    }
8556
8557    pub fn bound(&self) -> &pipelines::figure::BoundLocals { &self.bound }
8558}
8559
8560fn figure_bone_data_from_anim(
8561    mats: &[anim::FigureBoneData; anim::MAX_BONE_COUNT],
8562) -> &[FigureBoneData] {
8563    bytemuck::cast_slice(mats)
8564}