veloren_voxygen/scene/figure/
mod.rs

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