Skip to main content

veloren_voxygen/scene/figure/
mod.rs

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