Skip to main content

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, 'd>(
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: impl Into<Option<&'d 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 manifests = self.manifests.clone();
488
489                let job = move || {
490                    // First, load all the base vertex data.
491                    let meshes = <Skel::Body as BodySpec>::bone_meshes(&key, &manifests, extra);
492
493                    // Then, set up meshing context.
494                    let mut greedy = FigureModel::make_greedy();
495                    let mut opaque = Mesh::<TerrainVertex>::new();
496                    // Choose the most conservative bounds for any LOD model.
497                    let mut figure_bounds = anim::vek::Aabb {
498                        min: anim::vek::Vec3::zero(),
499                        max: anim::vek::Vec3::zero(),
500                    };
501                    // Meshes all bone models for this figure using the given mesh generation
502                    // function, attaching it to the current greedy mesher and opaque vertex
503                    // list.  Returns the vertex bounds of the meshed model within the opaque
504                    // mesh.
505                    let mut make_model = |generate_mesh: for<'a, 'b> fn(
506                        &mut GreedyMesh<'a, FigureSpriteAtlasData>,
507                        &'b mut _,
508                        &'a _,
509                        _,
510                        _,
511                    )
512                        -> _| {
513                        let vertex_start = opaque.vertices().len();
514                        meshes
515                            .iter()
516                            .enumerate()
517                            // NOTE: Cast to u8 is safe because i < 16.
518                            .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i as u8, bm)))
519                            .for_each(|(i, (segment, offset))| {
520                                // Generate this mesh.
521                                let (_opaque_mesh, bounds) = generate_mesh(&mut greedy, &mut opaque, segment, *offset, i);
522                                // Update the figure bounds to the largest granularity seen so far
523                                // (NOTE: this is more than a little imperfect).
524                                //
525                                // FIXME: Maybe use the default bone position in the idle animation
526                                // to figure this out instead?
527                                figure_bounds.expand_to_contain(bounds);
528                            });
529                        // NOTE: vertex_start and vertex_end *should* fit in a u32, by the
530                        // following logic:
531                        //
532                        // Our new figure maximum is constrained to at most 2^8 × 2^8 × 2^8.
533                        // This uses at most 24 bits to store every vertex exactly once.
534                        // Greedy meshing can store each vertex in up to 3 quads, we have 3
535                        // greedy models, and we store 1.5x the vertex count, so the maximum
536                        // total space a model can take up is 3 * 3 * 1.5 * 2^24; rounding
537                        // up to 4 * 4 * 2^24 gets us to 2^28, which clearly still fits in a
538                        // u32.
539                        //
540                        // (We could also, though we prefer not to, reason backwards from the
541                        // maximum figure texture size of 2^15 × 2^15, also fits in a u32; we
542                        // can also see that, since we can have at most one texture entry per
543                        // vertex, any texture atlas of size 2^14 × 2^14 or higher should be
544                        // able to store data for any figure.  So the only reason we would fail
545                        // here would be if the user's computer could not store a texture large
546                        // enough to fit all the LOD models for the figure, not for fundamental
547                        // reasons related to fitting in a u32).
548                        //
549                        // Therefore, these casts are safe.
550                        vertex_start as u32..opaque.vertices().len() as u32
551                    };
552
553                    fn generate_mesh<'a>(
554                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
555                        opaque_mesh: &mut Mesh<TerrainVertex>,
556                        segment: &'a Segment,
557                        offset: Vec3<f32>,
558                        bone_idx: u8,
559                    ) -> BoneMeshes {
560                        let (opaque, _, _, bounds) = generate_mesh_base_vol_figure(
561                            segment,
562                            (greedy, opaque_mesh, offset, Vec3::one(), bone_idx),
563                        );
564                        (opaque, bounds)
565                    }
566
567                    fn generate_mesh_lod_mid<'a>(
568                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
569                        opaque_mesh: &mut Mesh<TerrainVertex>,
570                        segment: &'a Segment,
571                        offset: Vec3<f32>,
572                        bone_idx: u8,
573                    ) -> BoneMeshes {
574                        let lod_scale = 0.6;
575                        let (opaque, _, _, bounds) = generate_mesh_base_vol_figure(
576                            segment.scaled_by(Vec3::broadcast(lod_scale)),
577                            (
578                                greedy,
579                                opaque_mesh,
580                                offset * lod_scale,
581                                Vec3::one() / lod_scale,
582                                bone_idx,
583                            ),
584                        );
585                        (opaque, bounds)
586                    }
587
588                    fn generate_mesh_lod_low<'a>(
589                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
590                        opaque_mesh: &mut Mesh<TerrainVertex>,
591                        segment: &'a Segment,
592                        offset: Vec3<f32>,
593                        bone_idx: u8,
594                    ) -> BoneMeshes {
595                        let lod_scale = 0.3;
596                        let (opaque, _, _, bounds) = generate_mesh_base_vol_figure(
597                            segment.scaled_by(Vec3::broadcast(lod_scale)),
598                            (
599                                greedy,
600                                opaque_mesh,
601                                offset * lod_scale,
602                                Vec3::one() / lod_scale,
603                                bone_idx,
604                            ),
605                        );
606                        (opaque, bounds)
607                    }
608
609                    let models = [
610                        make_model(generate_mesh),
611                        make_model(generate_mesh_lod_mid),
612                        make_model(generate_mesh_lod_low),
613                    ];
614
615                    let (atlas_texture_data, atlas_size) = greedy.finalize();
616                    MeshWorkerResponse {
617                        atlas_texture_data,
618                        atlas_size,
619                        opaque,
620                        bounds: figure_bounds,
621                        vertex_range: models,
622                    }
623                };
624
625                let model = if let Some(slow_jobs) = slow_jobs.into() {
626                    let slot = Arc::new(atomic::AtomicCell::new(None));
627                    let slot_ = Arc::clone(&slot);
628
629                    slow_jobs.spawn("FIGURE_MESHING", move || {
630                        slot_.store(Some(job()));
631                    });
632
633                    FigureModelEntryFuture::Pending(slot)
634                } else {
635                    let MeshWorkerResponse {
636                        atlas_texture_data,
637                        atlas_size,
638                        opaque,
639                        bounds,
640                        vertex_range,
641                    } = job();
642                    FigureModelEntryFuture::Done(atlas.create_figure(
643                        renderer,
644                        atlas_texture_data,
645                        atlas_size,
646                        (opaque, bounds),
647                        vertex_range,
648                    ))
649                };
650
651                let (model, skel) = &(v.insert(((model, skeleton_attr), tick)).0);
652                (model.get_done(), skel)
653            },
654        }
655    }
656}
657
658impl<Skel: Skeleton> FigureModelCache<Skel>
659where
660    Skel::Body: BodySpec<
661            BoneMesh = ShipBoneMeshes,
662            ModelEntryFuture<LOD_COUNT> = TerrainModelEntryFuture<LOD_COUNT>,
663        > + Eq
664        + Hash,
665{
666    pub(crate) fn get_or_create_terrain_model<'c>(
667        &'c mut self,
668        renderer: &mut Renderer,
669        atlas: &mut super::FigureAtlas,
670        body: Skel::Body,
671        extra: <Skel::Body as BodySpec>::Extra,
672        tick: u64,
673        slow_jobs: &SlowJobPool,
674        sprite_render_state: &Arc<SpriteRenderState>,
675    ) -> (TerrainModelEntryLod<'c>, &'c Skel::Attr)
676    where
677        Skel::Attr: 'c,
678        for<'a> &'a Skel::Body: Into<Skel::Attr>,
679        Skel::Body: Clone + Send + Sync + 'static,
680        <Skel::Body as BodySpec>::Spec: Send + Sync + 'static,
681    {
682        let skeleton_attr = (&body).into();
683        let key = FigureKey {
684            body,
685            item_key: None,
686            extra: None,
687        };
688
689        // TODO: Use raw entries to avoid significant performance overhead.
690        match self.models.entry(key) {
691            Entry::Occupied(o) => {
692                let ((model, skel), last_used) = o.into_mut();
693                *last_used = tick;
694                (
695                    match model {
696                        TerrainModelEntryFuture::Pending(recv) => {
697                            if let Some(TerrainMeshWorkerResponse {
698                                atlas_texture_data,
699                                atlas_size,
700                                opaque,
701                                bounds,
702                                vertex_range,
703                                sprite_instances,
704                                blocks_of_interest,
705                                blocks_offset,
706                            }) = Arc::get_mut(recv).and_then(|cell| cell.take())
707                            {
708                                let model_entry = atlas.create_terrain(
709                                    renderer,
710                                    atlas_texture_data,
711                                    atlas_size,
712                                    (opaque, bounds),
713                                    vertex_range,
714                                    sprite_instances,
715                                    blocks_of_interest,
716                                    blocks_offset,
717                                );
718                                *model = TerrainModelEntryFuture::Done(model_entry);
719                                // NOTE: Borrow checker isn't smart enough to figure this out.
720                                if let TerrainModelEntryFuture::Done(model) = model {
721                                    Some(model)
722                                } else {
723                                    unreachable!();
724                                }
725                            } else {
726                                None
727                            }
728                        },
729                        TerrainModelEntryFuture::Done(model) => Some(model),
730                    },
731                    skel,
732                )
733            },
734            Entry::Vacant(v) => {
735                let key = v.key().clone();
736                let slot = Arc::new(atomic::AtomicCell::new(None));
737                let manifests = self.manifests.clone();
738                let sprite_render_state = Arc::clone(sprite_render_state);
739                let slot_ = Arc::clone(&slot);
740
741                slow_jobs.spawn("FIGURE_MESHING", move || {
742                    // First, load all the base vertex data.
743                    let meshes =
744                        <Skel::Body as BodySpec>::bone_meshes(&key, &manifests, extra);
745
746                    // Then, set up meshing context.
747                    let mut greedy = FigureModel::make_greedy();
748                    let mut opaque = Mesh::<TerrainVertex>::new();
749                    // Choose the most conservative bounds for any LOD model.
750                    let mut figure_bounds = anim::vek::Aabb {
751                        min: anim::vek::Vec3::zero(),
752                        max: anim::vek::Vec3::zero(),
753                    };
754                    // Meshes all bone models for this figure using the given mesh generation
755                    // function, attaching it to the current greedy mesher and opaque vertex
756                    // list.  Returns the vertex bounds of the meshed model within the opaque
757                    // mesh.
758                    let mut make_model = |generate_mesh: for<'a, 'b> fn(
759                        &mut GreedyMesh<'a, FigureSpriteAtlasData>,
760                        &'b mut _,
761                        &'a _,
762                        _,
763                        _,
764                    )
765                        -> _| {
766                        let vertex_start = opaque.vertices().len();
767                        meshes
768                            .iter()
769                            .enumerate()
770                            // NOTE: Cast to u8 is safe because i < 16.
771                            .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i as u8, bm)))
772                            .for_each(|(i, (segment, offset))| {
773                                // Generate this mesh.
774                                let (_opaque_mesh, bounds) = generate_mesh(&mut greedy, &mut opaque, segment, *offset, i);
775                                // Update the figure bounds to the largest granularity seen so far
776                                // (NOTE: this is more than a little imperfect).
777                                //
778                                // FIXME: Maybe use the default bone position in the idle animation
779                                // to figure this out instead?
780                                figure_bounds.expand_to_contain(bounds);
781                            });
782                        // NOTE: vertex_start and vertex_end *should* fit in a u32, by the
783                        // following logic:
784                        //
785                        // Our new figure maximum is constrained to at most 2^8 × 2^8 × 2^8.
786                        // This uses at most 24 bits to store every vertex exactly once.
787                        // Greedy meshing can store each vertex in up to 3 quads, we have 3
788                        // greedy models, and we store 1.5x the vertex count, so the maximum
789                        // total space a model can take up is 3 * 3 * 1.5 * 2^24; rounding
790                        // up to 4 * 4 * 2^24 gets us to 2^28, which clearly still fits in a
791                        // u32.
792                        //
793                        // (We could also, though we prefer not to, reason backwards from the
794                        // maximum figure texture size of 2^15 × 2^15, also fits in a u32; we
795                        // can also see that, since we can have at most one texture entry per
796                        // vertex, any texture atlas of size 2^14 × 2^14 or higher should be
797                        // able to store data for any figure.  So the only reason we would fail
798                        // here would be if the user's computer could not store a texture large
799                        // enough to fit all the LOD models for the figure, not for fundamental
800                        // reasons related to fitting in a u32).
801                        //
802                        // Therefore, these casts are safe.
803                        vertex_start as u32..opaque.vertices().len() as u32
804                    };
805
806                    fn generate_mesh<'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 (opaque, _, _, bounds) = generate_mesh_base_vol_terrain(
814                            segment,
815                            (greedy, opaque_mesh, offset, Vec3::one(), bone_idx),
816                        );
817                        (opaque, bounds)
818                    }
819
820                    fn generate_mesh_lod_mid<'a>(
821                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
822                        opaque_mesh: &mut Mesh<TerrainVertex>,
823                        segment: &'a TerrainSegment,
824                        offset: Vec3<f32>,
825                        bone_idx: u8,
826                    ) -> BoneMeshes {
827                        let lod_scale = 0.6;
828                        let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain(
829                            segment.scaled_by(Vec3::broadcast(lod_scale)),
830                            (
831                                greedy,
832                                opaque_mesh,
833                                offset * lod_scale,
834                                Vec3::one() / lod_scale,
835                                bone_idx,
836                            ),
837                        );
838                        (opaque, bounds)
839                    }
840
841                    fn generate_mesh_lod_low<'a>(
842                        greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
843                        opaque_mesh: &mut Mesh<TerrainVertex>,
844                        segment: &'a TerrainSegment,
845                        offset: Vec3<f32>,
846                        bone_idx: u8,
847                    ) -> BoneMeshes {
848                        let lod_scale = 0.3;
849                        let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain(
850                            segment.scaled_by(Vec3::broadcast(lod_scale)),
851                            (
852                                greedy,
853                                opaque_mesh,
854                                offset * lod_scale,
855                                Vec3::one() / lod_scale,
856                                bone_idx,
857                            ),
858                        );
859                        (opaque, bounds)
860                    }
861
862                    let models = [
863                        make_model(generate_mesh),
864                        make_model(generate_mesh_lod_mid),
865                        make_model(generate_mesh_lod_low),
866                    ];
867
868                    let (dyna, offset) = &meshes[0].as_ref().unwrap();
869                    let block_iter = dyna.vol_iter(Vec3::zero(), dyna.sz.as_()).map(|(pos, block)| (pos, *block));
870
871                    let (atlas_texture_data, atlas_size) = greedy.finalize();
872                    slot_.store(Some(TerrainMeshWorkerResponse {
873                        atlas_texture_data,
874                        atlas_size,
875                        opaque,
876                        bounds: figure_bounds,
877                        vertex_range: models,
878                        sprite_instances: {
879                            let mut instances = from_fn::<Vec<pipelines::sprite::Instance>, SPRITE_LOD_LEVELS, _>(|_| Vec::new());
880                            get_sprite_instances(
881                                &mut instances,
882                                |lod, instance, _| {
883                                    lod.push(instance);
884                                },
885                                block_iter.clone().map(|(pos, block)| (pos.as_() + *offset, block)),
886                                |p| p.as_(),
887                                |_| 1.0,
888                                |pos| (Vec3::zero(), dyna.get(pos).ok().and_then(|block| block.get_glow()).map(|glow| glow as f32 / 255.0).unwrap_or(0.0)),
889                                &sprite_render_state.sprite_data,
890                                &sprite_render_state.missing_sprite_placeholder,
891                            );
892                            instances
893                        },
894                        blocks_of_interest: BlocksOfInterest::from_blocks(block_iter, Vec3::zero(), 10.0, 0.0, dyna),
895                        blocks_offset: *offset,
896                    }));
897                });
898
899                let skel = &(v
900                    .insert((
901                        (TerrainModelEntryFuture::Pending(slot), skeleton_attr),
902                        tick,
903                    ))
904                    .0)
905                    .1;
906                (None, skel)
907            },
908        }
909    }
910
911    pub fn get_blocks_of_interest(
912        &self,
913        body: Skel::Body,
914    ) -> Option<(&BlocksOfInterest, Vec3<f32>)> {
915        let key = FigureKey {
916            body,
917            item_key: None,
918            extra: None,
919        };
920        self.models.get(&key).and_then(|((model, _), _)| {
921            let TerrainModelEntryFuture::Done(model) = model else {
922                return None;
923            };
924
925            Some((&model.blocks_of_interest, model.blocks_offset))
926        })
927    }
928
929    pub fn get_sprites(
930        &self,
931        body: Skel::Body,
932    ) -> Option<&[Instances<SpriteInstance>; SPRITE_LOD_LEVELS]> {
933        let key = FigureKey {
934            body,
935            item_key: None,
936            extra: None,
937        };
938        self.models.get(&key).and_then(|((model, _), _)| {
939            let TerrainModelEntryFuture::Done(model) = model else {
940                return None;
941            };
942
943            Some(&model.sprite_instances)
944        })
945    }
946
947    /*
948    pub fn update_terrain_locals(
949        &mut self,
950        renderer: &mut Renderer,
951        entity: Entity,
952        body: Skel::Body,
953        pos: Vec3<f32>,
954        ori: Quaternion<f32>,
955    ) {
956        let key = FigureKey {
957            body,
958            item_key: None,
959            extra: None,
960        };
961        if let Some(model) = self.models.get_mut(&key).and_then(|((model, _), _)| {
962            if let TerrainModelEntryFuture::Done(model) = model {
963                Some(model)
964            } else {
965                None
966            }
967        }) {
968            renderer.update_consts(&mut *model.terrain_locals, &[TerrainLocals::new(
969                pos,
970                ori,
971                Vec2::zero(),
972                0.0,
973            )])
974        }
975    }
976    */
977}