1use crate::resources::*;
2
3pub(crate) struct MeshPrep {
10 pub vertices: Vec<Vertex>,
13 pub normal_line_verts: Vec<Vertex>,
16 pub computed_tangents: Option<Vec<[f32; 4]>>,
20}
21
22impl DeviceResources {
23 pub fn upload_mesh(
42 &mut self,
43 device: &wgpu::Device,
44 vertices: &[Vertex],
45 indices: &[u32],
46 ) -> crate::error::ViewportResult<crate::resources::mesh::mesh_store::MeshId> {
47 if vertices.is_empty() || indices.is_empty() {
48 return Err(crate::error::ViewportError::EmptyMesh {
49 positions: vertices.len(),
50 indices: indices.len(),
51 });
52 }
53 self.frame_upload_bytes += (vertices.len() * std::mem::size_of::<Vertex>()
54 + indices.len() * std::mem::size_of::<u32>()) as u64;
55 let mesh = Self::create_mesh(
56 device,
57 &self.object_bind_group_layout,
58 &self.fallback_texture.view,
59 &self.fallback_normal_map_view,
60 &self.fallback_ao_map_view,
61 &self.material_sampler,
62 &self.lut_sampler,
63 &self.content.fallback_lut_view,
64 &self.content.fallback_scalar_buf,
65 &self.fallback_texture.view,
66 &self.content.fallback_face_colour_buf,
67 &self.content.fallback_warp_buf,
68 &self.content.fallback_position_override_buf,
69 &self.content.fallback_normal_override_buf,
70 &self.fallback_metallic_roughness_texture_view,
71 &self.fallback_emissive_texture_view,
72 vertices,
73 indices,
74 );
75 Ok(self.mesh_store.insert(mesh))
76 }
77
78 pub fn upload_mesh_data(
90 &mut self,
91 device: &wgpu::Device,
92 data: &MeshData,
93 ) -> crate::error::ViewportResult<crate::resources::mesh::mesh_store::MeshId> {
94 Self::validate_mesh_data(data)?;
95 let prep = Self::prep_mesh_data(data);
96 Ok(self.assemble_mesh_data(device, data, prep))
97 }
98
99 pub(crate) fn prep_mesh_data(data: &MeshData) -> MeshPrep {
108 let computed_tangents: Option<Vec<[f32; 4]>> = if data.tangents.is_none() {
109 data.uvs.as_ref().map(|uvs| {
110 Self::compute_tangents(&data.positions, &data.normals, uvs, &data.indices)
111 })
112 } else {
113 None
114 };
115 let tangent_slice = data.tangents.as_deref().or(computed_tangents.as_deref());
116
117 let vertices: Vec<Vertex> = data
118 .positions
119 .iter()
120 .zip(data.normals.iter())
121 .enumerate()
122 .map(|(i, (p, n))| {
123 let uv = data
124 .uvs
125 .as_ref()
126 .and_then(|uvs| uvs.get(i))
127 .copied()
128 .unwrap_or([0.0, 0.0]);
129 let tangent = tangent_slice
130 .and_then(|ts| ts.get(i))
131 .copied()
132 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
133 Vertex {
134 position: *p,
135 normal: *n,
136 colour: [1.0, 1.0, 1.0, 1.0],
137 uv,
138 tangent,
139 }
140 })
141 .collect();
142
143 let normal_line_verts = Self::build_normal_lines(data);
144
145 MeshPrep {
146 vertices,
147 normal_line_verts,
148 computed_tangents,
149 }
150 }
151
152 pub(crate) fn assemble_mesh_data(
156 &mut self,
157 device: &wgpu::Device,
158 data: &MeshData,
159 prep: MeshPrep,
160 ) -> crate::resources::mesh::mesh_store::MeshId {
161 let MeshPrep {
162 vertices,
163 normal_line_verts,
164 computed_tangents,
165 } = prep;
166 let tangent_slice = data.tangents.as_deref().or(computed_tangents.as_deref());
167
168 let mut mesh = Self::create_mesh_with_normals(
169 device,
170 &self.object_bind_group_layout,
171 &self.fallback_texture.view,
172 &self.fallback_normal_map_view,
173 &self.fallback_ao_map_view,
174 &self.material_sampler,
175 &self.lut_sampler,
176 &self.content.fallback_lut_view,
177 &self.content.fallback_scalar_buf,
178 &self.fallback_texture.view,
179 &self.content.fallback_face_colour_buf,
180 &self.content.fallback_warp_buf,
181 &self.content.fallback_position_override_buf,
182 &self.content.fallback_normal_override_buf,
183 &self.fallback_metallic_roughness_texture_view,
184 &self.fallback_emissive_texture_view,
185 &vertices,
186 &data.indices,
187 Some(&normal_line_verts),
188 );
189 mesh.cpu_positions = Some(data.positions.clone());
190 mesh.cpu_indices = Some(data.indices.clone());
191 let (attr_bufs, attr_ranges, face_vbuf, face_attr_bufs, face_colour_bufs, vector_attr_bufs) =
192 Self::upload_attributes(
193 device,
194 &data.attributes,
195 &data.positions,
196 &data.normals,
197 &data.indices,
198 data.uvs.as_deref(),
199 tangent_slice,
200 );
201 mesh.attribute_buffers = attr_bufs;
202 mesh.attribute_ranges = attr_ranges;
203 mesh.face_vertex_buffer = face_vbuf;
204 mesh.face_attribute_buffers = face_attr_bufs;
205 mesh.face_colour_buffers = face_colour_bufs;
206 mesh.vector_attribute_buffers = vector_attr_bufs;
207 self.frame_upload_bytes += (vertices.len() * std::mem::size_of::<Vertex>()
208 + data.indices.len() * std::mem::size_of::<u32>())
209 as u64;
210 let id = self.mesh_store.insert(mesh);
211 tracing::debug!(
212 mesh_index = id.index(),
213 vertices = data.positions.len(),
214 indices = data.indices.len(),
215 "mesh uploaded"
216 );
217 id
218 }
219
220 pub fn begin_upload_mesh_data(
238 &mut self,
239 device: &wgpu::Device,
240 data: MeshData,
241 ) -> crate::error::ViewportResult<crate::resources::JobId> {
242 Self::validate_mesh_data(&data)?;
243
244 let slot =
245 crate::resources::ResultSlot::<crate::resources::mesh::mesh_store::MeshId>::new();
246 let slot_for_apply = slot.clone();
247 let device_for_apply = device.clone();
248
249 let id = {
250 let mut runner = self.jobs.lock().expect("upload job runner poisoned");
251 runner.submit_cpu(move |progress| {
252 progress.set(0.1);
253 let prep = DeviceResources::prep_mesh_data(&data);
254 progress.set(0.95);
255 Ok(crate::resources::upload_jobs::JobProduct::with_apply(
256 Box::new(move |resources: &mut DeviceResources| {
257 let mesh_id = resources.assemble_mesh_data(&device_for_apply, &data, prep);
258 slot_for_apply.set(mesh_id);
259 }),
260 ))
261 })
262 };
263
264 self.job_results
265 .mesh
266 .lock()
267 .expect("mesh result map poisoned")
268 .insert(id, slot);
269 Ok(id)
270 }
271
272 pub fn upload_result_mesh(
279 &mut self,
280 id: crate::resources::JobId,
281 ) -> crate::error::ViewportResult<crate::resources::mesh::mesh_store::MeshId> {
282 let mut map = self
283 .job_results
284 .mesh
285 .lock()
286 .expect("mesh result map poisoned");
287 let slot = match map.get(&id) {
288 Some(s) => s.clone(),
289 None => {
290 return Err(crate::error::ViewportError::JobResultMissing {
291 reason: "unknown id or wrong upload type",
292 });
293 }
294 };
295 match slot.take() {
296 Some(mesh_id) => {
297 map.remove(&id);
298 Ok(mesh_id)
299 }
300 None => Err(crate::error::ViewportError::JobNotReady),
301 }
302 }
303
304 pub fn upload_mesh_data_pickable(
315 &mut self,
316 device: &wgpu::Device,
317 data: &MeshData,
318 ) -> crate::error::ViewportResult<crate::resources::mesh::mesh_store::MeshId> {
319 self.upload_mesh_data(device, data)
320 }
321
322 pub fn set_pickable(
335 &mut self,
336 mesh_id: crate::resources::mesh::mesh_store::MeshId,
337 pickable: bool,
338 ) {
339 if let Some(mesh) = self.mesh_store.get_mut(mesh_id) {
340 if !pickable {
341 mesh.cpu_positions = None;
342 mesh.cpu_indices = None;
343 }
344 }
345 }
346
347 pub fn write_mesh_positions_normals(
369 &mut self,
370 queue: &wgpu::Queue,
371 mesh_id: crate::resources::mesh::mesh_store::MeshId,
372 positions: &[[f32; 3]],
373 normals: &[[f32; 3]],
374 ) -> crate::error::ViewportResult<()> {
375 use bytemuck::cast_slice;
376
377 if !self.mesh_store.contains(mesh_id) {
378 return Err(crate::error::ViewportError::StaleHandle {
379 index: mesh_id.index(),
380 count: self.mesh_store.len(),
381 });
382 }
383 if positions.len() != normals.len() {
384 return Err(crate::error::ViewportError::MeshLengthMismatch {
385 positions: positions.len(),
386 normals: normals.len(),
387 });
388 }
389
390 let existing_vertex_count = {
391 let mesh = self.mesh_store.get(mesh_id).unwrap();
392 debug_assert!(
393 mesh.position_override_buffer.is_none() && mesh.normal_override_buffer.is_none(),
394 "write_mesh_positions_normals called on mesh {} that has a GPU position/normal override bound. The CPU write and the GPU override race; call clear_position_override / clear_normal_override first.",
395 mesh_id.index(),
396 );
397 (mesh.vertex_buffer.size() / std::mem::size_of::<Vertex>() as u64) as usize
398 };
399 if positions.len() != existing_vertex_count {
400 return Err(crate::error::ViewportError::MeshLengthMismatch {
401 positions: positions.len(),
402 normals: existing_vertex_count,
403 });
404 }
405
406 let vertices: Vec<Vertex> = positions
407 .iter()
408 .zip(normals.iter())
409 .map(|(p, n)| Vertex {
410 position: *p,
411 normal: *n,
412 colour: [1.0, 1.0, 1.0, 1.0],
413 uv: [0.0, 0.0],
414 tangent: [0.0, 0.0, 0.0, 1.0],
415 })
416 .collect();
417
418 let has_normal_lines = self
419 .mesh_store
420 .get(mesh_id)
421 .unwrap()
422 .normal_line_buffer
423 .is_some();
424 let normal_line_verts: Option<Vec<Vertex>> = if has_normal_lines {
425 let normal_length = 0.1_f32;
426 let normal_colour = [0.627_f32, 0.769, 1.0, 1.0];
427 let mut verts = Vec::with_capacity(positions.len() * 2);
428 for (p, n) in positions.iter().zip(normals.iter()) {
429 let tip = [
430 p[0] + n[0] * normal_length,
431 p[1] + n[1] * normal_length,
432 p[2] + n[2] * normal_length,
433 ];
434 verts.push(Vertex {
435 position: *p,
436 normal: *n,
437 colour: normal_colour,
438 uv: [0.0, 0.0],
439 tangent: [0.0, 0.0, 0.0, 1.0],
440 });
441 verts.push(Vertex {
442 position: tip,
443 normal: *n,
444 colour: normal_colour,
445 uv: [0.0, 0.0],
446 tangent: [0.0, 0.0, 0.0, 1.0],
447 });
448 }
449 Some(verts)
450 } else {
451 None
452 };
453
454 let aabb = crate::scene::aabb::Aabb::from_positions(positions);
455 let mesh = self.mesh_store.get_mut(mesh_id).unwrap();
456 queue.write_buffer(&mesh.vertex_buffer, 0, cast_slice(&vertices));
457 if let (Some(nl_buf), Some(nl_verts)) = (&mesh.normal_line_buffer, &normal_line_verts) {
458 queue.write_buffer(nl_buf, 0, cast_slice(nl_verts.as_slice()));
459 }
460 mesh.aabb = aabb;
461 if let Some(ref mut cp) = mesh.cpu_positions {
462 *cp = positions.to_vec();
463 }
464
465 self.frame_upload_bytes += (vertices.len() * std::mem::size_of::<Vertex>()) as u64;
466 if let Some(ref nl) = normal_line_verts {
467 self.frame_upload_bytes += (nl.len() * std::mem::size_of::<Vertex>()) as u64;
468 }
469
470 Ok(())
471 }
472
473 pub fn set_position_override_buffer(
507 &mut self,
508 mesh_id: crate::resources::mesh::mesh_store::MeshId,
509 buffer: wgpu::Buffer,
510 ) -> crate::error::ViewportResult<()> {
511 let store_len = self.mesh_store.len();
512 let mesh =
513 self.mesh_store
514 .get_mut(mesh_id)
515 .ok_or(crate::error::ViewportError::StaleHandle {
516 index: mesh_id.index(),
517 count: store_len,
518 })?;
519 mesh.position_override_buffer = Some(buffer);
520 mesh.position_override_gen = mesh.position_override_gen.wrapping_add(1);
526 Ok(())
527 }
528
529 pub fn set_normal_override_buffer(
537 &mut self,
538 mesh_id: crate::resources::mesh::mesh_store::MeshId,
539 buffer: wgpu::Buffer,
540 ) -> crate::error::ViewportResult<()> {
541 let store_len = self.mesh_store.len();
542 let mesh =
543 self.mesh_store
544 .get_mut(mesh_id)
545 .ok_or(crate::error::ViewportError::StaleHandle {
546 index: mesh_id.index(),
547 count: store_len,
548 })?;
549 mesh.normal_override_buffer = Some(buffer);
550 mesh.normal_override_gen = mesh.normal_override_gen.wrapping_add(1);
553 Ok(())
554 }
555
556 pub fn clear_position_override(
565 &mut self,
566 mesh_id: crate::resources::mesh::mesh_store::MeshId,
567 ) -> crate::error::ViewportResult<()> {
568 let store_len = self.mesh_store.len();
569 let mesh =
570 self.mesh_store
571 .get_mut(mesh_id)
572 .ok_or(crate::error::ViewportError::StaleHandle {
573 index: mesh_id.index(),
574 count: store_len,
575 })?;
576 mesh.position_override_buffer = None;
577 mesh.position_override_gen = mesh.position_override_gen.wrapping_add(1);
578 Ok(())
579 }
580
581 pub fn clear_normal_override(
588 &mut self,
589 mesh_id: crate::resources::mesh::mesh_store::MeshId,
590 ) -> crate::error::ViewportResult<()> {
591 let store_len = self.mesh_store.len();
592 let mesh =
593 self.mesh_store
594 .get_mut(mesh_id)
595 .ok_or(crate::error::ViewportError::StaleHandle {
596 index: mesh_id.index(),
597 count: store_len,
598 })?;
599 mesh.normal_override_buffer = None;
600 mesh.normal_override_gen = mesh.normal_override_gen.wrapping_add(1);
601 Ok(())
602 }
603
604 pub fn replace_mesh_data(
622 &mut self,
623 device: &wgpu::Device,
624 queue: &wgpu::Queue,
625 mesh_id: crate::resources::mesh::mesh_store::MeshId,
626 data: &MeshData,
627 ) -> crate::error::ViewportResult<()> {
628 if !self.mesh_store.contains(mesh_id) {
629 return Err(crate::error::ViewportError::StaleHandle {
630 index: mesh_id.index(),
631 count: self.mesh_store.len(),
632 });
633 }
634 Self::validate_mesh_data(data)?;
635
636 let computed_tangents: Option<Vec<[f32; 4]>> = if data.tangents.is_none() {
637 data.uvs.as_ref().map(|uvs| {
638 Self::compute_tangents(&data.positions, &data.normals, uvs, &data.indices)
639 })
640 } else {
641 None
642 };
643 let tangent_slice = data.tangents.as_deref().or(computed_tangents.as_deref());
644
645 let vertices: Vec<Vertex> = data
646 .positions
647 .iter()
648 .zip(data.normals.iter())
649 .enumerate()
650 .map(|(i, (p, n))| {
651 let uv = data
652 .uvs
653 .as_ref()
654 .and_then(|uvs| uvs.get(i))
655 .copied()
656 .unwrap_or([0.0, 0.0]);
657 let tangent = tangent_slice
658 .and_then(|ts| ts.get(i))
659 .copied()
660 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
661 Vertex {
662 position: *p,
663 normal: *n,
664 colour: [1.0, 1.0, 1.0, 1.0],
665 uv,
666 tangent,
667 }
668 })
669 .collect();
670
671 {
674 let existing = self.mesh_store.get(mesh_id).unwrap();
675 let existing_vc =
676 (existing.vertex_buffer.size() / std::mem::size_of::<Vertex>() as u64) as usize;
677 let in_place = existing_vc == vertices.len()
678 && existing.index_count as usize == data.indices.len()
679 && data.attributes.is_empty();
680
681 if in_place {
682 use bytemuck::cast_slice;
683 let edge_indices =
684 crate::resources::mesh::geometry::generate_edge_indices(&data.indices);
685 let normal_line_verts = Self::build_normal_lines(data);
686 let aabb = crate::scene::aabb::Aabb::from_positions(&data.positions);
687
688 let mesh = self.mesh_store.get_mut(mesh_id).unwrap();
689 queue.write_buffer(&mesh.vertex_buffer, 0, cast_slice(&vertices));
690 queue.write_buffer(&mesh.index_buffer, 0, cast_slice(data.indices.as_slice()));
691 let edge_byte_len = (edge_indices.len() * std::mem::size_of::<u32>()) as u64;
692 if edge_byte_len <= mesh.edge_index_buffer.size() {
693 queue.write_buffer(&mesh.edge_index_buffer, 0, cast_slice(&edge_indices));
694 mesh.edge_index_count = edge_indices.len() as u32;
695 }
696 if let Some(ref nl_buf) = mesh.normal_line_buffer {
697 queue.write_buffer(nl_buf, 0, cast_slice(&normal_line_verts));
698 }
699 mesh.aabb = aabb;
700 mesh.cpu_positions = Some(data.positions.clone());
701 mesh.cpu_indices = Some(data.indices.clone());
702
703 self.frame_upload_bytes += (vertices.len() * std::mem::size_of::<Vertex>()
704 + data.indices.len() * std::mem::size_of::<u32>())
705 as u64;
706 tracing::debug!(
707 mesh_index = mesh_id.index(),
708 vertices = data.positions.len(),
709 "mesh updated in place"
710 );
711 return Ok(());
712 }
713 }
714
715 let normal_line_verts = Self::build_normal_lines(data);
716 let mut new_mesh = Self::create_mesh_with_normals(
717 device,
718 &self.object_bind_group_layout,
719 &self.fallback_texture.view,
720 &self.fallback_normal_map_view,
721 &self.fallback_ao_map_view,
722 &self.material_sampler,
723 &self.lut_sampler,
724 &self.content.fallback_lut_view,
725 &self.content.fallback_scalar_buf,
726 &self.fallback_texture.view,
727 &self.content.fallback_face_colour_buf,
728 &self.content.fallback_warp_buf,
729 &self.content.fallback_position_override_buf,
730 &self.content.fallback_normal_override_buf,
731 &self.fallback_metallic_roughness_texture_view,
732 &self.fallback_emissive_texture_view,
733 &vertices,
734 &data.indices,
735 Some(&normal_line_verts),
736 );
737 new_mesh.cpu_positions = Some(data.positions.clone());
738 new_mesh.cpu_indices = Some(data.indices.clone());
739 let (attr_bufs, attr_ranges, face_vbuf, face_attr_bufs, face_colour_bufs, vector_attr_bufs) =
740 Self::upload_attributes(
741 device,
742 &data.attributes,
743 &data.positions,
744 &data.normals,
745 &data.indices,
746 data.uvs.as_deref(),
747 tangent_slice,
748 );
749 new_mesh.attribute_buffers = attr_bufs;
750 new_mesh.attribute_ranges = attr_ranges;
751 new_mesh.face_vertex_buffer = face_vbuf;
752 new_mesh.face_attribute_buffers = face_attr_bufs;
753 new_mesh.face_colour_buffers = face_colour_bufs;
754 new_mesh.vector_attribute_buffers = vector_attr_bufs;
755 self.frame_upload_bytes += (vertices.len() * std::mem::size_of::<Vertex>()
756 + data.indices.len() * std::mem::size_of::<u32>())
757 as u64;
758 let _ = self.mesh_store.replace(mesh_id, new_mesh);
759 tracing::debug!(
760 mesh_index = mesh_id.index(),
761 vertices = data.positions.len(),
762 indices = data.indices.len(),
763 "mesh replaced"
764 );
765 Ok(())
766 }
767
768 pub fn mesh(&self, id: crate::resources::mesh::mesh_store::MeshId) -> Option<&GpuMesh> {
770 self.mesh_store.get(id)
771 }
772
773 pub fn mesh_slot_count(&self) -> usize {
775 self.mesh_store.slot_count()
776 }
777
778 #[deprecated(note = "renamed to free_mesh")]
780 pub fn remove_mesh(&mut self, id: crate::resources::mesh::mesh_store::MeshId) -> bool {
781 self.free_mesh(id)
782 }
783
784 pub fn free_mesh(&mut self, id: crate::resources::mesh::mesh_store::MeshId) -> bool {
800 self.mesh_store.remove(id)
801 }
802
803 pub fn upload_volume_mesh(
817 &mut self,
818 device: &wgpu::Device,
819 data: &crate::resources::volume::volume_mesh::VolumeMeshData,
820 ) -> crate::error::ViewportResult<crate::VolumeMeshItem> {
821 let (mesh_data, face_to_cell) =
822 crate::resources::volume::volume_mesh::extract_boundary_faces(data);
823 let mesh_id = self.upload_mesh_data(device, &mesh_data)?;
824 Ok(crate::VolumeMeshItem::new(mesh_id, face_to_cell))
825 }
826
827 pub fn upload_volume_mesh_with_transparency(
846 &mut self,
847 device: &wgpu::Device,
848 data: crate::resources::volume::volume_mesh::VolumeMeshData,
849 scalar_attribute: &str,
850 ) -> crate::error::ViewportResult<crate::VolumeMeshItem> {
851 let (mesh_data, face_to_cell) =
852 crate::resources::volume::volume_mesh::extract_boundary_faces(&data);
853 let mesh_id = self.upload_mesh_data(device, &mesh_data)?;
854 let (pt_id, _, _) = self.upload_projected_tet(device, &data, scalar_attribute)?;
855 let mut item = crate::VolumeMeshItem::new(mesh_id, face_to_cell);
856 item.projected_tet_id = Some(pt_id);
857 item.volume_mesh_data = Some(std::sync::Arc::new(data));
858 Ok(item)
859 }
860
861 pub fn upload_clipped_volume_mesh(
870 &mut self,
871 device: &wgpu::Device,
872 data: &crate::resources::volume::volume_mesh::VolumeMeshData,
873 clip_planes: &[[f32; 4]],
874 ) -> crate::error::ViewportResult<crate::VolumeMeshItem> {
875 let (mesh_data, face_to_cell) =
876 crate::resources::volume::volume_mesh::extract_clipped_volume_faces(data, clip_planes);
877 let mesh_id = self.upload_mesh_data(device, &mesh_data)?;
878 Ok(crate::VolumeMeshItem::new(mesh_id, face_to_cell))
879 }
880
881 pub fn replace_clipped_volume_mesh(
889 &mut self,
890 device: &wgpu::Device,
891 queue: &wgpu::Queue,
892 mesh_id: crate::resources::mesh::mesh_store::MeshId,
893 data: &crate::resources::volume::volume_mesh::VolumeMeshData,
894 clip_planes: &[[f32; 4]],
895 ) -> crate::error::ViewportResult<Vec<u32>> {
896 let (mesh_data, face_to_cell) =
897 crate::resources::volume::volume_mesh::extract_clipped_volume_faces(data, clip_planes);
898 self.replace_mesh_data(device, queue, mesh_id, &mesh_data)?;
899 Ok(face_to_cell)
900 }
901
902 pub fn replace_sparse_volume_grid_data(
908 &mut self,
909 device: &wgpu::Device,
910 queue: &wgpu::Queue,
911 mesh_id: crate::resources::mesh::mesh_store::MeshId,
912 data: &crate::resources::volume::sparse_volume::SparseVolumeGridData,
913 ) -> crate::error::ViewportResult<()> {
914 let mesh_data = crate::resources::volume::sparse_volume::extract_sparse_boundary(data);
915 self.replace_mesh_data(device, queue, mesh_id, &mesh_data)
916 }
917
918 pub fn upload_sparse_volume_grid_data(
928 &mut self,
929 device: &wgpu::Device,
930 data: &crate::resources::volume::sparse_volume::SparseVolumeGridData,
931 ) -> crate::error::ViewportResult<crate::resources::mesh::mesh_store::MeshId> {
932 let mesh_data = crate::resources::volume::sparse_volume::extract_sparse_boundary(data);
933 self.upload_mesh_data(device, &mesh_data)
934 }
935
936 pub fn begin_upload_volume_mesh(
950 &mut self,
951 device: &wgpu::Device,
952 data: crate::resources::volume::volume_mesh::VolumeMeshData,
953 ) -> crate::resources::JobId {
954 let slot = crate::resources::ResultSlot::<(
955 crate::resources::mesh::mesh_store::MeshId,
956 Vec<u32>,
957 )>::new();
958 let slot_for_apply = slot.clone();
959 let device_for_apply = device.clone();
960
961 let id = {
962 let mut runner = self.jobs.lock().expect("upload job runner poisoned");
963 runner.submit_cpu(move |progress| {
964 progress.set(0.1);
965 let (mesh_data, face_to_cell) =
966 crate::resources::volume::volume_mesh::extract_boundary_faces(&data);
967 progress.set(0.5);
968 DeviceResources::validate_mesh_data(&mesh_data)?;
969 let prep = DeviceResources::prep_mesh_data(&mesh_data);
970 progress.set(0.95);
971 Ok(crate::resources::upload_jobs::JobProduct::with_apply(
972 Box::new(move |resources: &mut DeviceResources| {
973 let mesh_id =
974 resources.assemble_mesh_data(&device_for_apply, &mesh_data, prep);
975 slot_for_apply.set((mesh_id, face_to_cell));
976 }),
977 ))
978 })
979 };
980
981 self.job_results
982 .volume_mesh
983 .lock()
984 .expect("volume mesh result map poisoned")
985 .insert(id, slot);
986 id
987 }
988
989 pub fn upload_result_volume_mesh(
993 &mut self,
994 id: crate::resources::JobId,
995 ) -> crate::error::ViewportResult<crate::VolumeMeshItem> {
996 let mut map = self
997 .job_results
998 .volume_mesh
999 .lock()
1000 .expect("volume mesh result map poisoned");
1001 let slot = match map.get(&id) {
1002 Some(s) => s.clone(),
1003 None => {
1004 return Err(crate::error::ViewportError::JobResultMissing {
1005 reason: "unknown id or wrong upload type",
1006 });
1007 }
1008 };
1009 match slot.take() {
1010 Some((mesh_id, face_to_cell)) => {
1011 map.remove(&id);
1012 Ok(crate::VolumeMeshItem::new(mesh_id, face_to_cell))
1013 }
1014 None => Err(crate::error::ViewportError::JobNotReady),
1015 }
1016 }
1017
1018 pub fn begin_upload_clipped_volume_mesh(
1022 &mut self,
1023 device: &wgpu::Device,
1024 data: crate::resources::volume::volume_mesh::VolumeMeshData,
1025 clip_planes: Vec<[f32; 4]>,
1026 ) -> crate::resources::JobId {
1027 let slot = crate::resources::ResultSlot::<(
1028 crate::resources::mesh::mesh_store::MeshId,
1029 Vec<u32>,
1030 )>::new();
1031 let slot_for_apply = slot.clone();
1032 let device_for_apply = device.clone();
1033
1034 let id = {
1035 let mut runner = self.jobs.lock().expect("upload job runner poisoned");
1036 runner.submit_cpu(move |progress| {
1037 progress.set(0.1);
1038 let (mesh_data, face_to_cell) =
1039 crate::resources::volume::volume_mesh::extract_clipped_volume_faces(
1040 &data,
1041 &clip_planes,
1042 );
1043 progress.set(0.5);
1044 DeviceResources::validate_mesh_data(&mesh_data)?;
1045 let prep = DeviceResources::prep_mesh_data(&mesh_data);
1046 progress.set(0.95);
1047 Ok(crate::resources::upload_jobs::JobProduct::with_apply(
1048 Box::new(move |resources: &mut DeviceResources| {
1049 let mesh_id =
1050 resources.assemble_mesh_data(&device_for_apply, &mesh_data, prep);
1051 slot_for_apply.set((mesh_id, face_to_cell));
1052 }),
1053 ))
1054 })
1055 };
1056
1057 self.job_results
1058 .clipped_volume_mesh
1059 .lock()
1060 .expect("clipped volume mesh result map poisoned")
1061 .insert(id, slot);
1062 id
1063 }
1064
1065 pub fn upload_result_clipped_volume_mesh(
1069 &mut self,
1070 id: crate::resources::JobId,
1071 ) -> crate::error::ViewportResult<crate::VolumeMeshItem> {
1072 let mut map = self
1073 .job_results
1074 .clipped_volume_mesh
1075 .lock()
1076 .expect("clipped volume mesh result map poisoned");
1077 let slot = match map.get(&id) {
1078 Some(s) => s.clone(),
1079 None => {
1080 return Err(crate::error::ViewportError::JobResultMissing {
1081 reason: "unknown id or wrong upload type",
1082 });
1083 }
1084 };
1085 match slot.take() {
1086 Some((mesh_id, face_to_cell)) => {
1087 map.remove(&id);
1088 Ok(crate::VolumeMeshItem::new(mesh_id, face_to_cell))
1089 }
1090 None => Err(crate::error::ViewportError::JobNotReady),
1091 }
1092 }
1093
1094 pub fn begin_upload_sparse_volume_grid_data(
1096 &mut self,
1097 device: &wgpu::Device,
1098 data: crate::resources::volume::sparse_volume::SparseVolumeGridData,
1099 ) -> crate::resources::JobId {
1100 let slot =
1101 crate::resources::ResultSlot::<crate::resources::mesh::mesh_store::MeshId>::new();
1102 let slot_for_apply = slot.clone();
1103 let device_for_apply = device.clone();
1104
1105 let id = {
1106 let mut runner = self.jobs.lock().expect("upload job runner poisoned");
1107 runner.submit_cpu(move |progress| {
1108 progress.set(0.1);
1109 let mesh_data =
1110 crate::resources::volume::sparse_volume::extract_sparse_boundary(&data);
1111 progress.set(0.5);
1112 DeviceResources::validate_mesh_data(&mesh_data)?;
1113 let prep = DeviceResources::prep_mesh_data(&mesh_data);
1114 progress.set(0.95);
1115 Ok(crate::resources::upload_jobs::JobProduct::with_apply(
1116 Box::new(move |resources: &mut DeviceResources| {
1117 let mesh_id =
1118 resources.assemble_mesh_data(&device_for_apply, &mesh_data, prep);
1119 slot_for_apply.set(mesh_id);
1120 }),
1121 ))
1122 })
1123 };
1124
1125 self.job_results
1126 .sparse_volume_grid
1127 .lock()
1128 .expect("sparse volume grid result map poisoned")
1129 .insert(id, slot);
1130 id
1131 }
1132
1133 pub fn upload_result_sparse_volume_grid(
1137 &mut self,
1138 id: crate::resources::JobId,
1139 ) -> crate::error::ViewportResult<crate::resources::mesh::mesh_store::MeshId> {
1140 let mut map = self
1141 .job_results
1142 .sparse_volume_grid
1143 .lock()
1144 .expect("sparse volume grid result map poisoned");
1145 let slot = match map.get(&id) {
1146 Some(s) => s.clone(),
1147 None => {
1148 return Err(crate::error::ViewportError::JobResultMissing {
1149 reason: "unknown id or wrong upload type",
1150 });
1151 }
1152 };
1153 match slot.take() {
1154 Some(mesh_id) => {
1155 map.remove(&id);
1156 Ok(mesh_id)
1157 }
1158 None => Err(crate::error::ViewportError::JobNotReady),
1159 }
1160 }
1161
1162 fn upload_attributes(
1173 device: &wgpu::Device,
1174 attributes: &std::collections::HashMap<String, AttributeData>,
1175 positions: &[[f32; 3]],
1176 normals: &[[f32; 3]],
1177 indices: &[u32],
1178 uvs: Option<&[[f32; 2]]>,
1179 tangents: Option<&[[f32; 4]]>,
1180 ) -> (
1181 std::collections::HashMap<String, wgpu::Buffer>,
1182 std::collections::HashMap<String, (f32, f32)>,
1183 Option<wgpu::Buffer>,
1184 std::collections::HashMap<String, wgpu::Buffer>,
1185 std::collections::HashMap<String, wgpu::Buffer>,
1186 std::collections::HashMap<String, wgpu::Buffer>,
1187 ) {
1188 let mut bufs = std::collections::HashMap::new();
1189 let mut ranges = std::collections::HashMap::new();
1190 let mut face_attr_bufs: std::collections::HashMap<String, wgpu::Buffer> =
1191 std::collections::HashMap::new();
1192 let mut vector_attr_bufs: std::collections::HashMap<String, wgpu::Buffer> =
1193 std::collections::HashMap::new();
1194 let mut face_colour_bufs: std::collections::HashMap<String, wgpu::Buffer> =
1195 std::collections::HashMap::new();
1196 let mut face_vbuf: Option<wgpu::Buffer> = None;
1197
1198 let n_tris = indices.len() / 3;
1199
1200 for (name, attr_data) in attributes {
1201 match attr_data {
1202 AttributeData::Vertex(v) => {
1203 let scalars = v.clone();
1204 if scalars.is_empty() {
1205 continue;
1206 }
1207 let min = scalars.iter().cloned().fold(f32::INFINITY, f32::min);
1208 let max = scalars.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1209 let buf =
1210 Self::create_storage_buffer_f32(device, &format!("attr_{name}"), &scalars);
1211 bufs.insert(name.clone(), buf);
1212 ranges.insert(name.clone(), (min, max));
1213 }
1214 AttributeData::Cell(c) => {
1215 let scalars = Self::expand_cell_to_vertex(c, positions, indices);
1216 if scalars.is_empty() {
1217 continue;
1218 }
1219 let min = scalars.iter().cloned().fold(f32::INFINITY, f32::min);
1220 let max = scalars.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1221 let buf =
1222 Self::create_storage_buffer_f32(device, &format!("attr_{name}"), &scalars);
1223 bufs.insert(name.clone(), buf);
1224 ranges.insert(name.clone(), (min, max));
1225 }
1226 AttributeData::Face(f) => {
1227 if face_vbuf.is_none() {
1229 face_vbuf = Some(Self::build_face_vertex_buffer(
1230 device, positions, normals, indices, uvs, tangents,
1231 ));
1232 }
1233 let expanded = Self::expand_face_scalars_to_3n(f, n_tris);
1234 if expanded.is_empty() {
1235 continue;
1236 }
1237 let min = expanded.iter().cloned().fold(f32::INFINITY, f32::min);
1238 let max = expanded.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1239 let buf = Self::create_storage_buffer_f32(
1240 device,
1241 &format!("face_attr_{name}"),
1242 &expanded,
1243 );
1244 face_attr_bufs.insert(name.clone(), buf);
1245 ranges.insert(name.clone(), (min, max));
1246 }
1247 AttributeData::FaceColour(colours) => {
1248 if face_vbuf.is_none() {
1250 face_vbuf = Some(Self::build_face_vertex_buffer(
1251 device, positions, normals, indices, uvs, tangents,
1252 ));
1253 }
1254 let expanded = Self::expand_face_colours_to_3n(colours, n_tris);
1255 if expanded.is_empty() {
1256 continue;
1257 }
1258 let byte_len = std::mem::size_of::<[f32; 4]>() * expanded.len();
1259 let buf = device.create_buffer(&wgpu::BufferDescriptor {
1260 label: Some(&format!("face_colour_{name}")),
1261 size: byte_len as u64,
1262 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1263 mapped_at_creation: true,
1264 });
1265 {
1266 let mut view = buf.slice(..).get_mapped_range_mut();
1267 view.copy_from_slice(bytemuck::cast_slice(&expanded));
1268 }
1269 buf.unmap();
1270 face_colour_bufs.insert(name.clone(), buf);
1271 }
1272 AttributeData::Edge(e) => {
1273 let scalars = Self::expand_edge_to_vertex(e, positions, indices);
1276 if scalars.is_empty() {
1277 continue;
1278 }
1279 let min = scalars.iter().cloned().fold(f32::INFINITY, f32::min);
1280 let max = scalars.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1281 let buf =
1282 Self::create_storage_buffer_f32(device, &format!("attr_{name}"), &scalars);
1283 bufs.insert(name.clone(), buf);
1284 ranges.insert(name.clone(), (min, max));
1285 }
1286 AttributeData::Halfedge(h) | AttributeData::Corner(h) => {
1287 if face_vbuf.is_none() {
1290 face_vbuf = Some(Self::build_face_vertex_buffer(
1291 device, positions, normals, indices, uvs, tangents,
1292 ));
1293 }
1294 if h.is_empty() {
1295 continue;
1296 }
1297 let expanded = h.as_slice();
1298 let min = expanded.iter().cloned().fold(f32::INFINITY, f32::min);
1299 let max = expanded.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1300 let buf = Self::create_storage_buffer_f32(
1301 device,
1302 &format!("face_attr_{name}"),
1303 &expanded,
1304 );
1305 face_attr_bufs.insert(name.clone(), buf);
1306 ranges.insert(name.clone(), (min, max));
1307 }
1308 AttributeData::VertexVector(v) => {
1309 if v.is_empty() {
1312 continue;
1313 }
1314 let flat: Vec<f32> = v.iter().flat_map(|&[x, y, z]| [x, y, z]).collect();
1315 let byte_len = (std::mem::size_of::<f32>() * flat.len()) as u64;
1316 let buf = device.create_buffer(&wgpu::BufferDescriptor {
1317 label: Some(&format!("vec_attr_{name}")),
1318 size: byte_len,
1319 usage: wgpu::BufferUsages::VERTEX
1320 | wgpu::BufferUsages::STORAGE
1321 | wgpu::BufferUsages::COPY_DST,
1322 mapped_at_creation: true,
1323 });
1324 {
1325 let mut view = buf.slice(..).get_mapped_range_mut();
1326 view.copy_from_slice(bytemuck::cast_slice(&flat));
1327 }
1328 buf.unmap();
1329 vector_attr_bufs.insert(name.clone(), buf);
1330 }
1331 }
1332 }
1333 (
1334 bufs,
1335 ranges,
1336 face_vbuf,
1337 face_attr_bufs,
1338 face_colour_bufs,
1339 vector_attr_bufs,
1340 )
1341 }
1342
1343 fn create_storage_buffer_f32(device: &wgpu::Device, label: &str, data: &[f32]) -> wgpu::Buffer {
1345 let buf = device.create_buffer(&wgpu::BufferDescriptor {
1346 label: Some(label),
1347 size: (std::mem::size_of::<f32>() * data.len()) as u64,
1348 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1349 mapped_at_creation: true,
1350 });
1351 {
1352 let mut view = buf.slice(..).get_mapped_range_mut();
1353 view.copy_from_slice(bytemuck::cast_slice(data));
1354 }
1355 buf.unmap();
1356 buf
1357 }
1358
1359 fn build_face_vertex_buffer(
1361 device: &wgpu::Device,
1362 positions: &[[f32; 3]],
1363 normals: &[[f32; 3]],
1364 indices: &[u32],
1365 uvs: Option<&[[f32; 2]]>,
1366 tangents: Option<&[[f32; 4]]>,
1367 ) -> wgpu::Buffer {
1368 let n_tris = indices.len() / 3;
1369 let mut verts: Vec<Vertex> = Vec::with_capacity(n_tris * 3);
1370 for tri in indices.chunks(3) {
1371 for &vi in tri {
1372 let vi = vi as usize;
1373 let uv = uvs.and_then(|u| u.get(vi)).copied().unwrap_or([0.0, 0.0]);
1374 let tangent = tangents
1375 .and_then(|t| t.get(vi))
1376 .copied()
1377 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
1378 verts.push(Vertex {
1379 position: positions.get(vi).copied().unwrap_or([0.0, 0.0, 0.0]),
1380 normal: normals.get(vi).copied().unwrap_or([0.0, 1.0, 0.0]),
1381 colour: [1.0, 1.0, 1.0, 1.0],
1382 uv,
1383 tangent,
1384 });
1385 }
1386 }
1387 let buf = device.create_buffer(&wgpu::BufferDescriptor {
1388 label: Some("face_vertex_buf"),
1389 size: (std::mem::size_of::<Vertex>() * verts.len().max(1)) as u64,
1390 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1391 mapped_at_creation: true,
1392 });
1393 {
1394 let mut view = buf.slice(..).get_mapped_range_mut();
1395 view.copy_from_slice(bytemuck::cast_slice(&verts));
1396 }
1397 buf.unmap();
1398 buf
1399 }
1400
1401 fn expand_face_scalars_to_3n(values: &[f32], n_tris: usize) -> Vec<f32> {
1403 let mut out = Vec::with_capacity(n_tris * 3);
1404 for i in 0..n_tris {
1405 let v = values.get(i).copied().unwrap_or(0.0);
1406 out.push(v);
1407 out.push(v);
1408 out.push(v);
1409 }
1410 out
1411 }
1412
1413 fn expand_face_colours_to_3n(colours: &[[f32; 4]], n_tris: usize) -> Vec<[f32; 4]> {
1415 let mut out = Vec::with_capacity(n_tris * 3);
1416 for i in 0..n_tris {
1417 let c = colours.get(i).copied().unwrap_or([1.0, 1.0, 1.0, 1.0]);
1418 out.push(c);
1419 out.push(c);
1420 out.push(c);
1421 }
1422 out
1423 }
1424
1425 fn expand_edge_to_vertex(
1432 edge_values: &[f32],
1433 positions: &[[f32; 3]],
1434 indices: &[u32],
1435 ) -> Vec<f32> {
1436 let n = positions.len();
1437 let mut sum = vec![0.0f32; n];
1438 let mut count = vec![0u32; n];
1439 for (tri_idx, chunk) in indices.chunks(3).enumerate() {
1440 for k in 0..3 {
1441 let v = edge_values.get(3 * tri_idx + k).copied().unwrap_or(0.0);
1442 let vi0 = chunk[k] as usize;
1443 let vi1 = chunk[(k + 1) % 3] as usize;
1444 if vi0 < n {
1445 sum[vi0] += v;
1446 count[vi0] += 1;
1447 }
1448 if vi1 < n {
1449 sum[vi1] += v;
1450 count[vi1] += 1;
1451 }
1452 }
1453 }
1454 (0..n)
1455 .map(|i| {
1456 if count[i] > 0 {
1457 sum[i] / count[i] as f32
1458 } else {
1459 0.0
1460 }
1461 })
1462 .collect()
1463 }
1464
1465 fn expand_cell_to_vertex(
1467 cell_values: &[f32],
1468 positions: &[[f32; 3]],
1469 indices: &[u32],
1470 ) -> Vec<f32> {
1471 let n = positions.len();
1472 let mut sum = vec![0.0f32; n];
1473 let mut count = vec![0u32; n];
1474 for (tri_idx, chunk) in indices.chunks(3).enumerate() {
1475 let v = cell_values.get(tri_idx).copied().unwrap_or(0.0);
1476 for &vi in chunk {
1477 let vi = vi as usize;
1478 if vi < n {
1479 sum[vi] += v;
1480 count[vi] += 1;
1481 }
1482 }
1483 }
1484 (0..n)
1485 .map(|i| {
1486 if count[i] > 0 {
1487 sum[i] / count[i] as f32
1488 } else {
1489 0.0
1490 }
1491 })
1492 .collect()
1493 }
1494
1495 fn compute_tangents(
1503 positions: &[[f32; 3]],
1504 normals: &[[f32; 3]],
1505 uvs: &[[f32; 2]],
1506 indices: &[u32],
1507 ) -> Vec<[f32; 4]> {
1508 let n = positions.len();
1509 let tri_count = indices.len() / 3;
1510
1511 let mut tan1 = vec![[0.0f32; 3]; n];
1530 let mut tan2 = vec![[0.0f32; 3]; n];
1531 for t in 0..tri_count {
1532 let i0 = indices[t * 3] as usize;
1533 let i1 = indices[t * 3 + 1] as usize;
1534 let i2 = indices[t * 3 + 2] as usize;
1535
1536 let p0 = positions[i0];
1537 let p1 = positions[i1];
1538 let p2 = positions[i2];
1539 let uv0 = uvs[i0];
1540 let uv1 = uvs[i1];
1541 let uv2 = uvs[i2];
1542
1543 let e1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
1544 let e2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
1545 let du1 = uv1[0] - uv0[0];
1546 let dv1 = uv1[1] - uv0[1];
1547 let du2 = uv2[0] - uv0[0];
1548 let dv2 = uv2[1] - uv0[1];
1549
1550 let det = du1 * dv2 - du2 * dv1;
1551 if det.abs() < 1e-10 {
1552 continue;
1553 }
1554 let r = 1.0 / det;
1555
1556 let sdir = [
1557 (dv2 * e1[0] - dv1 * e2[0]) * r,
1558 (dv2 * e1[1] - dv1 * e2[1]) * r,
1559 (dv2 * e1[2] - dv1 * e2[2]) * r,
1560 ];
1561 let tdir = [
1562 (du1 * e2[0] - du2 * e1[0]) * r,
1563 (du1 * e2[1] - du2 * e1[1]) * r,
1564 (du1 * e2[2] - du2 * e1[2]) * r,
1565 ];
1566
1567 for &vi in &[i0, i1, i2] {
1568 for k in 0..3 {
1569 tan1[vi][k] += sdir[k];
1570 tan2[vi][k] += tdir[k];
1571 }
1572 }
1573 }
1574
1575 (0..n)
1578 .map(|i| {
1579 let n_v = normals[i];
1580 let t = tan1[i];
1581 let dot = n_v[0] * t[0] + n_v[1] * t[1] + n_v[2] * t[2];
1582 let tx = t[0] - n_v[0] * dot;
1583 let ty = t[1] - n_v[1] * dot;
1584 let tz = t[2] - n_v[2] * dot;
1585 let len = (tx * tx + ty * ty + tz * tz).sqrt();
1586 let (tx, ty, tz) = if len > 1e-7 {
1587 (tx / len, ty / len, tz / len)
1588 } else {
1589 (1.0, 0.0, 0.0)
1590 };
1591 let cx = n_v[1] * tz - n_v[2] * ty;
1592 let cy = n_v[2] * tx - n_v[0] * tz;
1593 let cz = n_v[0] * ty - n_v[1] * tx;
1594 let w = if cx * tan2[i][0] + cy * tan2[i][1] + cz * tan2[i][2] < 0.0 {
1595 -1.0
1596 } else {
1597 1.0
1598 };
1599 [tx, ty, tz, w]
1600 })
1601 .collect()
1602 }
1603
1604 fn validate_mesh_data(data: &MeshData) -> crate::error::ViewportResult<()> {
1606 if data.positions.is_empty() || data.indices.is_empty() {
1607 return Err(crate::error::ViewportError::EmptyMesh {
1608 positions: data.positions.len(),
1609 indices: data.indices.len(),
1610 });
1611 }
1612 if data.positions.len() != data.normals.len() {
1613 return Err(crate::error::ViewportError::MeshLengthMismatch {
1614 positions: data.positions.len(),
1615 normals: data.normals.len(),
1616 });
1617 }
1618 let vertex_count = data.positions.len();
1619 for &idx in &data.indices {
1620 if (idx as usize) >= vertex_count {
1621 return Err(crate::error::ViewportError::InvalidVertexIndex {
1622 vertex_index: idx,
1623 vertex_count,
1624 });
1625 }
1626 }
1627 Ok(())
1628 }
1629
1630 fn build_normal_lines(data: &MeshData) -> Vec<Vertex> {
1632 let normal_colour = [0.627_f32, 0.769, 1.0, 1.0];
1633 let normal_length = 0.1_f32;
1634 let mut normal_line_verts: Vec<Vertex> = Vec::with_capacity(data.positions.len() * 2);
1635 for (p, n) in data.positions.iter().zip(data.normals.iter()) {
1636 let tip = [
1637 p[0] + n[0] * normal_length,
1638 p[1] + n[1] * normal_length,
1639 p[2] + n[2] * normal_length,
1640 ];
1641 normal_line_verts.push(Vertex {
1642 position: *p,
1643 normal: *n,
1644 colour: normal_colour,
1645 uv: [0.0, 0.0],
1646 tangent: [0.0, 0.0, 0.0, 1.0],
1647 });
1648 normal_line_verts.push(Vertex {
1649 position: tip,
1650 normal: *n,
1651 colour: normal_colour,
1652 uv: [0.0, 0.0],
1653 tangent: [0.0, 0.0, 0.0, 1.0],
1654 });
1655 }
1656 normal_line_verts
1657 }
1658
1659 pub(crate) fn create_mesh(
1660 device: &wgpu::Device,
1661 object_bgl: &wgpu::BindGroupLayout,
1662 fallback_albedo_view: &wgpu::TextureView,
1663 fallback_normal_view: &wgpu::TextureView,
1664 fallback_ao_view: &wgpu::TextureView,
1665 fallback_sampler: &wgpu::Sampler,
1666 lut_sampler: &wgpu::Sampler,
1667 fallback_lut_view: &wgpu::TextureView,
1668 fallback_scalar_buf: &wgpu::Buffer,
1669 fallback_matcap_view: &wgpu::TextureView,
1670 fallback_face_colour_buf: &wgpu::Buffer,
1671 fallback_warp_buf: &wgpu::Buffer,
1672 fallback_position_override_buf: &wgpu::Buffer,
1673 fallback_normal_override_buf: &wgpu::Buffer,
1674 fallback_metallic_roughness_view: &wgpu::TextureView,
1675 fallback_emissive_view: &wgpu::TextureView,
1676 vertices: &[Vertex],
1677 indices: &[u32],
1678 ) -> GpuMesh {
1679 Self::create_mesh_with_normals(
1680 device,
1681 object_bgl,
1682 fallback_albedo_view,
1683 fallback_normal_view,
1684 fallback_ao_view,
1685 fallback_sampler,
1686 lut_sampler,
1687 fallback_lut_view,
1688 fallback_scalar_buf,
1689 fallback_matcap_view,
1690 fallback_face_colour_buf,
1691 fallback_warp_buf,
1692 fallback_position_override_buf,
1693 fallback_normal_override_buf,
1694 fallback_metallic_roughness_view,
1695 fallback_emissive_view,
1696 vertices,
1697 indices,
1698 None,
1699 )
1700 }
1701
1702 pub(crate) fn create_mesh_with_normals(
1703 device: &wgpu::Device,
1704 object_bgl: &wgpu::BindGroupLayout,
1705 fallback_albedo_view: &wgpu::TextureView,
1706 fallback_normal_view: &wgpu::TextureView,
1707 fallback_ao_view: &wgpu::TextureView,
1708 fallback_sampler: &wgpu::Sampler,
1709 lut_sampler: &wgpu::Sampler,
1710 fallback_lut_view: &wgpu::TextureView,
1711 fallback_scalar_buf: &wgpu::Buffer,
1712 fallback_matcap_view: &wgpu::TextureView,
1713 fallback_face_colour_buf: &wgpu::Buffer,
1714 fallback_warp_buf: &wgpu::Buffer,
1715 fallback_position_override_buf: &wgpu::Buffer,
1716 fallback_normal_override_buf: &wgpu::Buffer,
1717 fallback_metallic_roughness_view: &wgpu::TextureView,
1718 fallback_emissive_view: &wgpu::TextureView,
1719 vertices: &[Vertex],
1720 indices: &[u32],
1721 normal_line_verts: Option<&[Vertex]>,
1722 ) -> GpuMesh {
1723 use bytemuck::cast_slice;
1724 use wgpu;
1725
1726 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1727 label: Some("vertex_buf"),
1728 size: (std::mem::size_of::<Vertex>() * vertices.len()) as u64,
1729 usage: wgpu::BufferUsages::VERTEX
1730 | wgpu::BufferUsages::COPY_DST
1731 | wgpu::BufferUsages::STORAGE,
1732 mapped_at_creation: true,
1733 });
1734 vertex_buffer
1735 .slice(..)
1736 .get_mapped_range_mut()
1737 .copy_from_slice(cast_slice(vertices));
1738 vertex_buffer.unmap();
1739
1740 let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1741 label: Some("index_buf"),
1742 size: (std::mem::size_of::<u32>() * indices.len()) as u64,
1743 usage: wgpu::BufferUsages::INDEX
1744 | wgpu::BufferUsages::COPY_DST
1745 | wgpu::BufferUsages::STORAGE,
1746 mapped_at_creation: true,
1747 });
1748 index_buffer
1749 .slice(..)
1750 .get_mapped_range_mut()
1751 .copy_from_slice(cast_slice(indices));
1752 index_buffer.unmap();
1753
1754 let edge_indices = generate_edge_indices(indices);
1755 let edge_buf_size = (std::mem::size_of::<u32>() * edge_indices.len().max(2)) as u64;
1756 let edge_index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1757 label: Some("edge_index_buf"),
1758 size: edge_buf_size,
1759 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
1760 mapped_at_creation: true,
1761 });
1762 {
1763 let mut mapped = edge_index_buffer.slice(..).get_mapped_range_mut();
1764 let edge_bytes = cast_slice::<u32, u8>(&edge_indices);
1765 mapped[..edge_bytes.len()].copy_from_slice(edge_bytes);
1766 }
1767 edge_index_buffer.unmap();
1768
1769 let identity = glam::Mat4::IDENTITY.to_cols_array_2d();
1770 let object_uniform = ObjectUniform {
1771 model: identity,
1772 colour: [1.0, 1.0, 1.0, 1.0],
1773 selected: 0,
1774 wireframe: 0,
1775 ambient: 0.15,
1776 diffuse: 0.75,
1777 specular: 0.4,
1778 shininess: 32.0,
1779 has_texture: 0,
1780 use_pbr: 0,
1781 metallic: 0.0,
1782 roughness: 0.5,
1783 has_normal_map: 0,
1784 has_ao_map: 0,
1785 has_attribute: 0,
1786 scalar_min: 0.0,
1787 scalar_max: 1.0,
1788 receive_shadows: 1,
1789 nan_colour: [0.0, 0.0, 0.0, 0.0],
1790 use_nan_colour: 0,
1791 use_matcap: 0,
1792 matcap_blendable: 0,
1793 unlit: 0,
1794 use_face_colour: 0,
1795 uv_vis_mode: 0,
1796 uv_vis_scale: 8.0,
1797 backface_policy: 0,
1798 backface_colour: [0.0; 4],
1799 has_warp: 0,
1800 warp_scale: 1.0,
1801 has_position_override: 0,
1802 has_normal_override: 0,
1803 emissive: [0.0; 3],
1804 use_flat: 0,
1805 alpha_mode: 0,
1806 alpha_cutoff: 0.5,
1807 has_metallic_roughness_tex: 0,
1808 has_emissive_tex: 0,
1809 uv_transform: [0.0, 0.0, 1.0, 1.0],
1810 deform_flags: 0,
1811 _pad_after_deform: 0,
1812 ao_range: [0.0, 1.0],
1813 metallic_range: [0.0, 1.0],
1814 roughness_range: [0.0, 1.0],
1815 };
1816 let object_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
1817 label: Some("object_uniform_buf"),
1818 size: std::mem::size_of::<ObjectUniform>() as u64,
1819 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1820 mapped_at_creation: true,
1821 });
1822 object_uniform_buf
1823 .slice(..)
1824 .get_mapped_range_mut()
1825 .copy_from_slice(cast_slice(&[object_uniform]));
1826 object_uniform_buf.unmap();
1827
1828 let object_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1829 label: Some("object_bind_group"),
1830 layout: object_bgl,
1831 entries: &[
1832 wgpu::BindGroupEntry {
1833 binding: 0,
1834 resource: object_uniform_buf.as_entire_binding(),
1835 },
1836 wgpu::BindGroupEntry {
1837 binding: 1,
1838 resource: wgpu::BindingResource::TextureView(fallback_albedo_view),
1839 },
1840 wgpu::BindGroupEntry {
1841 binding: 2,
1842 resource: wgpu::BindingResource::Sampler(fallback_sampler),
1843 },
1844 wgpu::BindGroupEntry {
1845 binding: 3,
1846 resource: wgpu::BindingResource::TextureView(fallback_normal_view),
1847 },
1848 wgpu::BindGroupEntry {
1849 binding: 4,
1850 resource: wgpu::BindingResource::TextureView(fallback_ao_view),
1851 },
1852 wgpu::BindGroupEntry {
1853 binding: 5,
1854 resource: wgpu::BindingResource::TextureView(fallback_lut_view),
1855 },
1856 wgpu::BindGroupEntry {
1857 binding: 6,
1858 resource: fallback_scalar_buf.as_entire_binding(),
1859 },
1860 wgpu::BindGroupEntry {
1861 binding: 7,
1862 resource: wgpu::BindingResource::TextureView(fallback_matcap_view),
1863 },
1864 wgpu::BindGroupEntry {
1865 binding: 8,
1866 resource: fallback_face_colour_buf.as_entire_binding(),
1867 },
1868 wgpu::BindGroupEntry {
1869 binding: 9,
1870 resource: fallback_warp_buf.as_entire_binding(),
1871 },
1872 wgpu::BindGroupEntry {
1873 binding: 10,
1874 resource: wgpu::BindingResource::Sampler(lut_sampler),
1875 },
1876 wgpu::BindGroupEntry {
1877 binding: 11,
1878 resource: wgpu::BindingResource::TextureView(fallback_metallic_roughness_view),
1879 },
1880 wgpu::BindGroupEntry {
1881 binding: 12,
1882 resource: wgpu::BindingResource::TextureView(fallback_emissive_view),
1883 },
1884 wgpu::BindGroupEntry {
1885 binding: 13,
1886 resource: fallback_position_override_buf.as_entire_binding(),
1887 },
1888 wgpu::BindGroupEntry {
1889 binding: 14,
1890 resource: fallback_normal_override_buf.as_entire_binding(),
1891 },
1892 ],
1893 });
1894
1895 let normal_override_uniform = ObjectUniform {
1896 model: identity,
1897 colour: [1.0, 1.0, 1.0, 1.0],
1898 selected: 0,
1899 wireframe: 0,
1900 ambient: 0.15,
1901 diffuse: 0.75,
1902 specular: 0.4,
1903 shininess: 32.0,
1904 has_texture: 0,
1905 use_pbr: 0,
1906 metallic: 0.0,
1907 roughness: 0.5,
1908 has_normal_map: 0,
1909 has_ao_map: 0,
1910 has_attribute: 0,
1911 scalar_min: 0.0,
1912 scalar_max: 1.0,
1913 receive_shadows: 1,
1914 nan_colour: [0.0, 0.0, 0.0, 0.0],
1915 use_nan_colour: 0,
1916 use_matcap: 0,
1917 matcap_blendable: 0,
1918 unlit: 0,
1919 use_face_colour: 0,
1920 uv_vis_mode: 0,
1921 uv_vis_scale: 8.0,
1922 backface_policy: 0,
1923 backface_colour: [0.0; 4],
1924 has_warp: 0,
1925 warp_scale: 1.0,
1926 has_position_override: 0,
1927 has_normal_override: 0,
1928 emissive: [0.0; 3],
1929 use_flat: 0,
1930 alpha_mode: 0,
1931 alpha_cutoff: 0.5,
1932 has_metallic_roughness_tex: 0,
1933 has_emissive_tex: 0,
1934 uv_transform: [0.0, 0.0, 1.0, 1.0],
1935 deform_flags: 0,
1936 _pad_after_deform: 0,
1937 ao_range: [0.0, 1.0],
1938 metallic_range: [0.0, 1.0],
1939 roughness_range: [0.0, 1.0],
1940 };
1941 let normal_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
1942 label: Some("normal_uniform_buf"),
1943 size: std::mem::size_of::<ObjectUniform>() as u64,
1944 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1945 mapped_at_creation: true,
1946 });
1947 normal_uniform_buf
1948 .slice(..)
1949 .get_mapped_range_mut()
1950 .copy_from_slice(cast_slice(&[normal_override_uniform]));
1951 normal_uniform_buf.unmap();
1952
1953 let normal_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1954 label: Some("normal_bind_group"),
1955 layout: object_bgl,
1956 entries: &[
1957 wgpu::BindGroupEntry {
1958 binding: 0,
1959 resource: normal_uniform_buf.as_entire_binding(),
1960 },
1961 wgpu::BindGroupEntry {
1962 binding: 1,
1963 resource: wgpu::BindingResource::TextureView(fallback_albedo_view),
1964 },
1965 wgpu::BindGroupEntry {
1966 binding: 2,
1967 resource: wgpu::BindingResource::Sampler(fallback_sampler),
1968 },
1969 wgpu::BindGroupEntry {
1970 binding: 3,
1971 resource: wgpu::BindingResource::TextureView(fallback_normal_view),
1972 },
1973 wgpu::BindGroupEntry {
1974 binding: 4,
1975 resource: wgpu::BindingResource::TextureView(fallback_ao_view),
1976 },
1977 wgpu::BindGroupEntry {
1978 binding: 5,
1979 resource: wgpu::BindingResource::TextureView(fallback_lut_view),
1980 },
1981 wgpu::BindGroupEntry {
1982 binding: 6,
1983 resource: fallback_scalar_buf.as_entire_binding(),
1984 },
1985 wgpu::BindGroupEntry {
1986 binding: 7,
1987 resource: wgpu::BindingResource::TextureView(fallback_matcap_view),
1988 },
1989 wgpu::BindGroupEntry {
1990 binding: 8,
1991 resource: fallback_face_colour_buf.as_entire_binding(),
1992 },
1993 wgpu::BindGroupEntry {
1994 binding: 9,
1995 resource: fallback_warp_buf.as_entire_binding(),
1996 },
1997 wgpu::BindGroupEntry {
1998 binding: 10,
1999 resource: wgpu::BindingResource::Sampler(lut_sampler),
2000 },
2001 wgpu::BindGroupEntry {
2002 binding: 11,
2003 resource: wgpu::BindingResource::TextureView(fallback_metallic_roughness_view),
2004 },
2005 wgpu::BindGroupEntry {
2006 binding: 12,
2007 resource: wgpu::BindingResource::TextureView(fallback_emissive_view),
2008 },
2009 wgpu::BindGroupEntry {
2010 binding: 13,
2011 resource: fallback_position_override_buf.as_entire_binding(),
2012 },
2013 wgpu::BindGroupEntry {
2014 binding: 14,
2015 resource: fallback_normal_override_buf.as_entire_binding(),
2016 },
2017 ],
2018 });
2019
2020 let (normal_line_buffer, normal_line_count) = if let Some(nl_verts) = normal_line_verts {
2021 if nl_verts.is_empty() {
2022 (None, 0)
2023 } else {
2024 let buf = device.create_buffer(&wgpu::BufferDescriptor {
2025 label: Some("normal_line_buf"),
2026 size: (std::mem::size_of::<Vertex>() * nl_verts.len()) as u64,
2027 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2028 mapped_at_creation: true,
2029 });
2030 buf.slice(..)
2031 .get_mapped_range_mut()
2032 .copy_from_slice(cast_slice(nl_verts));
2033 buf.unmap();
2034 let count = nl_verts.len() as u32;
2035 (Some(buf), count)
2036 }
2037 } else {
2038 (None, 0)
2039 };
2040
2041 let aabb = crate::scene::aabb::Aabb::from_positions(
2042 &vertices.iter().map(|v| v.position).collect::<Vec<_>>(),
2043 );
2044
2045 GpuMesh {
2046 vertex_buffer,
2047 index_buffer,
2048 index_count: indices.len() as u32,
2049 edge_index_buffer,
2050 edge_index_count: edge_indices.len() as u32,
2051 normal_line_buffer,
2052 normal_line_count,
2053 object_uniform_buf,
2054 object_bind_group,
2055 last_tex_key: (
2056 u64::MAX,
2057 u64::MAX,
2058 u64::MAX,
2059 u64::MAX,
2060 u64::MAX,
2061 u64::MAX,
2062 u64::MAX,
2063 u64::MAX,
2064 u64::MAX,
2065 0,
2066 0,
2067 ),
2068 normal_uniform_buf,
2069 normal_bind_group,
2070 aabb,
2071 cpu_positions: None,
2072 cpu_indices: None,
2073 attribute_buffers: std::collections::HashMap::new(),
2074 attribute_ranges: std::collections::HashMap::new(),
2075 face_vertex_buffer: None,
2076 face_attribute_buffers: std::collections::HashMap::new(),
2077 face_colour_buffers: std::collections::HashMap::new(),
2078 vector_attribute_buffers: std::collections::HashMap::new(),
2079 position_override_buffer: None,
2080 normal_override_buffer: None,
2081 position_override_gen: 0,
2082 normal_override_gen: 0,
2083 }
2084 }
2085
2086 pub(crate) fn ensure_pt_bind_group_layout(&mut self, device: &wgpu::Device) {
2100 if self.pt.bind_group_layout.is_none() {
2101 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2102 label: Some("pt_bgl"),
2103 entries: &[
2104 wgpu::BindGroupLayoutEntry {
2106 binding: 0,
2107 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
2108 ty: wgpu::BindingType::Buffer {
2109 ty: wgpu::BufferBindingType::Uniform,
2110 has_dynamic_offset: false,
2111 min_binding_size: None,
2112 },
2113 count: None,
2114 },
2115 wgpu::BindGroupLayoutEntry {
2117 binding: 1,
2118 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
2119 ty: wgpu::BindingType::Buffer {
2120 ty: wgpu::BufferBindingType::Storage { read_only: true },
2121 has_dynamic_offset: false,
2122 min_binding_size: None,
2123 },
2124 count: None,
2125 },
2126 ],
2127 });
2128 self.pt.bind_group_layout = Some(bgl);
2129 }
2130 if self.pt.lut_bind_group_layout.is_none() {
2131 let bgl = crate::resources::builders::texture_sampler_bgl(
2133 device,
2134 "pt_lut_bgl",
2135 wgpu::ShaderStages::FRAGMENT,
2136 );
2137 self.pt.lut_bind_group_layout = Some(bgl);
2138 }
2139 }
2140
2141 pub(crate) fn ensure_pt_lut_bind_group(
2148 &mut self,
2149 device: &wgpu::Device,
2150 colourmap_id: Option<crate::resources::ColourmapId>,
2151 ) -> &wgpu::BindGroup {
2152 let bgl = self
2153 .pt
2154 .lut_bind_group_layout
2155 .as_ref()
2156 .expect("pt_lut_bind_group_layout must exist");
2157 let sampler = &self.material_sampler;
2158
2159 match colourmap_id.and_then(|id| self.content.colourmap_views.get(id.0).map(|_| id.0)) {
2160 Some(slot) => {
2161 if !self.pt.lut_bind_groups.contains_key(&slot) {
2162 let lut_view = &self.content.colourmap_views[slot];
2163 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
2164 label: Some("pt_lut_bind_group"),
2165 layout: bgl,
2166 entries: &[
2167 wgpu::BindGroupEntry {
2168 binding: 0,
2169 resource: wgpu::BindingResource::TextureView(lut_view),
2170 },
2171 wgpu::BindGroupEntry {
2172 binding: 1,
2173 resource: wgpu::BindingResource::Sampler(sampler),
2174 },
2175 ],
2176 });
2177 self.pt.lut_bind_groups.insert(slot, bg);
2178 }
2179 self.pt.lut_bind_groups.get(&slot).unwrap()
2180 }
2181 None => {
2182 if self.pt.fallback_lut_bind_group.is_none() {
2183 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
2184 label: Some("pt_fallback_lut_bind_group"),
2185 layout: bgl,
2186 entries: &[
2187 wgpu::BindGroupEntry {
2188 binding: 0,
2189 resource: wgpu::BindingResource::TextureView(
2190 &self.content.fallback_lut_view,
2191 ),
2192 },
2193 wgpu::BindGroupEntry {
2194 binding: 1,
2195 resource: wgpu::BindingResource::Sampler(sampler),
2196 },
2197 ],
2198 });
2199 self.pt.fallback_lut_bind_group = Some(bg);
2200 }
2201 self.pt.fallback_lut_bind_group.as_ref().unwrap()
2202 }
2203 }
2204 }
2205
2206 pub(crate) fn upload_projected_tet(
2219 &mut self,
2220 device: &wgpu::Device,
2221 data: &crate::resources::volume::volume_mesh::VolumeMeshData,
2222 scalar_attribute: &str,
2223 ) -> crate::error::ViewportResult<(ProjectedTetId, f32, f32)> {
2224 self.ensure_pt_bind_group_layout(device);
2225
2226 let (pending, scalar_range, uniform_buffer) =
2227 Self::decompose_into_chunks(device, data, scalar_attribute);
2228
2229 let chunks = {
2231 let bgl = self
2232 .pt
2233 .bind_group_layout
2234 .as_ref()
2235 .expect("pt_bind_group_layout must exist after ensure_pt_bind_group_layout");
2236 pending
2237 .into_iter()
2238 .map(|(tet_buffer, tet_count)| {
2239 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
2240 label: Some("pt_bind_group"),
2241 layout: bgl,
2242 entries: &[
2243 wgpu::BindGroupEntry {
2244 binding: 0,
2245 resource: uniform_buffer.as_entire_binding(),
2246 },
2247 wgpu::BindGroupEntry {
2248 binding: 1,
2249 resource: tet_buffer.as_entire_binding(),
2250 },
2251 ],
2252 });
2253 crate::resources::types::ProjectedTetChunk {
2254 tet_buffer,
2255 tet_count,
2256 bind_group,
2257 }
2258 })
2259 .collect::<Vec<_>>()
2260 };
2261
2262 let id = ProjectedTetId(self.content.projected_tet_store.push(GpuProjectedTetMesh {
2263 chunks,
2264 uniform_buffer,
2265 scalar_range,
2266 }));
2267 Ok((id, scalar_range.0, scalar_range.1))
2268 }
2269
2270 #[allow(dead_code)]
2280 pub(crate) fn begin_upload_projected_tet(
2281 &mut self,
2282 device: &wgpu::Device,
2283 data: crate::resources::volume::volume_mesh::VolumeMeshData,
2284 scalar_attribute: String,
2285 ) -> crate::resources::JobId {
2286 self.ensure_pt_bind_group_layout(device);
2288
2289 let slot = crate::resources::ResultSlot::<(ProjectedTetId, f32, f32)>::new();
2290 let slot_for_apply = slot.clone();
2291 let device_for_worker = device.clone();
2292 let device_for_apply = device.clone();
2293
2294 let id = {
2295 let mut runner = self.jobs.lock().expect("upload job runner poisoned");
2296 runner.submit_cpu(move |progress| {
2297 progress.set(0.1);
2298 let (pending, scalar_range, uniform_buffer) =
2299 DeviceResources::decompose_into_chunks(
2300 &device_for_worker,
2301 &data,
2302 &scalar_attribute,
2303 );
2304 progress.set(0.95);
2305 Ok(crate::resources::upload_jobs::JobProduct::with_apply(
2306 Box::new(move |resources: &mut DeviceResources| {
2307 let chunks = {
2308 let bgl = resources
2309 .pt
2310 .bind_group_layout
2311 .as_ref()
2312 .expect("pt_bind_group_layout must exist");
2313 pending
2314 .into_iter()
2315 .map(|(tet_buffer, tet_count)| {
2316 let bind_group = device_for_apply.create_bind_group(
2317 &wgpu::BindGroupDescriptor {
2318 label: Some("pt_bind_group"),
2319 layout: bgl,
2320 entries: &[
2321 wgpu::BindGroupEntry {
2322 binding: 0,
2323 resource: uniform_buffer.as_entire_binding(),
2324 },
2325 wgpu::BindGroupEntry {
2326 binding: 1,
2327 resource: tet_buffer.as_entire_binding(),
2328 },
2329 ],
2330 },
2331 );
2332 crate::resources::types::ProjectedTetChunk {
2333 tet_buffer,
2334 tet_count,
2335 bind_group,
2336 }
2337 })
2338 .collect::<Vec<_>>()
2339 };
2340 let pid = ProjectedTetId(resources.content.projected_tet_store.push(
2341 GpuProjectedTetMesh {
2342 chunks,
2343 uniform_buffer,
2344 scalar_range,
2345 },
2346 ));
2347 slot_for_apply.set((pid, scalar_range.0, scalar_range.1));
2348 }),
2349 ))
2350 })
2351 };
2352
2353 self.job_results
2354 .projected_tet
2355 .lock()
2356 .expect("projected tet result map poisoned")
2357 .insert(id, slot);
2358 id
2359 }
2360
2361 #[allow(dead_code)]
2364 pub(crate) fn upload_result_projected_tet(
2365 &mut self,
2366 id: crate::resources::JobId,
2367 ) -> crate::error::ViewportResult<(ProjectedTetId, f32, f32)> {
2368 let mut map = self
2369 .job_results
2370 .projected_tet
2371 .lock()
2372 .expect("projected tet result map poisoned");
2373 let slot = match map.get(&id) {
2374 Some(s) => s.clone(),
2375 None => {
2376 return Err(crate::error::ViewportError::JobResultMissing {
2377 reason: "unknown id or wrong upload type",
2378 });
2379 }
2380 };
2381 match slot.take() {
2382 Some(triple) => {
2383 map.remove(&id);
2384 Ok(triple)
2385 }
2386 None => Err(crate::error::ViewportError::JobNotReady),
2387 }
2388 }
2389
2390 pub fn replace_projected_tet(
2398 &mut self,
2399 device: &wgpu::Device,
2400 id: crate::resources::ProjectedTetId,
2401 data: &crate::resources::volume::volume_mesh::VolumeMeshData,
2402 scalar_attribute: &str,
2403 ) -> crate::error::ViewportResult<()> {
2404 self.ensure_pt_bind_group_layout(device);
2405
2406 let (pending, scalar_range, _new_uniform) =
2407 Self::decompose_into_chunks(device, data, scalar_attribute);
2408
2409 let chunks = {
2410 let bgl = self
2411 .pt
2412 .bind_group_layout
2413 .as_ref()
2414 .expect("pt_bind_group_layout must exist after ensure_pt_bind_group_layout");
2415 let uniform_buf = &self
2416 .content
2417 .projected_tet_store
2418 .get(id.0)
2419 .expect("ProjectedTetId must reference an uploaded mesh")
2420 .uniform_buffer;
2421 pending
2422 .into_iter()
2423 .map(|(tet_buffer, tet_count)| {
2424 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
2425 label: Some("pt_bind_group"),
2426 layout: bgl,
2427 entries: &[
2428 wgpu::BindGroupEntry {
2429 binding: 0,
2430 resource: uniform_buf.as_entire_binding(),
2431 },
2432 wgpu::BindGroupEntry {
2433 binding: 1,
2434 resource: tet_buffer.as_entire_binding(),
2435 },
2436 ],
2437 });
2438 crate::resources::types::ProjectedTetChunk {
2439 tet_buffer,
2440 tet_count,
2441 bind_group,
2442 }
2443 })
2444 .collect::<Vec<_>>()
2445 };
2446
2447 let slot = self
2448 .content
2449 .projected_tet_store
2450 .get_mut(id.0)
2451 .expect("ProjectedTetId must reference an uploaded mesh");
2452 slot.chunks = chunks;
2453 slot.scalar_range = scalar_range;
2454
2455 Ok(())
2456 }
2457
2458 fn decompose_into_chunks(
2465 device: &wgpu::Device,
2466 data: &crate::resources::volume::volume_mesh::VolumeMeshData,
2467 scalar_attribute: &str,
2468 ) -> (Vec<(wgpu::Buffer, u32)>, (f32, f32), wgpu::Buffer) {
2469 let max_binding = device.limits().max_storage_buffer_binding_size as u64;
2472 let max_buf = device.limits().max_buffer_size;
2473 let chunk_size_tets = ((max_binding.min(max_buf)) / 64).max(1) as usize;
2474
2475 let mut pending: Vec<(wgpu::Buffer, u32)> = Vec::new();
2476 let mut current_raw: Vec<f32> = Vec::with_capacity(chunk_size_tets * 16);
2477 let mut scalar_min = f32::INFINITY;
2478 let mut scalar_max = f32::NEG_INFINITY;
2479
2480 let flush = |raw: &mut Vec<f32>, pending: &mut Vec<(wgpu::Buffer, u32)>| {
2481 let tet_count = (raw.len() / 16) as u32;
2482 let buf = device.create_buffer(&wgpu::BufferDescriptor {
2483 label: Some("pt_tet_buffer"),
2484 size: (raw.len() * std::mem::size_of::<f32>()) as u64,
2485 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
2486 mapped_at_creation: true,
2487 });
2488 buf.slice(..)
2489 .get_mapped_range_mut()
2490 .copy_from_slice(bytemuck::cast_slice(raw));
2491 buf.unmap();
2492 pending.push((buf, tet_count));
2493 raw.clear();
2494 };
2495
2496 crate::resources::volume::volume_mesh::for_each_tet(
2497 data,
2498 scalar_attribute,
2499 |verts, scalar| {
2500 scalar_min = scalar_min.min(scalar);
2501 scalar_max = scalar_max.max(scalar);
2502 current_raw.extend_from_slice(&[verts[0][0], verts[0][1], verts[0][2], scalar]);
2503 current_raw.extend_from_slice(&[verts[1][0], verts[1][1], verts[1][2], 0.0]);
2504 current_raw.extend_from_slice(&[verts[2][0], verts[2][1], verts[2][2], 0.0]);
2505 current_raw.extend_from_slice(&[verts[3][0], verts[3][1], verts[3][2], 0.0]);
2506 if current_raw.len() == chunk_size_tets * 16 {
2507 flush(&mut current_raw, &mut pending);
2508 }
2509 },
2510 );
2511
2512 if !current_raw.is_empty() {
2513 flush(&mut current_raw, &mut pending);
2514 }
2515
2516 let scalar_range = if scalar_min.is_infinite() {
2517 (0.0f32, 1.0f32)
2518 } else {
2519 let max_s = if (scalar_max - scalar_min).abs() < 1e-12 {
2520 scalar_min + 1.0
2521 } else {
2522 scalar_max
2523 };
2524 (scalar_min, max_s)
2525 };
2526
2527 let initial_uniform = crate::resources::types::ProjectedTetUniform {
2528 density: 1.0,
2529 scalar_min: scalar_range.0,
2530 scalar_max: scalar_range.1,
2531 threshold_min: f32::NEG_INFINITY,
2532 threshold_max: f32::INFINITY,
2533 unlit: 0,
2534 opacity: 1.0,
2535 _pad: 0.0,
2536 };
2537 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
2538 label: Some("pt_uniform_buf"),
2539 size: std::mem::size_of::<crate::resources::types::ProjectedTetUniform>() as u64,
2540 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2541 mapped_at_creation: true,
2542 });
2543 uniform_buffer
2544 .slice(..)
2545 .get_mapped_range_mut()
2546 .copy_from_slice(bytemuck::bytes_of(&initial_uniform));
2547 uniform_buffer.unmap();
2548
2549 (pending, scalar_range, uniform_buffer)
2550 }
2551}
2552
2553#[cfg(test)]
2554mod override_tests {
2555 use crate::DeviceResources;
2556 use crate::geometry::primitives;
2557
2558 fn try_make_device() -> Option<(wgpu::Device, wgpu::Queue)> {
2559 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
2560 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
2561 power_preference: wgpu::PowerPreference::LowPower,
2562 compatible_surface: None,
2563 force_fallback_adapter: false,
2564 }))
2565 .ok()?;
2566 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()
2567 }
2568
2569 fn dummy_override_buffer(device: &wgpu::Device, vertex_count: usize) -> wgpu::Buffer {
2570 device.create_buffer(&wgpu::BufferDescriptor {
2571 label: Some("test_override_buf"),
2572 size: (vertex_count * 12) as u64,
2573 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
2574 mapped_at_creation: false,
2575 })
2576 }
2577
2578 #[test]
2579 fn set_position_override_roundtrip() {
2580 let Some((device, _queue)) = try_make_device() else {
2581 eprintln!("skipping: no wgpu adapter available");
2582 return;
2583 };
2584 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2585 let plane = primitives::grid_plane(1.0, 1.0, 2, 2);
2586 let mesh_id = resources.upload_mesh_data(&device, &plane).unwrap();
2587 let vertex_count = plane.positions.len();
2588
2589 {
2591 let mesh = resources.mesh_store.get(mesh_id).unwrap();
2592 assert!(mesh.position_override_buffer.is_none());
2593 let gen0 = mesh.position_override_gen;
2594 assert_eq!(gen0, 0);
2595 }
2596
2597 let buf = dummy_override_buffer(&device, vertex_count);
2598 resources
2599 .set_position_override_buffer(mesh_id, buf)
2600 .unwrap();
2601
2602 {
2603 let mesh = resources.mesh_store.get(mesh_id).unwrap();
2604 assert!(mesh.position_override_buffer.is_some());
2605 assert_eq!(mesh.position_override_gen, 1);
2606 }
2607
2608 resources.clear_position_override(mesh_id).unwrap();
2609 {
2610 let mesh = resources.mesh_store.get(mesh_id).unwrap();
2611 assert!(mesh.position_override_buffer.is_none());
2612 assert_eq!(mesh.position_override_gen, 2);
2613 }
2614 }
2615
2616 #[test]
2617 fn set_normal_override_roundtrip() {
2618 let Some((device, _queue)) = try_make_device() else {
2619 eprintln!("skipping: no wgpu adapter available");
2620 return;
2621 };
2622 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2623 let plane = primitives::grid_plane(1.0, 1.0, 2, 2);
2624 let mesh_id = resources.upload_mesh_data(&device, &plane).unwrap();
2625 let vertex_count = plane.positions.len();
2626
2627 let buf = dummy_override_buffer(&device, vertex_count);
2628 resources.set_normal_override_buffer(mesh_id, buf).unwrap();
2629 {
2630 let mesh = resources.mesh_store.get(mesh_id).unwrap();
2631 assert!(mesh.normal_override_buffer.is_some());
2632 assert_eq!(mesh.normal_override_gen, 1);
2633 }
2634
2635 resources.clear_normal_override(mesh_id).unwrap();
2636 {
2637 let mesh = resources.mesh_store.get(mesh_id).unwrap();
2638 assert!(mesh.normal_override_buffer.is_none());
2639 assert_eq!(mesh.normal_override_gen, 2);
2640 }
2641 }
2642
2643 #[test]
2644 fn override_on_unknown_mesh_id_errors() {
2645 let Some((device, _queue)) = try_make_device() else {
2646 eprintln!("skipping: no wgpu adapter available");
2647 return;
2648 };
2649 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2650 let bogus = crate::resources::mesh::mesh_store::MeshId::new(9999, 0);
2652 let buf = dummy_override_buffer(&device, 4);
2653 let err = resources.set_position_override_buffer(bogus, buf);
2654 assert!(matches!(
2655 err,
2656 Err(crate::error::ViewportError::StaleHandle { .. })
2657 ));
2658 }
2659
2660 #[test]
2661 fn stale_mesh_handle_does_not_alias_after_slot_reuse() {
2662 let Some((device, _queue)) = try_make_device() else {
2663 eprintln!("skipping: no wgpu adapter available");
2664 return;
2665 };
2666 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2667
2668 let id1 = resources
2670 .upload_mesh_data(&device, &primitives::cube(1.0))
2671 .unwrap();
2672 assert!(resources.mesh_store.get(id1).is_some());
2673 assert!(resources.free_mesh(id1));
2674 assert!(
2675 resources.mesh_store.get(id1).is_none(),
2676 "a removed handle must not resolve"
2677 );
2678
2679 let id2 = resources
2681 .upload_mesh_data(&device, &primitives::cube(2.0))
2682 .unwrap();
2683 assert_eq!(id1.index(), id2.index(), "the freed slot should be reused");
2684 assert_ne!(
2685 id1, id2,
2686 "the reused slot must carry a new generation so the old handle differs"
2687 );
2688 assert!(resources.mesh_store.get(id2).is_some());
2689 assert!(
2690 resources.mesh_store.get(id1).is_none(),
2691 "the stale handle must not alias the mesh now occupying its slot"
2692 );
2693 }
2694
2695 #[test]
2696 fn stale_handle_replace_is_rejected_after_free_and_reuse() {
2697 let Some((device, queue)) = try_make_device() else {
2702 eprintln!("skipping: no wgpu adapter available");
2703 return;
2704 };
2705 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2706
2707 let stale = resources
2708 .upload_mesh_data(&device, &primitives::cube(1.0))
2709 .unwrap();
2710 assert!(resources.free_mesh(stale));
2711
2712 let live = resources
2714 .upload_mesh_data(&device, &primitives::cube(2.0))
2715 .unwrap();
2716 assert_eq!(stale.index(), live.index());
2717
2718 let err = resources.replace_mesh_data(&device, &queue, stale, &primitives::cube(3.0));
2721 assert!(
2722 matches!(err, Err(crate::error::ViewportError::StaleHandle { .. })),
2723 "replace through a stale handle must fail rather than alias the reused slot"
2724 );
2725 assert!(
2726 resources.mesh_store.get(live).is_some(),
2727 "the live mesh must be untouched by the rejected replace"
2728 );
2729 }
2730
2731 #[test]
2732 fn resident_bytes_track_mesh_upload_and_free() {
2733 let Some((device, _queue)) = try_make_device() else {
2734 eprintln!("skipping: no wgpu adapter available");
2735 return;
2736 };
2737 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2738
2739 let start = resources.resident_bytes().mesh_bytes;
2740 let id = resources
2741 .upload_mesh_data(&device, &primitives::cube(1.0))
2742 .unwrap();
2743 let after_upload = resources.resident_bytes().mesh_bytes;
2744 assert!(
2745 after_upload > start,
2746 "uploading a mesh must increase resident mesh bytes"
2747 );
2748
2749 assert!(resources.free_mesh(id));
2750 let after_free = resources.resident_bytes().mesh_bytes;
2751 assert_eq!(
2752 after_free, start,
2753 "freeing the mesh must return resident bytes to the starting total"
2754 );
2755 }
2756}
2757
2758#[cfg(test)]
2759mod async_upload_tests {
2760 use crate::DeviceResources;
2761 use crate::geometry::primitives;
2762 use crate::resources::UploadStatus;
2763
2764 fn try_make_device() -> Option<(wgpu::Device, wgpu::Queue)> {
2765 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
2766 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
2767 power_preference: wgpu::PowerPreference::LowPower,
2768 compatible_surface: None,
2769 force_fallback_adapter: false,
2770 }))
2771 .ok()?;
2772 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()
2773 }
2774
2775 fn drive_until_ready(
2776 resources: &mut DeviceResources,
2777 device: &wgpu::Device,
2778 queue: &wgpu::Queue,
2779 id: crate::resources::JobId,
2780 ) {
2781 for _ in 0..200 {
2782 resources.process_uploads(device, queue);
2783 match resources.upload_status(id) {
2784 UploadStatus::Ready => return,
2785 UploadStatus::Failed(e) => panic!("upload failed: {e:?}"),
2786 UploadStatus::Pending { .. } => {
2787 std::thread::sleep(std::time::Duration::from_millis(5));
2788 }
2789 UploadStatus::Unknown => panic!("job id disappeared"),
2790 }
2791 }
2792 panic!("mesh upload did not complete in time");
2793 }
2794
2795 #[test]
2796 fn invalid_mesh_data_errors_synchronously() {
2797 let Some((device, _queue)) = try_make_device() else {
2798 eprintln!("skipping: no wgpu adapter available");
2799 return;
2800 };
2801 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2802
2803 let empty = crate::resources::MeshData::default();
2804 let err = resources
2805 .begin_upload_mesh_data(&device, empty)
2806 .expect_err("empty mesh should be rejected");
2807 assert!(matches!(err, crate::error::ViewportError::EmptyMesh { .. }));
2808 assert_eq!(resources.uploads_pending(), 0);
2809 }
2810
2811 #[test]
2812 fn begin_upload_completes_and_yields_mesh_id() {
2813 let Some((device, queue)) = try_make_device() else {
2814 eprintln!("skipping: no wgpu adapter available");
2815 return;
2816 };
2817 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2818
2819 let plane = primitives::grid_plane(1.0, 1.0, 8, 8);
2820 let id = resources
2821 .begin_upload_mesh_data(&device, plane.clone())
2822 .unwrap();
2823 assert_eq!(resources.uploads_pending(), 1);
2824
2825 let err = resources.upload_result_mesh(id).unwrap_err();
2827 assert!(matches!(err, crate::error::ViewportError::JobNotReady));
2828
2829 drive_until_ready(&mut resources, &device, &queue, id);
2830
2831 let mesh_id = resources.upload_result_mesh(id).expect("ready result");
2832 assert!(resources.mesh_store.get(mesh_id).is_some());
2833
2834 let err = resources.upload_result_mesh(id).unwrap_err();
2836 assert!(matches!(
2837 err,
2838 crate::error::ViewportError::JobResultMissing { .. }
2839 ));
2840 }
2841
2842 #[test]
2843 fn sync_upload_still_works_alongside_async() {
2844 let Some((device, _queue)) = try_make_device() else {
2845 eprintln!("skipping: no wgpu adapter available");
2846 return;
2847 };
2848 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2849
2850 let plane = primitives::grid_plane(1.0, 1.0, 4, 4);
2851 let mesh_id = resources.upload_mesh_data(&device, &plane).unwrap();
2852 assert!(resources.mesh_store.get(mesh_id).is_some());
2853 }
2854
2855 #[test]
2856 fn unknown_job_id_returns_missing() {
2857 let Some((device, _queue)) = try_make_device() else {
2858 eprintln!("skipping: no wgpu adapter available");
2859 return;
2860 };
2861 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2862
2863 let pixels = vec![0.5f32; 8 * 4 * 4];
2865 let other_id = crate::resources::material::environment::begin_upload_environment_map(
2866 &mut resources,
2867 &device,
2868 &try_make_device().unwrap().1,
2869 pixels,
2870 8,
2871 4,
2872 )
2873 .unwrap();
2874
2875 let err = resources.upload_result_mesh(other_id).unwrap_err();
2876 assert!(matches!(
2877 err,
2878 crate::error::ViewportError::JobResultMissing { .. }
2879 ));
2880 }
2881}
2882
2883#[cfg(test)]
2884mod c4_volume_mesh_tests {
2885 use crate::DeviceResources;
2886 use crate::resources::volume::volume_mesh::VolumeMeshData;
2887 use crate::resources::{CELL_SENTINEL, SparseVolumeGridData, UploadStatus};
2888
2889 fn try_make_device() -> Option<(wgpu::Device, wgpu::Queue)> {
2890 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
2891 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
2892 power_preference: wgpu::PowerPreference::LowPower,
2893 compatible_surface: None,
2894 force_fallback_adapter: false,
2895 }))
2896 .ok()?;
2897 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()
2898 }
2899
2900 fn drive_until_ready(
2901 resources: &mut DeviceResources,
2902 device: &wgpu::Device,
2903 queue: &wgpu::Queue,
2904 id: crate::resources::JobId,
2905 label: &str,
2906 ) {
2907 for _ in 0..200 {
2908 resources.process_uploads(device, queue);
2909 match resources.upload_status(id) {
2910 UploadStatus::Ready => return,
2911 UploadStatus::Failed(e) => panic!("{label} upload failed: {e:?}"),
2912 UploadStatus::Pending { .. } => {
2913 std::thread::sleep(std::time::Duration::from_millis(5));
2914 }
2915 UploadStatus::Unknown => panic!("{label} job id disappeared"),
2916 }
2917 }
2918 panic!("{label} upload did not complete in time");
2919 }
2920
2921 fn single_tet_volume() -> VolumeMeshData {
2922 let mut v = VolumeMeshData::default();
2923 v.positions = vec![
2924 [0.0, 0.0, 0.0],
2925 [1.0, 0.0, 0.0],
2926 [0.5, 1.0, 0.0],
2927 [0.5, 0.5, 1.0],
2928 ];
2929 v.cells = vec![[
2930 0,
2931 1,
2932 2,
2933 3,
2934 CELL_SENTINEL,
2935 CELL_SENTINEL,
2936 CELL_SENTINEL,
2937 CELL_SENTINEL,
2938 ]];
2939 v.cell_scalars.insert("density".into(), vec![0.5]);
2940 v
2941 }
2942
2943 fn single_cell_sparse() -> SparseVolumeGridData {
2944 let mut g = SparseVolumeGridData::default();
2945 g.active_cells = vec![[0, 0, 0]];
2946 g.cell_size = 1.0;
2947 g.origin = [0.0, 0.0, 0.0];
2948 g
2949 }
2950
2951 #[test]
2952 fn begin_upload_volume_mesh_drains_to_pair() {
2953 let Some((device, queue)) = try_make_device() else {
2954 eprintln!("skipping: no wgpu adapter available");
2955 return;
2956 };
2957 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2958 let job = resources.begin_upload_volume_mesh(&device, single_tet_volume());
2959 drive_until_ready(&mut resources, &device, &queue, job, "volume_mesh");
2960 let item = resources.upload_result_volume_mesh(job).expect("ready");
2961 assert!(resources.mesh_store.get(item.boundary_mesh_id).is_some());
2962 assert!(!item.face_to_cell.is_empty());
2963 let res = resources.upload_result_volume_mesh(job);
2964 assert!(matches!(
2965 res,
2966 Err(crate::error::ViewportError::JobResultMissing { .. })
2967 ));
2968 }
2969
2970 #[test]
2971 fn begin_upload_clipped_volume_mesh_drains_to_pair() {
2972 let Some((device, queue)) = try_make_device() else {
2973 eprintln!("skipping: no wgpu adapter available");
2974 return;
2975 };
2976 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2977 let job =
2979 resources.begin_upload_clipped_volume_mesh(&device, single_tet_volume(), Vec::new());
2980 drive_until_ready(&mut resources, &device, &queue, job, "clipped_volume_mesh");
2981 let item = resources
2982 .upload_result_clipped_volume_mesh(job)
2983 .expect("ready");
2984 assert!(resources.mesh_store.get(item.boundary_mesh_id).is_some());
2985 assert!(!item.face_to_cell.is_empty());
2986 }
2987
2988 #[test]
2989 fn begin_upload_sparse_volume_grid_drains_to_handle() {
2990 let Some((device, queue)) = try_make_device() else {
2991 eprintln!("skipping: no wgpu adapter available");
2992 return;
2993 };
2994 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
2995 let job = resources.begin_upload_sparse_volume_grid_data(&device, single_cell_sparse());
2996 drive_until_ready(&mut resources, &device, &queue, job, "sparse_volume_grid");
2997 let mesh_id = resources
2998 .upload_result_sparse_volume_grid(job)
2999 .expect("ready");
3000 assert!(resources.mesh_store.get(mesh_id).is_some());
3001 }
3002
3003 #[test]
3004 fn begin_upload_projected_tet_drains_to_triple() {
3005 let Some((device, queue)) = try_make_device() else {
3006 eprintln!("skipping: no wgpu adapter available");
3007 return;
3008 };
3009 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
3010 let job =
3011 resources.begin_upload_projected_tet(&device, single_tet_volume(), "density".into());
3012 drive_until_ready(&mut resources, &device, &queue, job, "projected_tet");
3013 let (_id, smin, smax) = resources.upload_result_projected_tet(job).expect("ready");
3014 assert!(smin <= smax);
3015 }
3016
3017 #[test]
3018 fn sync_paths_still_work() {
3019 let Some((device, _queue)) = try_make_device() else {
3020 eprintln!("skipping: no wgpu adapter available");
3021 return;
3022 };
3023 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
3024 let vol = single_tet_volume();
3025 let _item = resources
3026 .upload_volume_mesh(&device, &vol)
3027 .expect("sync ok");
3028 let _item2 = resources
3029 .upload_clipped_volume_mesh(&device, &vol, &[])
3030 .expect("clipped sync ok");
3031 let _grid_id = resources
3032 .upload_sparse_volume_grid_data(&device, &single_cell_sparse())
3033 .expect("sparse sync ok");
3034 }
3035}
3036
3037#[derive(Clone)]
3039#[non_exhaustive]
3040pub struct MeshData {
3041 pub positions: Vec<[f32; 3]>,
3043 pub normals: Vec<[f32; 3]>,
3045 pub indices: Vec<u32>,
3047 pub uvs: Option<Vec<[f32; 2]>>,
3049 pub tangents: Option<Vec<[f32; 4]>>,
3054 pub attributes: std::collections::HashMap<String, AttributeData>,
3059}
3060
3061impl Default for MeshData {
3062 fn default() -> Self {
3063 Self {
3064 positions: Vec::new(),
3065 normals: Vec::new(),
3066 indices: Vec::new(),
3067 uvs: None,
3068 tangents: None,
3069 attributes: std::collections::HashMap::new(),
3070 }
3071 }
3072}
3073
3074impl MeshData {
3075 pub fn compute_aabb(&self) -> crate::scene::aabb::Aabb {
3077 crate::scene::aabb::Aabb::from_positions(&self.positions)
3078 }
3079}
3080
3081impl crate::resources::DeviceResources {
3082 pub fn replace_attribute(
3095 &mut self,
3096 queue: &wgpu::Queue,
3097 mesh_id: crate::resources::mesh::mesh_store::MeshId,
3098 name: &str,
3099 data: &[f32],
3100 ) -> crate::error::ViewportResult<()> {
3101 let gpu_mesh =
3103 self.mesh_store
3104 .get_mut(mesh_id)
3105 .ok_or(crate::error::ViewportError::SlotEmpty {
3106 index: mesh_id.index(),
3107 })?;
3108
3109 let buffer = gpu_mesh.attribute_buffers.get(name).ok_or_else(|| {
3111 crate::error::ViewportError::AttributeNotFound {
3112 mesh_id: mesh_id.index(),
3113 name: name.to_string(),
3114 }
3115 })?;
3116
3117 let expected_elems = (buffer.size() / 4) as usize;
3119 if data.len() != expected_elems {
3120 return Err(crate::error::ViewportError::AttributeLengthMismatch {
3121 expected: expected_elems,
3122 got: data.len(),
3123 });
3124 }
3125
3126 queue.write_buffer(buffer, 0, bytemuck::cast_slice(data));
3128
3129 let (min, max) = data
3131 .iter()
3132 .fold((f32::MAX, f32::MIN), |(mn, mx), &v| (mn.min(v), mx.max(v)));
3133 let range = if min > max { (0.0, 1.0) } else { (min, max) };
3134 gpu_mesh.attribute_ranges.insert(name.to_string(), range);
3135
3136 gpu_mesh.last_tex_key = (
3138 gpu_mesh.last_tex_key.0,
3139 gpu_mesh.last_tex_key.1,
3140 gpu_mesh.last_tex_key.2,
3141 gpu_mesh.last_tex_key.3,
3142 u64::MAX, gpu_mesh.last_tex_key.5,
3144 gpu_mesh.last_tex_key.6,
3145 gpu_mesh.last_tex_key.7,
3146 gpu_mesh.last_tex_key.8,
3147 gpu_mesh.last_tex_key.9,
3148 gpu_mesh.last_tex_key.10,
3149 );
3150
3151 Ok(())
3152 }
3153}
3154
3155pub fn lerp_attributes(a: &[f32], b: &[f32], t: f32) -> Vec<f32> {
3163 let t = t.clamp(0.0, 1.0);
3164 let one_minus_t = 1.0 - t;
3165 a.iter()
3166 .zip(b.iter())
3167 .map(|(&av, &bv)| av * one_minus_t + bv * t)
3168 .collect()
3169}