veloren_voxygen/scene/figure/
mod.rs

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