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