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