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