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