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}