veloren_voxygen/scene/figure/
mod.rs

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