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