Skip to main content

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