veloren_voxygen/scene/figure/
mod.rs

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