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