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