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