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