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