veloren_voxygen/scene/figure/
cache.rs

1use super::{
2    FigureModelEntry, ModelEntry, TerrainModelEntry,
3    load::{BodySpec, ShipBoneMeshes},
4};
5use crate::{
6    mesh::{
7        greedy::GreedyMesh,
8        segment::{generate_mesh_base_vol_figure, generate_mesh_base_vol_terrain},
9    },
10    render::{
11        BoneMeshes, FigureModel, FigureSpriteAtlasData, Instances, Mesh, Renderer, SpriteInstance,
12        TerrainVertex, pipelines,
13    },
14    scene::{
15        camera::CameraMode,
16        terrain::{BlocksOfInterest, SPRITE_LOD_LEVELS, SpriteRenderState, get_sprite_instances},
17    },
18};
19use anim::Skeleton;
20use common::{
21    assets::ReloadWatcher,
22    comp::{
23        CharacterState,
24        inventory::{
25            Inventory,
26            slot::{ArmorSlot, EquipSlot},
27        },
28        item::{Item, ItemDefinitionId, item_key::ItemKey, modular},
29    },
30    figure::{Segment, TerrainSegment},
31    slowjob::SlowJobPool,
32    vol::{BaseVol, IntoVolIterator, ReadVol},
33};
34use core::{hash::Hash, ops::Range};
35use crossbeam_utils::atomic;
36use hashbrown::{HashMap, hash_map::Entry};
37use serde::Deserialize;
38use std::{array::from_fn, sync::Arc};
39use vek::*;
40
41/// A type produced by mesh worker threads corresponding to the information
42/// needed to mesh figures.
43pub struct MeshWorkerResponse<const N: usize> {
44    atlas_texture_data: FigureSpriteAtlasData,
45    atlas_size: Vec2<u16>,
46    opaque: Mesh<TerrainVertex>,
47    bounds: anim::vek::Aabb<f32>,
48    vertex_range: [Range<u32>; N],
49}
50
51/// A type produced by mesh worker threads corresponding to the information
52/// needed to mesh figures.
53pub struct TerrainMeshWorkerResponse<const N: usize> {
54    // TODO: This probably needs fixing to use `TerrainAtlasData`. However, right now, we just
55    // treat volume entities like regular figures for the sake of rendering.
56    atlas_texture_data: FigureSpriteAtlasData,
57    atlas_size: Vec2<u16>,
58    opaque: Mesh<TerrainVertex>,
59    bounds: anim::vek::Aabb<f32>,
60    vertex_range: [Range<u32>; N],
61    sprite_instances: [Vec<SpriteInstance>; SPRITE_LOD_LEVELS],
62    blocks_of_interest: BlocksOfInterest,
63    blocks_offset: Vec3<f32>,
64}
65
66/// NOTE: To test this cell for validity, we currently first use
67/// Arc::get_mut(), and then only if that succeeds do we call AtomicCell::take.
68/// This way, we avoid all atomic updates for the fast path read in the "not yet
69/// updated" case (though it would be faster without weak pointers); since once
70/// it's updated, we switch from `Pending` to `Done`, this is only suboptimal
71/// for one frame.
72pub type MeshWorkerCell<const N: usize> = atomic::AtomicCell<Option<MeshWorkerResponse<N>>>;
73pub type TerrainMeshWorkerCell<const N: usize> =
74    atomic::AtomicCell<Option<TerrainMeshWorkerResponse<N>>>;
75
76pub trait ModelEntryFuture<const N: usize> {
77    type ModelEntry: ModelEntry;
78
79    // TODO: is there a potential use for this?
80    fn into_done(self) -> Option<Self::ModelEntry>;
81
82    fn get_done(&self) -> Option<&Self::ModelEntry>;
83}
84
85/// A future FigureModelEntryLod.
86#[expect(clippy::large_enum_variant)]
87pub enum FigureModelEntryFuture<const N: usize> {
88    /// We can poll the future to see whether the figure model is ready.
89    // TODO: See if we can find away to either get rid of this Arc, or reuse Arcs across different
90    // figures.  Updates to uvth for thread pool shared storage might obviate this requirement.
91    Pending(Arc<MeshWorkerCell<N>>),
92    /// Stores the already-meshed model.
93    Done(FigureModelEntry<N>),
94}
95
96impl<const N: usize> ModelEntryFuture<N> for FigureModelEntryFuture<N> {
97    type ModelEntry = FigureModelEntry<N>;
98
99    fn into_done(self) -> Option<Self::ModelEntry> {
100        match self {
101            Self::Pending(_) => None,
102            Self::Done(d) => Some(d),
103        }
104    }
105
106    fn get_done(&self) -> Option<&Self::ModelEntry> {
107        match self {
108            Self::Pending(_) => None,
109            Self::Done(d) => Some(d),
110        }
111    }
112}
113
114/// A future TerrainModelEntryLod.
115#[expect(clippy::large_enum_variant)]
116pub enum TerrainModelEntryFuture<const N: usize> {
117    /// We can poll the future to see whether the figure model is ready.
118    // TODO: See if we can find away to either get rid of this Arc, or reuse Arcs across different
119    // figures.  Updates to uvth for thread pool shared storage might obviate this requirement.
120    Pending(Arc<TerrainMeshWorkerCell<N>>),
121    /// Stores the already-meshed model.
122    Done(TerrainModelEntry<N>),
123}
124
125impl<const N: usize> ModelEntryFuture<N> for TerrainModelEntryFuture<N> {
126    type ModelEntry = TerrainModelEntry<N>;
127
128    fn into_done(self) -> Option<Self::ModelEntry> {
129        match self {
130            Self::Pending(_) => None,
131            Self::Done(d) => Some(d),
132        }
133    }
134
135    fn get_done(&self) -> Option<&Self::ModelEntry> {
136        match self {
137            Self::Pending(_) => None,
138            Self::Done(d) => Some(d),
139        }
140    }
141}
142
143const LOD_COUNT: usize = 3;
144
145type FigureModelEntryLod<'b> = Option<&'b FigureModelEntry<LOD_COUNT>>;
146type TerrainModelEntryLod<'b> = Option<&'b TerrainModelEntry<LOD_COUNT>>;
147
148#[derive(Clone, Eq, Hash, PartialEq)]
149/// TODO: merge item_key and extra field into an enum
150pub struct FigureKey<Body> {
151    /// Body pointed to by this key.
152    pub body: Body,
153    /// Only used by Body::ItemDrop
154    pub item_key: Option<Arc<ItemKey>>,
155    /// Extra state.
156    pub extra: Option<Arc<CharacterCacheKey>>,
157}
158
159#[derive(Deserialize, Eq, Hash, PartialEq, Debug)]
160pub enum ToolKey {
161    Tool(String),
162    Modular(modular::ModularWeaponKey),
163}
164
165/// Character data that should be visible when tools are visible (i.e. in third
166/// person or when the character is in a tool-using state).
167#[derive(Eq, Hash, PartialEq)]
168pub(super) struct CharacterToolKey {
169    pub active: Option<ToolKey>,
170    pub second: Option<ToolKey>,
171}
172
173/// Character data that exists in third person only.
174#[derive(Eq, Hash, PartialEq)]
175pub(super) struct CharacterThirdPersonKey {
176    pub head: Option<String>,
177    pub shoulder: Option<String>,
178    pub chest: Option<String>,
179    pub belt: Option<String>,
180    pub back: Option<String>,
181    pub pants: Option<String>,
182}
183
184#[derive(Eq, Hash, PartialEq)]
185/// NOTE: To avoid spamming the character cache with player models, we try to
186/// store only the minimum information required to correctly update the model.
187///
188/// TODO: Memoize, etc.
189pub struct CharacterCacheKey {
190    /// Character state that is only visible in third person.
191    pub(super) third_person: Option<CharacterThirdPersonKey>,
192    /// Tool state should be present when a character is either in third person,
193    /// or is in first person and the character state is tool-using.
194    ///
195    /// NOTE: This representation could be tightened in various ways to
196    /// eliminate incorrect states, e.g. setting active_tool to None when no
197    /// tools are equipped, but currently we are more focused on the big
198    /// performance impact of recreating the whole model whenever the character
199    /// state changes, so for now we don't bother with this.
200    pub(super) tool: Option<CharacterToolKey>,
201    pub(super) lantern: Option<String>,
202    pub(super) glider: Option<String>,
203    pub(super) foot: Option<String>,
204    pub(super) head: Option<String>,
205    // None = invisible, Some(None) = no armour
206    pub(super) hand: Option<Option<String>>,
207}
208
209impl CharacterCacheKey {
210    pub fn from(
211        cs: Option<&CharacterState>,
212        camera_mode: CameraMode,
213        inventory: &Inventory,
214    ) -> Self {
215        let is_first_person = match camera_mode {
216            CameraMode::FirstPerson => true,
217            CameraMode::ThirdPerson | CameraMode::Freefly => false,
218        };
219
220        let key_from_slot = |slot| {
221            inventory
222                .equipped(slot)
223                .map(|i| i.item_definition_id())
224                .map(|id| match id {
225                    // TODO: Properly handle items with components here. Probably wait until modular
226                    // armor?
227                    ItemDefinitionId::Simple(id) => String::from(id),
228                    ItemDefinitionId::Compound { simple_base, .. } => String::from(simple_base),
229                    ItemDefinitionId::Modular { pseudo_base, .. } => String::from(pseudo_base),
230                })
231        };
232
233        // Third person tools are only modeled when the camera is either not first
234        // person, or the camera is first person and we are in a tool-using
235        // state.
236        let are_tools_visible = !is_first_person
237            || cs
238            .map(|cs| cs.is_attack() || cs.is_wield())
239            // If there's no provided character state but we're still somehow in first person,
240            // We currently assume there's no need to visually model tools.
241            //
242            // TODO: Figure out what to do here, and/or refactor how this works.
243            .unwrap_or(false);
244
245        // When in first-person, hide our hands to make aiming a bow easier
246        // TODO: Make this less of a hack
247        let are_hands_visible = !is_first_person || cs.map(|cs| !cs.is_ranged()).unwrap_or(true);
248
249        Self {
250            // Third person armor is only modeled when the camera mode is not first person.
251            third_person: if is_first_person {
252                None
253            } else {
254                Some(CharacterThirdPersonKey {
255                    head: key_from_slot(EquipSlot::Armor(ArmorSlot::Head)),
256                    shoulder: key_from_slot(EquipSlot::Armor(ArmorSlot::Shoulders)),
257                    chest: key_from_slot(EquipSlot::Armor(ArmorSlot::Chest)),
258                    belt: key_from_slot(EquipSlot::Armor(ArmorSlot::Belt)),
259                    back: key_from_slot(EquipSlot::Armor(ArmorSlot::Back)),
260                    pants: key_from_slot(EquipSlot::Armor(ArmorSlot::Legs)),
261                })
262            },
263            tool: if are_tools_visible {
264                let tool_key_from_item = |item: &Item| match item.item_definition_id() {
265                    ItemDefinitionId::Simple(id) => ToolKey::Tool(String::from(id)),
266                    ItemDefinitionId::Modular { .. } => {
267                        ToolKey::Modular(modular::weapon_to_key(item))
268                    },
269                    ItemDefinitionId::Compound { simple_base, .. } => {
270                        ToolKey::Tool(String::from(simple_base))
271                    },
272                };
273                Some(CharacterToolKey {
274                    active: inventory
275                        .equipped(EquipSlot::ActiveMainhand)
276                        .map(tool_key_from_item),
277                    second: inventory
278                        .equipped(EquipSlot::ActiveOffhand)
279                        .map(tool_key_from_item),
280                })
281            } else {
282                None
283            },
284            lantern: key_from_slot(EquipSlot::Lantern),
285            glider: key_from_slot(EquipSlot::Glider),
286            foot: key_from_slot(EquipSlot::Armor(ArmorSlot::Feet)),
287            head: key_from_slot(EquipSlot::Armor(ArmorSlot::Head)),
288            hand: if are_hands_visible {
289                Some(key_from_slot(EquipSlot::Armor(ArmorSlot::Hands)))
290            } else {
291                None
292            },
293        }
294    }
295}
296
297pub(crate) struct FigureModelCache<Skel>
298where
299    Skel: Skeleton,
300    Skel::Body: BodySpec,
301{
302    models: HashMap<
303        FigureKey<Skel::Body>,
304        (
305            (
306                <Skel::Body as BodySpec>::ModelEntryFuture<LOD_COUNT>,
307                Skel::Attr,
308            ),
309            u64,
310        ),
311    >,
312    manifests: <Skel::Body as BodySpec>::Manifests,
313    watcher: ReloadWatcher,
314}
315
316impl<Skel: Skeleton> FigureModelCache<Skel>
317where
318    Skel::Body: BodySpec + Eq + Hash,
319{
320    pub fn new() -> Self {
321        // NOTE: It might be better to bubble this error up rather than panicking.
322        let manifests = <Skel::Body as BodySpec>::load_spec().unwrap();
323        let watcher = <Skel::Body as BodySpec>::reload_watcher(&manifests);
324
325        Self {
326            models: HashMap::new(),
327            manifests,
328            watcher,
329        }
330    }
331
332    pub fn watcher_reloaded(&mut self) -> bool { self.watcher.reloaded() }
333
334    /// NOTE: Intended for render time (useful with systems like wgpu that
335    /// expect data used by the rendering pipelines to be stable throughout
336    /// the render pass).
337    ///
338    /// NOTE: Since this is intended to be called primarily in order to render
339    /// the model, we don't return skeleton data.
340    pub(crate) fn get_model<'b>(
341        &'b self,
342        // TODO: If we ever convert to using an atlas here, use this.
343        _atlas: &super::FigureAtlas,
344        body: Skel::Body,
345        inventory: Option<&Inventory>,
346        // TODO: Consider updating the tick by putting it in a Cell.
347        _tick: u64,
348        camera_mode: CameraMode,
349        character_state: Option<&CharacterState>,
350        item_key: Option<ItemKey>,
351    ) -> Option<
352        &'b <<Skel::Body as BodySpec>::ModelEntryFuture<LOD_COUNT> as ModelEntryFuture<
353            LOD_COUNT,
354        >>::ModelEntry,
355    > {
356        // TODO: Use raw entries to avoid lots of allocation (among other things).
357        let key = FigureKey {
358            body,
359            item_key: item_key.map(Arc::new),
360            extra: inventory.map(|inventory| {
361                Arc::new(CharacterCacheKey::from(
362                    character_state,
363                    camera_mode,
364                    inventory,
365                ))
366            }),
367        };
368
369        if let Some(model) = self.models.get(&key).and_then(|d| d.0.0.get_done()) {
370            Some(model)
371        } else {
372            None
373        }
374    }
375
376    pub fn clear_models(&mut self) { self.models.clear(); }
377
378    pub fn clean(&mut self, atlas: &mut super::FigureAtlas, tick: u64)
379    where
380        <Skel::Body as BodySpec>::Spec: Clone,
381    {
382        // TODO: Don't hard-code this.
383        if tick.is_multiple_of(60) {
384            self.models.retain(|_, ((model_entry, _), last_used)| {
385                // Wait about a minute at 60 fps before invalidating old models.
386                let delta = 60 * 60;
387                let alive = *last_used + delta > tick;
388                if !alive && let Some(model_entry) = model_entry.get_done() {
389                    atlas.allocator.deallocate(model_entry.allocation().id);
390                }
391                alive
392            });
393        }
394    }
395}
396
397impl<Skel: Skeleton> FigureModelCache<Skel>
398where
399    Skel::Body: BodySpec<
400            BoneMesh = super::load::BoneMeshes,
401            ModelEntryFuture<LOD_COUNT> = FigureModelEntryFuture<LOD_COUNT>,
402        > + Eq
403        + Hash,
404{
405    pub fn get_or_create_model<'c>(
406        &'c mut self,
407        renderer: &mut Renderer,
408        atlas: &mut super::FigureAtlas,
409        body: Skel::Body,
410        inventory: Option<&Inventory>,
411        extra: <Skel::Body as BodySpec>::Extra,
412        tick: u64,
413        camera_mode: CameraMode,
414        character_state: Option<&CharacterState>,
415        slow_jobs: &SlowJobPool,
416        item_key: Option<ItemKey>,
417    ) -> (FigureModelEntryLod<'c>, &'c Skel::Attr)
418    where
419        Skel::Attr: 'c,
420        Skel::Attr: for<'a> From<&'a Skel::Body>,
421        Skel::Body: Clone + Send + Sync + 'static,
422        <Skel::Body as BodySpec>::Spec: Send + Sync + 'static,
423    {
424        let skeleton_attr = (&body).into();
425        let key = FigureKey {
426            body,
427            item_key: item_key.map(Arc::new),
428            extra: inventory.map(|inventory| {
429                Arc::new(CharacterCacheKey::from(
430                    character_state,
431                    camera_mode,
432                    inventory,
433                ))
434            }),
435        };
436
437        // TODO: Use raw entries to avoid significant performance overhead.
438        match self.models.entry(key) {
439            Entry::Occupied(o) => {
440                let ((model, skel), last_used) = o.into_mut();
441
442                #[cfg(feature = "hot-reloading")]
443                {
444                    *skel = skeleton_attr;
445                }
446
447                *last_used = tick;
448                (
449                    match model {
450                        FigureModelEntryFuture::Pending(recv) => {
451                            if let Some(MeshWorkerResponse {
452                                atlas_texture_data,
453                                atlas_size,
454                                opaque,
455                                bounds,
456                                vertex_range,
457                            }) = Arc::get_mut(recv).and_then(|cell| cell.take())
458                            {
459                                let model_entry = atlas.create_figure(
460                                    renderer,
461                                    atlas_texture_data,
462                                    atlas_size,
463                                    (opaque, bounds),
464                                    vertex_range,
465                                );
466                                *model = FigureModelEntryFuture::Done(model_entry);
467                                // NOTE: Borrow checker isn't smart enough to figure this out.
468                                if let FigureModelEntryFuture::Done(model) = model {
469                                    Some(model)
470                                } else {
471                                    unreachable!();
472                                }
473                            } else {
474                                None
475                            }
476                        },
477                        FigureModelEntryFuture::Done(model) => Some(model),
478                    },
479                    skel,
480                )
481            },
482            Entry::Vacant(v) => {
483                let key = v.key().clone();
484                let slot = Arc::new(atomic::AtomicCell::new(None));
485                let manifests = self.manifests.clone();
486                let slot_ = Arc::clone(&slot);
487
488                slow_jobs.spawn("FIGURE_MESHING", move || {
489                    // First, load all the base vertex data.
490                    let meshes =
491                        <Skel::Body as BodySpec>::bone_meshes(&key, &manifests, extra);
492
493                    // Then, set up meshing context.
494                    let mut greedy = FigureModel::make_greedy();
495                    let mut opaque = Mesh::<TerrainVertex>::new();
496                    // Choose the most conservative bounds for any LOD model.
497                    let mut figure_bounds = anim::vek::Aabb {
498                        min: anim::vek::Vec3::zero(),
499                        max: anim::vek::Vec3::zero(),
500                    };
501                    // Meshes all bone models for this figure using the given mesh generation
502                    // function, attaching it to the current greedy mesher and opaque vertex
503                    // list.  Returns the vertex bounds of the meshed model within the opaque
504                    // mesh.
505                    let mut make_model = |generate_mesh: for<'a, 'b> fn(
506                        &mut GreedyMesh<'a, FigureSpriteAtlasData>,
507                        &'b mut _,
508                        &'a _,
509                        _,
510                        _,
511                    )
512                        -> _| {
513                        let vertex_start = opaque.vertices().len();
514                        meshes
515                            .iter()
516                            .enumerate()
517                            // NOTE: Cast to u8 is safe because i < 16.
518                            .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i as u8, bm)))
519                            .for_each(|(i, (segment, offset))| {
520                                // Generate this mesh.
521                                let (_opaque_mesh, bounds) = generate_mesh(&mut greedy, &mut opaque, segment, *offset, i);
522                                // Update the figure bounds to the largest granularity seen so far
523                                // (NOTE: this is more than a little imperfect).
524                                //
525                                // FIXME: Maybe use the default bone position in the idle animation
526                                // to figure this out instead?
527                                figure_bounds.expand_to_contain(bounds);
528                            });
529                        // NOTE: vertex_start and vertex_end *should* fit in a u32, by the
530                        // following logic:
531                        //
532                        // Our new figure maximum is constrained to at most 2^8 × 2^8 × 2^8.
533                        // This uses at most 24 bits to store every vertex exactly once.
534                        // Greedy meshing can store each vertex in up to 3 quads, we have 3
535                        // greedy models, and we store 1.5x the vertex count, so the maximum
536                        // total space a model can take up is 3 * 3 * 1.5 * 2^24; rounding
537                        // up to 4 * 4 * 2^24 gets us to 2^28, which clearly still fits in a
538                        // u32.
539                        //
540                        // (We could also, though we prefer not to, reason backwards from the
541                        // maximum figure texture size of 2^15 × 2^15, also fits in a u32; we
542                        // can also see that, since we can have at most one texture entry per
543                        // vertex, any texture atlas of size 2^14 × 2^14 or higher should be
544                        // able to store data for any figure.  So the only reason we would fail
545                        // here would be if the user's computer could not store a texture large
546                        // enough to fit all the LOD models for the figure, not for fundamental
547                        // reasons related to fitting in a u32).
548                        //
549                        // Therefore, these casts are safe.
550                        vertex_start as u32..opaque.vertices().len() as u32
551                    };
552
553                    fn generate_mesh<'a>(
554                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
555                        opaque_mesh: &mut Mesh<TerrainVertex>,
556                        segment: &'a Segment,
557                        offset: Vec3<f32>,
558                        bone_idx: u8,
559                    ) -> BoneMeshes {
560                        let (opaque, _, _, bounds) = generate_mesh_base_vol_figure(
561                            segment,
562                            (greedy, opaque_mesh, offset, Vec3::one(), bone_idx),
563                        );
564                        (opaque, bounds)
565                    }
566
567                    fn generate_mesh_lod_mid<'a>(
568                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
569                        opaque_mesh: &mut Mesh<TerrainVertex>,
570                        segment: &'a Segment,
571                        offset: Vec3<f32>,
572                        bone_idx: u8,
573                    ) -> BoneMeshes {
574                        let lod_scale = 0.6;
575                        let (opaque, _, _, bounds) = generate_mesh_base_vol_figure(
576                            segment.scaled_by(Vec3::broadcast(lod_scale)),
577                            (
578                                greedy,
579                                opaque_mesh,
580                                offset * lod_scale,
581                                Vec3::one() / lod_scale,
582                                bone_idx,
583                            ),
584                        );
585                        (opaque, bounds)
586                    }
587
588                    fn generate_mesh_lod_low<'a>(
589                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
590                        opaque_mesh: &mut Mesh<TerrainVertex>,
591                        segment: &'a Segment,
592                        offset: Vec3<f32>,
593                        bone_idx: u8,
594                    ) -> BoneMeshes {
595                        let lod_scale = 0.3;
596                        let (opaque, _, _, bounds) = generate_mesh_base_vol_figure(
597                            segment.scaled_by(Vec3::broadcast(lod_scale)),
598                            (
599                                greedy,
600                                opaque_mesh,
601                                offset * lod_scale,
602                                Vec3::one() / lod_scale,
603                                bone_idx,
604                            ),
605                        );
606                        (opaque, bounds)
607                    }
608
609                    let models = [
610                        make_model(generate_mesh),
611                        make_model(generate_mesh_lod_mid),
612                        make_model(generate_mesh_lod_low),
613                    ];
614
615                    let (atlas_texture_data, atlas_size) = greedy.finalize();
616                    slot_.store(Some(MeshWorkerResponse {
617                        atlas_texture_data,
618                        atlas_size,
619                        opaque,
620                        bounds: figure_bounds,
621                        vertex_range: models,
622                    }));
623                });
624
625                let skel = &(v
626                    .insert(((FigureModelEntryFuture::Pending(slot), skeleton_attr), tick))
627                    .0)
628                    .1;
629                (None, skel)
630            },
631        }
632    }
633}
634
635impl<Skel: Skeleton> FigureModelCache<Skel>
636where
637    Skel::Body: BodySpec<
638            BoneMesh = ShipBoneMeshes,
639            ModelEntryFuture<LOD_COUNT> = TerrainModelEntryFuture<LOD_COUNT>,
640        > + Eq
641        + Hash,
642{
643    pub(crate) fn get_or_create_terrain_model<'c>(
644        &'c mut self,
645        renderer: &mut Renderer,
646        atlas: &mut super::FigureAtlas,
647        body: Skel::Body,
648        extra: <Skel::Body as BodySpec>::Extra,
649        tick: u64,
650        slow_jobs: &SlowJobPool,
651        sprite_render_state: &Arc<SpriteRenderState>,
652    ) -> (TerrainModelEntryLod<'c>, &'c Skel::Attr)
653    where
654        Skel::Attr: 'c,
655        for<'a> &'a Skel::Body: Into<Skel::Attr>,
656        Skel::Body: Clone + Send + Sync + 'static,
657        <Skel::Body as BodySpec>::Spec: Send + Sync + 'static,
658    {
659        let skeleton_attr = (&body).into();
660        let key = FigureKey {
661            body,
662            item_key: None,
663            extra: None,
664        };
665
666        // TODO: Use raw entries to avoid significant performance overhead.
667        match self.models.entry(key) {
668            Entry::Occupied(o) => {
669                let ((model, skel), last_used) = o.into_mut();
670                *last_used = tick;
671                (
672                    match model {
673                        TerrainModelEntryFuture::Pending(recv) => {
674                            if let Some(TerrainMeshWorkerResponse {
675                                atlas_texture_data,
676                                atlas_size,
677                                opaque,
678                                bounds,
679                                vertex_range,
680                                sprite_instances,
681                                blocks_of_interest,
682                                blocks_offset,
683                            }) = Arc::get_mut(recv).and_then(|cell| cell.take())
684                            {
685                                let model_entry = atlas.create_terrain(
686                                    renderer,
687                                    atlas_texture_data,
688                                    atlas_size,
689                                    (opaque, bounds),
690                                    vertex_range,
691                                    sprite_instances,
692                                    blocks_of_interest,
693                                    blocks_offset,
694                                );
695                                *model = TerrainModelEntryFuture::Done(model_entry);
696                                // NOTE: Borrow checker isn't smart enough to figure this out.
697                                if let TerrainModelEntryFuture::Done(model) = model {
698                                    Some(model)
699                                } else {
700                                    unreachable!();
701                                }
702                            } else {
703                                None
704                            }
705                        },
706                        TerrainModelEntryFuture::Done(model) => Some(model),
707                    },
708                    skel,
709                )
710            },
711            Entry::Vacant(v) => {
712                let key = v.key().clone();
713                let slot = Arc::new(atomic::AtomicCell::new(None));
714                let manifests = self.manifests.clone();
715                let sprite_render_state = Arc::clone(sprite_render_state);
716                let slot_ = Arc::clone(&slot);
717
718                slow_jobs.spawn("FIGURE_MESHING", move || {
719                    // First, load all the base vertex data.
720                    let meshes =
721                        <Skel::Body as BodySpec>::bone_meshes(&key, &manifests, extra);
722
723                    // Then, set up meshing context.
724                    let mut greedy = FigureModel::make_greedy();
725                    let mut opaque = Mesh::<TerrainVertex>::new();
726                    // Choose the most conservative bounds for any LOD model.
727                    let mut figure_bounds = anim::vek::Aabb {
728                        min: anim::vek::Vec3::zero(),
729                        max: anim::vek::Vec3::zero(),
730                    };
731                    // Meshes all bone models for this figure using the given mesh generation
732                    // function, attaching it to the current greedy mesher and opaque vertex
733                    // list.  Returns the vertex bounds of the meshed model within the opaque
734                    // mesh.
735                    let mut make_model = |generate_mesh: for<'a, 'b> fn(
736                        &mut GreedyMesh<'a, FigureSpriteAtlasData>,
737                        &'b mut _,
738                        &'a _,
739                        _,
740                        _,
741                    )
742                        -> _| {
743                        let vertex_start = opaque.vertices().len();
744                        meshes
745                            .iter()
746                            .enumerate()
747                            // NOTE: Cast to u8 is safe because i < 16.
748                            .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i as u8, bm)))
749                            .for_each(|(i, (segment, offset))| {
750                                // Generate this mesh.
751                                let (_opaque_mesh, bounds) = generate_mesh(&mut greedy, &mut opaque, segment, *offset, i);
752                                // Update the figure bounds to the largest granularity seen so far
753                                // (NOTE: this is more than a little imperfect).
754                                //
755                                // FIXME: Maybe use the default bone position in the idle animation
756                                // to figure this out instead?
757                                figure_bounds.expand_to_contain(bounds);
758                            });
759                        // NOTE: vertex_start and vertex_end *should* fit in a u32, by the
760                        // following logic:
761                        //
762                        // Our new figure maximum is constrained to at most 2^8 × 2^8 × 2^8.
763                        // This uses at most 24 bits to store every vertex exactly once.
764                        // Greedy meshing can store each vertex in up to 3 quads, we have 3
765                        // greedy models, and we store 1.5x the vertex count, so the maximum
766                        // total space a model can take up is 3 * 3 * 1.5 * 2^24; rounding
767                        // up to 4 * 4 * 2^24 gets us to 2^28, which clearly still fits in a
768                        // u32.
769                        //
770                        // (We could also, though we prefer not to, reason backwards from the
771                        // maximum figure texture size of 2^15 × 2^15, also fits in a u32; we
772                        // can also see that, since we can have at most one texture entry per
773                        // vertex, any texture atlas of size 2^14 × 2^14 or higher should be
774                        // able to store data for any figure.  So the only reason we would fail
775                        // here would be if the user's computer could not store a texture large
776                        // enough to fit all the LOD models for the figure, not for fundamental
777                        // reasons related to fitting in a u32).
778                        //
779                        // Therefore, these casts are safe.
780                        vertex_start as u32..opaque.vertices().len() as u32
781                    };
782
783                    fn generate_mesh<'a>(
784                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
785                        opaque_mesh: &mut Mesh<TerrainVertex>,
786                        segment: &'a TerrainSegment,
787                        offset: Vec3<f32>,
788                        bone_idx: u8,
789                    ) -> BoneMeshes {
790                        let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain(
791                            segment,
792                            (greedy, opaque_mesh, offset, Vec3::one(), bone_idx),
793                        );
794                        (opaque, bounds)
795                    }
796
797                    fn generate_mesh_lod_mid<'a>(
798                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
799                        opaque_mesh: &mut Mesh<TerrainVertex>,
800                        segment: &'a TerrainSegment,
801                        offset: Vec3<f32>,
802                        bone_idx: u8,
803                    ) -> BoneMeshes {
804                        let lod_scale = 0.6;
805                        let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain(
806                            segment.scaled_by(Vec3::broadcast(lod_scale)),
807                            (
808                                greedy,
809                                opaque_mesh,
810                                offset * lod_scale,
811                                Vec3::one() / lod_scale,
812                                bone_idx,
813                            ),
814                        );
815                        (opaque, bounds)
816                    }
817
818                    fn generate_mesh_lod_low<'a>(
819                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
820                        opaque_mesh: &mut Mesh<TerrainVertex>,
821                        segment: &'a TerrainSegment,
822                        offset: Vec3<f32>,
823                        bone_idx: u8,
824                    ) -> BoneMeshes {
825                        let lod_scale = 0.3;
826                        let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain(
827                            segment.scaled_by(Vec3::broadcast(lod_scale)),
828                            (
829                                greedy,
830                                opaque_mesh,
831                                offset * lod_scale,
832                                Vec3::one() / lod_scale,
833                                bone_idx,
834                            ),
835                        );
836                        (opaque, bounds)
837                    }
838
839                    let models = [
840                        make_model(generate_mesh),
841                        make_model(generate_mesh_lod_mid),
842                        make_model(generate_mesh_lod_low),
843                    ];
844
845                    let (dyna, offset) = &meshes[0].as_ref().unwrap();
846                    let block_iter = dyna.vol_iter(Vec3::zero(), dyna.sz.as_()).map(|(pos, block)| (pos, *block));
847
848                    let (atlas_texture_data, atlas_size) = greedy.finalize();
849                    slot_.store(Some(TerrainMeshWorkerResponse {
850                        atlas_texture_data,
851                        atlas_size,
852                        opaque,
853                        bounds: figure_bounds,
854                        vertex_range: models,
855                        sprite_instances: {
856                            let mut instances = from_fn::<Vec<pipelines::sprite::Instance>, SPRITE_LOD_LEVELS, _>(|_| Vec::new());
857                            get_sprite_instances(
858                                &mut instances,
859                                |lod, instance, _| {
860                                    lod.push(instance);
861                                },
862                                block_iter.clone().map(|(pos, block)| (pos.as_() + *offset, block)),
863                                |p| p.as_(),
864                                |_| 1.0,
865                                |pos| dyna.get(pos).ok().and_then(|block| block.get_glow()).map(|glow| glow as f32 / 255.0).unwrap_or(0.0),
866                                &sprite_render_state.sprite_data,
867                                &sprite_render_state.missing_sprite_placeholder,
868                            );
869                            instances
870                        },
871                        blocks_of_interest: BlocksOfInterest::from_blocks(block_iter, Vec3::zero(), 10.0, 0.0, dyna),
872                        blocks_offset: *offset,
873                    }));
874                });
875
876                let skel = &(v
877                    .insert((
878                        (TerrainModelEntryFuture::Pending(slot), skeleton_attr),
879                        tick,
880                    ))
881                    .0)
882                    .1;
883                (None, skel)
884            },
885        }
886    }
887
888    pub fn get_blocks_of_interest(
889        &self,
890        body: Skel::Body,
891    ) -> Option<(&BlocksOfInterest, Vec3<f32>)> {
892        let key = FigureKey {
893            body,
894            item_key: None,
895            extra: None,
896        };
897        self.models.get(&key).and_then(|((model, _), _)| {
898            let TerrainModelEntryFuture::Done(model) = model else {
899                return None;
900            };
901
902            Some((&model.blocks_of_interest, model.blocks_offset))
903        })
904    }
905
906    pub fn get_sprites(
907        &self,
908        body: Skel::Body,
909    ) -> Option<&[Instances<SpriteInstance>; SPRITE_LOD_LEVELS]> {
910        let key = FigureKey {
911            body,
912            item_key: None,
913            extra: None,
914        };
915        self.models.get(&key).and_then(|((model, _), _)| {
916            let TerrainModelEntryFuture::Done(model) = model else {
917                return None;
918            };
919
920            Some(&model.sprite_instances)
921        })
922    }
923
924    /*
925    pub fn update_terrain_locals(
926        &mut self,
927        renderer: &mut Renderer,
928        entity: Entity,
929        body: Skel::Body,
930        pos: Vec3<f32>,
931        ori: Quaternion<f32>,
932    ) {
933        let key = FigureKey {
934            body,
935            item_key: None,
936            extra: None,
937        };
938        if let Some(model) = self.models.get_mut(&key).and_then(|((model, _), _)| {
939            if let TerrainModelEntryFuture::Done(model) = model {
940                Some(model)
941            } else {
942                None
943            }
944        }) {
945            renderer.update_consts(&mut *model.terrain_locals, &[TerrainLocals::new(
946                pos,
947                ori,
948                Vec2::zero(),
949                0.0,
950            )])
951        }
952    }
953    */
954}