mod color_quantity;
mod scalar_quantity;
pub mod slice_geometry;
mod vector_quantity;
pub use color_quantity::*;
pub use scalar_quantity::*;
pub use slice_geometry::{CellSliceResult, slice_hex, slice_tet};
pub use vector_quantity::*;
use glam::{Mat4, Vec3, Vec4};
use polyscope_core::pick::PickResult;
use polyscope_core::quantity::Quantity;
use polyscope_core::structure::{HasQuantities, RenderContext, Structure};
use polyscope_render::{
MeshPickUniforms, MeshUniforms, SliceMeshRenderData, SurfaceMeshRenderData,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VolumeCellType {
Tet,
Hex,
}
pub struct VolumeMesh {
name: String,
vertices: Vec<Vec3>,
cells: Vec<[u32; 8]>,
enabled: bool,
transform: Mat4,
quantities: Vec<Box<dyn Quantity>>,
color: Vec4,
interior_color: Vec4,
edge_color: Vec4,
edge_width: f32,
render_data: Option<SurfaceMeshRenderData>,
pick_uniform_buffer: Option<wgpu::Buffer>,
pick_bind_group: Option<wgpu::BindGroup>,
pick_cell_index_buffer: Option<wgpu::Buffer>,
global_start: u32,
slice_render_data: Option<SliceMeshRenderData>,
slice_plane_cache: Option<(Vec3, Vec3)>,
culling_plane_cache: Option<Vec<(Vec3, Vec3)>>,
}
impl VolumeMesh {
pub fn new(name: impl Into<String>, vertices: Vec<Vec3>, cells: Vec<[u32; 8]>) -> Self {
let color = Vec4::new(0.25, 0.50, 0.75, 1.0);
let interior_color = Vec4::new(0.45, 0.50, 0.55, 1.0);
Self {
name: name.into(),
vertices,
cells,
enabled: true,
transform: Mat4::IDENTITY,
quantities: Vec::new(),
color,
interior_color,
edge_color: Vec4::new(0.0, 0.0, 0.0, 1.0),
edge_width: 0.0,
render_data: None,
pick_uniform_buffer: None,
pick_bind_group: None,
pick_cell_index_buffer: None,
global_start: 0,
slice_render_data: None,
slice_plane_cache: None,
culling_plane_cache: None,
}
}
pub fn new_tet_mesh(name: impl Into<String>, vertices: Vec<Vec3>, tets: Vec<[u32; 4]>) -> Self {
let cells: Vec<[u32; 8]> = tets
.into_iter()
.map(|t| {
[
t[0],
t[1],
t[2],
t[3],
u32::MAX,
u32::MAX,
u32::MAX,
u32::MAX,
]
})
.collect();
Self::new(name, vertices, cells)
}
pub fn new_hex_mesh(
name: impl Into<String>,
vertices: Vec<Vec3>,
hexes: Vec<[u32; 8]>,
) -> Self {
Self::new(name, vertices, hexes)
}
#[must_use]
pub fn num_vertices(&self) -> usize {
self.vertices.len()
}
#[must_use]
pub fn num_cells(&self) -> usize {
self.cells.len()
}
#[must_use]
pub fn cell_type(&self, cell_idx: usize) -> VolumeCellType {
if self.cells[cell_idx][4] == u32::MAX {
VolumeCellType::Tet
} else {
VolumeCellType::Hex
}
}
#[must_use]
pub fn vertices(&self) -> &[Vec3] {
&self.vertices
}
#[must_use]
pub fn cells(&self) -> &[[u32; 8]] {
&self.cells
}
#[must_use]
pub fn color(&self) -> Vec4 {
self.color
}
pub fn set_color(&mut self, color: Vec3) -> &mut Self {
self.color = color.extend(1.0);
self
}
#[must_use]
pub fn interior_color(&self) -> Vec4 {
self.interior_color
}
pub fn set_interior_color(&mut self, color: Vec3) -> &mut Self {
self.interior_color = color.extend(1.0);
self
}
#[must_use]
pub fn edge_color(&self) -> Vec4 {
self.edge_color
}
pub fn set_edge_color(&mut self, color: Vec3) -> &mut Self {
self.edge_color = color.extend(1.0);
self
}
#[must_use]
pub fn edge_width(&self) -> f32 {
self.edge_width
}
pub fn set_edge_width(&mut self, width: f32) -> &mut Self {
self.edge_width = width;
self
}
#[must_use]
pub fn decompose_to_tets(&self) -> Vec<[u32; 4]> {
let mut tets = Vec::new();
for cell in &self.cells {
if cell[4] == u32::MAX {
tets.push([cell[0], cell[1], cell[2], cell[3]]);
} else {
for tet_local in &HEX_TO_TET_PATTERN {
let tet = [
cell[tet_local[0]],
cell[tet_local[1]],
cell[tet_local[2]],
cell[tet_local[3]],
];
tets.push(tet);
}
}
}
tets
}
#[must_use]
pub fn num_tets(&self) -> usize {
self.decompose_to_tets().len()
}
fn compute_face_counts(&self) -> HashMap<[u32; 4], usize> {
let mut face_counts: HashMap<[u32; 4], usize> = HashMap::new();
for cell in &self.cells {
if cell[4] == u32::MAX {
for [a, b, c] in TET_FACE_STENCIL {
let key = canonical_face_key(cell[a], cell[b], cell[c], None);
*face_counts.entry(key).or_insert(0) += 1;
}
} else {
for quad in HEX_FACE_STENCIL {
let v0 = cell[quad[0][0]];
let v1 = cell[quad[0][1]];
let v2 = cell[quad[0][2]];
let v3 = cell[quad[1][2]]; let key = canonical_face_key(v0, v1, v2, Some(v3));
*face_counts.entry(key).or_insert(0) += 1;
}
}
}
face_counts
}
fn cell_centroid(&self, cell: &[u32; 8]) -> Vec3 {
if cell[4] == u32::MAX {
let sum = self.vertices[cell[0] as usize]
+ self.vertices[cell[1] as usize]
+ self.vertices[cell[2] as usize]
+ self.vertices[cell[3] as usize];
sum / 4.0
} else {
let sum = (0..8)
.map(|i| self.vertices[cell[i] as usize])
.fold(Vec3::ZERO, |a, b| a + b);
sum / 8.0
}
}
fn is_cell_visible(&self, cell: &[u32; 8], planes: &[(Vec3, Vec3)]) -> bool {
if planes.is_empty() {
return true;
}
let centroid = self.cell_centroid(cell);
let centroid_world = (self.transform * centroid.extend(1.0)).truncate();
for (plane_origin, plane_normal) in planes {
let signed_dist = (centroid_world - *plane_origin).dot(*plane_normal);
if signed_dist < 0.0 {
return false;
}
}
true
}
fn compute_face_counts_with_culling(
&self,
planes: &[(Vec3, Vec3)],
) -> HashMap<[u32; 4], usize> {
let mut face_counts: HashMap<[u32; 4], usize> = HashMap::new();
for cell in &self.cells {
if !self.is_cell_visible(cell, planes) {
continue;
}
if cell[4] == u32::MAX {
for [a, b, c] in TET_FACE_STENCIL {
let key = canonical_face_key(cell[a], cell[b], cell[c], None);
*face_counts.entry(key).or_insert(0) += 1;
}
} else {
for quad in HEX_FACE_STENCIL {
let v0 = cell[quad[0][0]];
let v1 = cell[quad[0][1]];
let v2 = cell[quad[0][2]];
let v3 = cell[quad[1][2]];
let key = canonical_face_key(v0, v1, v2, Some(v3));
*face_counts.entry(key).or_insert(0) += 1;
}
}
}
face_counts
}
fn generate_render_geometry(&self) -> (Vec<Vec3>, Vec<[u32; 3]>) {
let face_counts = self.compute_face_counts();
let mut positions = Vec::new();
let mut faces = Vec::new();
for cell in &self.cells {
if cell[4] == u32::MAX {
for [a, b, c] in TET_FACE_STENCIL {
let key = canonical_face_key(cell[a], cell[b], cell[c], None);
if face_counts[&key] == 1 {
let base_idx = positions.len() as u32;
positions.push(self.vertices[cell[a] as usize]);
positions.push(self.vertices[cell[b] as usize]);
positions.push(self.vertices[cell[c] as usize]);
faces.push([base_idx, base_idx + 1, base_idx + 2]);
}
}
} else {
for quad in HEX_FACE_STENCIL {
let v0 = cell[quad[0][0]];
let v1 = cell[quad[0][1]];
let v2 = cell[quad[0][2]];
let v3 = cell[quad[1][2]];
let key = canonical_face_key(v0, v1, v2, Some(v3));
if face_counts[&key] == 1 {
for [a, b, c] in quad {
let base_idx = positions.len() as u32;
positions.push(self.vertices[cell[a] as usize]);
positions.push(self.vertices[cell[b] as usize]);
positions.push(self.vertices[cell[c] as usize]);
faces.push([base_idx, base_idx + 1, base_idx + 2]);
}
}
}
}
}
(positions, faces)
}
fn generate_render_geometry_with_culling(
&self,
planes: &[(Vec3, Vec3)],
) -> (Vec<Vec3>, Vec<[u32; 3]>) {
let face_counts = self.compute_face_counts_with_culling(planes);
let mut positions = Vec::new();
let mut faces = Vec::new();
for cell in &self.cells {
if !self.is_cell_visible(cell, planes) {
continue;
}
if cell[4] == u32::MAX {
for [a, b, c] in TET_FACE_STENCIL {
let key = canonical_face_key(cell[a], cell[b], cell[c], None);
if face_counts.get(&key) == Some(&1) {
let base_idx = positions.len() as u32;
positions.push(self.vertices[cell[a] as usize]);
positions.push(self.vertices[cell[b] as usize]);
positions.push(self.vertices[cell[c] as usize]);
faces.push([base_idx, base_idx + 1, base_idx + 2]);
}
}
} else {
for quad in HEX_FACE_STENCIL {
let v0 = cell[quad[0][0]];
let v1 = cell[quad[0][1]];
let v2 = cell[quad[0][2]];
let v3 = cell[quad[1][2]];
let key = canonical_face_key(v0, v1, v2, Some(v3));
if face_counts.get(&key) == Some(&1) {
for [a, b, c] in quad {
let base_idx = positions.len() as u32;
positions.push(self.vertices[cell[a] as usize]);
positions.push(self.vertices[cell[b] as usize]);
positions.push(self.vertices[cell[c] as usize]);
faces.push([base_idx, base_idx + 1, base_idx + 2]);
}
}
}
}
}
(positions, faces)
}
#[must_use]
pub fn generate_render_geometry_with_quantities(&self) -> VolumeMeshRenderGeometry {
let face_counts = self.compute_face_counts();
let mut positions = Vec::new();
let mut faces = Vec::new();
let mut vertex_indices = Vec::new(); let mut cell_indices = Vec::new();
for (cell_idx, cell) in self.cells.iter().enumerate() {
if cell[4] == u32::MAX {
for [a, b, c] in TET_FACE_STENCIL {
let key = canonical_face_key(cell[a], cell[b], cell[c], None);
if face_counts[&key] == 1 {
let base_idx = positions.len() as u32;
positions.push(self.vertices[cell[a] as usize]);
positions.push(self.vertices[cell[b] as usize]);
positions.push(self.vertices[cell[c] as usize]);
vertex_indices.push(cell[a] as usize);
vertex_indices.push(cell[b] as usize);
vertex_indices.push(cell[c] as usize);
cell_indices.push(cell_idx);
cell_indices.push(cell_idx);
cell_indices.push(cell_idx);
faces.push([base_idx, base_idx + 1, base_idx + 2]);
}
}
} else {
for quad in HEX_FACE_STENCIL {
let v0 = cell[quad[0][0]];
let v1 = cell[quad[0][1]];
let v2 = cell[quad[0][2]];
let v3 = cell[quad[1][2]];
let key = canonical_face_key(v0, v1, v2, Some(v3));
if face_counts[&key] == 1 {
for [a, b, c] in quad {
let base_idx = positions.len() as u32;
positions.push(self.vertices[cell[a] as usize]);
positions.push(self.vertices[cell[b] as usize]);
positions.push(self.vertices[cell[c] as usize]);
vertex_indices.push(cell[a] as usize);
vertex_indices.push(cell[b] as usize);
vertex_indices.push(cell[c] as usize);
cell_indices.push(cell_idx);
cell_indices.push(cell_idx);
cell_indices.push(cell_idx);
faces.push([base_idx, base_idx + 1, base_idx + 2]);
}
}
}
}
}
let mut normals = vec![Vec3::ZERO; positions.len()];
for [a, b, c] in &faces {
let p0 = positions[*a as usize];
let p1 = positions[*b as usize];
let p2 = positions[*c as usize];
let normal = (p1 - p0).cross(p2 - p0).normalize_or_zero();
normals[*a as usize] = normal;
normals[*b as usize] = normal;
normals[*c as usize] = normal;
}
let mut vertex_values = None;
let mut vertex_colors = None;
for q in &self.quantities {
if q.is_enabled() {
if let Some(scalar) = q.as_any().downcast_ref::<VolumeMeshVertexScalarQuantity>() {
let values: Vec<f32> = vertex_indices
.iter()
.map(|&idx| scalar.values().get(idx).copied().unwrap_or(0.0))
.collect();
vertex_values = Some(values);
break;
}
if let Some(color) = q.as_any().downcast_ref::<VolumeMeshVertexColorQuantity>() {
let colors: Vec<Vec3> = vertex_indices
.iter()
.map(|&idx| {
color
.colors()
.get(idx)
.copied()
.unwrap_or(Vec4::new(1.0, 1.0, 1.0, 1.0))
.truncate()
})
.collect();
vertex_colors = Some(colors);
break;
}
if let Some(scalar) = q.as_any().downcast_ref::<VolumeMeshCellScalarQuantity>() {
let values: Vec<f32> = cell_indices
.iter()
.map(|&idx| scalar.values().get(idx).copied().unwrap_or(0.0))
.collect();
vertex_values = Some(values);
break;
}
if let Some(color) = q.as_any().downcast_ref::<VolumeMeshCellColorQuantity>() {
let colors: Vec<Vec3> = cell_indices
.iter()
.map(|&idx| {
color
.colors()
.get(idx)
.copied()
.unwrap_or(Vec4::new(1.0, 1.0, 1.0, 1.0))
.truncate()
})
.collect();
vertex_colors = Some(colors);
break;
}
}
}
VolumeMeshRenderGeometry {
positions,
faces,
normals,
vertex_values,
vertex_colors,
}
}
pub fn init_render_data(
&mut self,
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
camera_buffer: &wgpu::Buffer,
) {
let (positions, triangles) = self.generate_render_geometry();
if triangles.is_empty() {
return;
}
let mut normals = vec![Vec3::ZERO; positions.len()];
for [a, b, c] in &triangles {
let p0 = positions[*a as usize];
let p1 = positions[*b as usize];
let p2 = positions[*c as usize];
let normal = (p1 - p0).cross(p2 - p0).normalize_or_zero();
normals[*a as usize] = normal;
normals[*b as usize] = normal;
normals[*c as usize] = normal;
}
let edge_is_real: Vec<Vec3> = vec![Vec3::ONE; triangles.len() * 3];
let render_data = SurfaceMeshRenderData::new(
device,
bind_group_layout,
camera_buffer,
&positions,
&triangles,
&normals,
&edge_is_real,
);
self.render_data = Some(render_data);
}
pub fn update_render_data_with_culling(
&mut self,
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
camera_buffer: &wgpu::Buffer,
planes: &[(Vec3, Vec3)],
) {
let cache_valid = self.culling_plane_cache.as_ref().is_some_and(|cache| {
if cache.len() != planes.len() {
return false;
}
cache.iter().zip(planes.iter()).all(|((o, n), (po, pn))| {
(*o - *po).length_squared() < 1e-10 && (*n - *pn).length_squared() < 1e-10
})
});
if cache_valid && self.render_data.is_some() {
return;
}
let (positions, triangles) = self.generate_render_geometry_with_culling(planes);
if triangles.is_empty() {
self.render_data = None;
self.culling_plane_cache = Some(planes.to_vec());
return;
}
let mut normals = vec![Vec3::ZERO; positions.len()];
for [a, b, c] in &triangles {
let p0 = positions[*a as usize];
let p1 = positions[*b as usize];
let p2 = positions[*c as usize];
let normal = (p1 - p0).cross(p2 - p0).normalize_or_zero();
normals[*a as usize] = normal;
normals[*b as usize] = normal;
normals[*c as usize] = normal;
}
let edge_is_real: Vec<Vec3> = vec![Vec3::ONE; triangles.len() * 3];
let render_data = SurfaceMeshRenderData::new(
device,
bind_group_layout,
camera_buffer,
&positions,
&triangles,
&normals,
&edge_is_real,
);
self.render_data = Some(render_data);
self.culling_plane_cache = Some(planes.to_vec());
}
pub fn reset_render_data(
&mut self,
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
camera_buffer: &wgpu::Buffer,
) {
self.culling_plane_cache = None;
self.init_render_data(device, bind_group_layout, camera_buffer);
}
#[must_use]
pub fn is_culled(&self) -> bool {
self.culling_plane_cache.is_some()
}
#[must_use]
pub fn render_data(&self) -> Option<&SurfaceMeshRenderData> {
self.render_data.as_ref()
}
#[must_use]
pub fn pick_triangles(&self, planes: &[(Vec3, Vec3)]) -> (Vec<Vec3>, Vec<[u32; 3]>) {
if planes.is_empty() {
self.generate_render_geometry()
} else {
self.generate_render_geometry_with_culling(planes)
}
}
fn generate_cell_index_per_triangle(&self) -> Vec<u32> {
let face_counts = self.compute_face_counts();
let mut cell_indices = Vec::new();
for (cell_idx, cell) in self.cells.iter().enumerate() {
if cell[4] == u32::MAX {
for [a, b, c] in TET_FACE_STENCIL {
let key = canonical_face_key(cell[a], cell[b], cell[c], None);
if face_counts[&key] == 1 {
cell_indices.push(cell_idx as u32);
}
}
} else {
for quad in HEX_FACE_STENCIL {
let v0 = cell[quad[0][0]];
let v1 = cell[quad[0][1]];
let v2 = cell[quad[0][2]];
let v3 = cell[quad[1][2]];
let key = canonical_face_key(v0, v1, v2, Some(v3));
if face_counts[&key] == 1 {
cell_indices.push(cell_idx as u32);
cell_indices.push(cell_idx as u32);
}
}
}
}
cell_indices
}
fn generate_cell_index_per_triangle_with_culling(&self, planes: &[(Vec3, Vec3)]) -> Vec<u32> {
let face_counts = self.compute_face_counts_with_culling(planes);
let mut cell_indices = Vec::new();
for (cell_idx, cell) in self.cells.iter().enumerate() {
if !self.is_cell_visible(cell, planes) {
continue;
}
if cell[4] == u32::MAX {
for [a, b, c] in TET_FACE_STENCIL {
let key = canonical_face_key(cell[a], cell[b], cell[c], None);
if face_counts.get(&key) == Some(&1) {
cell_indices.push(cell_idx as u32);
}
}
} else {
for quad in HEX_FACE_STENCIL {
let v0 = cell[quad[0][0]];
let v1 = cell[quad[0][1]];
let v2 = cell[quad[0][2]];
let v3 = cell[quad[1][2]];
let key = canonical_face_key(v0, v1, v2, Some(v3));
if face_counts.get(&key) == Some(&1) {
cell_indices.push(cell_idx as u32);
cell_indices.push(cell_idx as u32);
}
}
}
}
cell_indices
}
pub fn init_pick_resources(
&mut self,
device: &wgpu::Device,
mesh_pick_bind_group_layout: &wgpu::BindGroupLayout,
camera_buffer: &wgpu::Buffer,
global_start: u32,
) {
use wgpu::util::DeviceExt;
self.global_start = global_start;
let model = self.transform.to_cols_array_2d();
let pick_uniforms = MeshPickUniforms {
global_start,
_padding: [0.0; 3],
model,
};
let pick_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("volume mesh pick uniforms"),
contents: bytemuck::cast_slice(&[pick_uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let cell_index_data = if self.culling_plane_cache.is_some() {
self.generate_cell_index_per_triangle_with_culling(
self.culling_plane_cache.as_deref().unwrap_or(&[]),
)
} else {
self.generate_cell_index_per_triangle()
};
let pick_cell_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("volume mesh pick cell indices"),
contents: bytemuck::cast_slice(&cell_index_data),
usage: wgpu::BufferUsages::STORAGE,
});
if let Some(render_data) = &self.render_data {
let pick_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("volume mesh pick bind group"),
layout: mesh_pick_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: camera_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: pick_uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: render_data.vertex_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: pick_cell_index_buffer.as_entire_binding(),
},
],
});
self.pick_bind_group = Some(pick_bind_group);
}
self.pick_uniform_buffer = Some(pick_uniform_buffer);
self.pick_cell_index_buffer = Some(pick_cell_index_buffer);
}
#[must_use]
pub fn pick_bind_group(&self) -> Option<&wgpu::BindGroup> {
self.pick_bind_group.as_ref()
}
pub fn update_pick_uniforms(&self, queue: &wgpu::Queue) {
if let Some(buffer) = &self.pick_uniform_buffer {
let model = self.transform.to_cols_array_2d();
let pick_uniforms = MeshPickUniforms {
global_start: self.global_start,
_padding: [0.0; 3],
model,
};
queue.write_buffer(buffer, 0, bytemuck::cast_slice(&[pick_uniforms]));
}
}
#[must_use]
pub fn num_render_vertices(&self) -> u32 {
self.render_data
.as_ref()
.map_or(0, SurfaceMeshRenderData::num_vertices)
}
pub fn update_gpu_buffers(&self, queue: &wgpu::Queue) {
if let Some(ref rd) = self.render_data {
let model_matrix = self.transform.to_cols_array_2d();
let uniforms = MeshUniforms {
model_matrix,
shade_style: 0, show_edges: u32::from(self.edge_width > 0.0),
edge_width: self.edge_width,
transparency: 0.0,
surface_color: self.color.to_array(),
edge_color: self.edge_color.to_array(),
backface_policy: 0,
slice_planes_enabled: 0,
..Default::default()
};
rd.update_uniforms(queue, &uniforms);
}
}
pub fn update_slice_render_data(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
bind_group_layout: &wgpu::BindGroupLayout,
camera_buffer: &wgpu::Buffer,
plane_origin: Vec3,
plane_normal: Vec3,
) -> bool {
let cache_valid = self.slice_plane_cache.is_some_and(|(o, n)| {
(o - plane_origin).length_squared() < 1e-10
&& (n - plane_normal).length_squared() < 1e-10
});
if cache_valid {
if let Some(ref data) = self.slice_render_data {
return !data.is_empty();
}
}
if let Some(slice_data) = self.generate_slice_geometry(plane_origin, plane_normal) {
if let Some(ref mut rd) = self.slice_render_data {
rd.update(
device,
queue,
bind_group_layout,
camera_buffer,
&slice_data.vertices,
&slice_data.normals,
&slice_data.colors,
);
} else {
self.slice_render_data = Some(SliceMeshRenderData::new(
device,
bind_group_layout,
camera_buffer,
&slice_data.vertices,
&slice_data.normals,
&slice_data.colors,
));
}
if let Some(ref rd) = self.slice_render_data {
rd.update_uniforms(queue, self.interior_color.truncate());
}
self.slice_plane_cache = Some((plane_origin, plane_normal));
true
} else {
self.slice_render_data = None;
self.slice_plane_cache = None;
false
}
}
#[must_use]
pub fn slice_render_data(&self) -> Option<&SliceMeshRenderData> {
self.slice_render_data.as_ref()
}
pub fn clear_slice_render_data(&mut self) {
self.slice_render_data = None;
self.slice_plane_cache = None;
}
pub fn build_egui_ui(&mut self, ui: &mut egui::Ui) {
let num_tets = self.cells.iter().filter(|c| c[4] == u32::MAX).count();
let num_hexes = self.num_cells() - num_tets;
ui.label(format!(
"{} verts, {} cells ({} tets, {} hexes)",
self.num_vertices(),
self.num_cells(),
num_tets,
num_hexes
));
ui.horizontal(|ui| {
ui.label("Color:");
let mut color = [self.color.x, self.color.y, self.color.z];
if ui.color_edit_button_rgb(&mut color).changed() {
self.color = Vec4::new(color[0], color[1], color[2], self.color.w);
}
});
ui.horizontal(|ui| {
let mut show_edges = self.edge_width > 0.0;
if ui.checkbox(&mut show_edges, "Edges").changed() {
self.set_edge_width(if show_edges { 1.0 } else { 0.0 });
}
if show_edges {
let mut width = self.edge_width;
if ui
.add(
egui::DragValue::new(&mut width)
.speed(0.01)
.range(0.01..=5.0),
)
.changed()
{
self.set_edge_width(width);
}
}
});
}
pub fn add_vertex_scalar_quantity(
&mut self,
name: impl Into<String>,
values: Vec<f32>,
) -> &mut Self {
let name = name.into();
let quantity = VolumeMeshVertexScalarQuantity::new(name.clone(), self.name.clone(), values);
self.add_quantity(Box::new(quantity));
self
}
pub fn add_cell_scalar_quantity(
&mut self,
name: impl Into<String>,
values: Vec<f32>,
) -> &mut Self {
let name = name.into();
let quantity = VolumeMeshCellScalarQuantity::new(name.clone(), self.name.clone(), values);
self.add_quantity(Box::new(quantity));
self
}
pub fn add_vertex_color_quantity(
&mut self,
name: impl Into<String>,
colors: Vec<Vec3>,
) -> &mut Self {
let name = name.into();
let quantity = VolumeMeshVertexColorQuantity::new(name.clone(), self.name.clone(), colors);
self.add_quantity(Box::new(quantity));
self
}
pub fn add_cell_color_quantity(
&mut self,
name: impl Into<String>,
colors: Vec<Vec3>,
) -> &mut Self {
let name = name.into();
let quantity = VolumeMeshCellColorQuantity::new(name.clone(), self.name.clone(), colors);
self.add_quantity(Box::new(quantity));
self
}
pub fn add_vertex_vector_quantity(
&mut self,
name: impl Into<String>,
vectors: Vec<Vec3>,
) -> &mut Self {
let name = name.into();
let quantity =
VolumeMeshVertexVectorQuantity::new(name.clone(), self.name.clone(), vectors);
self.add_quantity(Box::new(quantity));
self
}
pub fn add_cell_vector_quantity(
&mut self,
name: impl Into<String>,
vectors: Vec<Vec3>,
) -> &mut Self {
let name = name.into();
let quantity = VolumeMeshCellVectorQuantity::new(name.clone(), self.name.clone(), vectors);
self.add_quantity(Box::new(quantity));
self
}
fn active_vertex_color_quantity(&self) -> Option<&VolumeMeshVertexColorQuantity> {
for q in &self.quantities {
if q.is_enabled() {
if let Some(vcq) = q.as_any().downcast_ref::<VolumeMeshVertexColorQuantity>() {
return Some(vcq);
}
}
}
None
}
#[must_use]
pub fn generate_slice_geometry(
&self,
plane_origin: Vec3,
plane_normal: Vec3,
) -> Option<SliceMeshData> {
let mut vertices = Vec::new();
let mut normals = Vec::new();
let mut colors = Vec::new();
let vertex_colors = self
.active_vertex_color_quantity()
.map(color_quantity::VolumeMeshVertexColorQuantity::colors);
for (cell_idx, cell) in self.cells.iter().enumerate() {
let cell_type = self.cell_type(cell_idx);
let slice = match cell_type {
VolumeCellType::Tet => slice_tet(
self.vertices[cell[0] as usize],
self.vertices[cell[1] as usize],
self.vertices[cell[2] as usize],
self.vertices[cell[3] as usize],
plane_origin,
plane_normal,
),
VolumeCellType::Hex => {
let hex_verts: [Vec3; 8] =
std::array::from_fn(|i| self.vertices[cell[i] as usize]);
slice_hex(hex_verts, plane_origin, plane_normal)
}
};
if slice.has_intersection() {
let slice_colors: Vec<Vec4> = if let Some(vc) = vertex_colors {
slice
.interpolation
.iter()
.map(|&(a, b, t)| {
let va_idx = cell[a as usize] as usize;
let vb_idx = cell[b as usize] as usize;
vc[va_idx].lerp(vc[vb_idx], t)
})
.collect()
} else {
vec![self.interior_color; slice.vertices.len()]
};
for i in 1..slice.vertices.len() - 1 {
vertices.push(slice.vertices[0]);
vertices.push(slice.vertices[i]);
vertices.push(slice.vertices[i + 1]);
normals.push(plane_normal);
normals.push(plane_normal);
normals.push(plane_normal);
colors.push(slice_colors[0]);
colors.push(slice_colors[i]);
colors.push(slice_colors[i + 1]);
}
}
}
if vertices.is_empty() {
return None;
}
Some(SliceMeshData {
vertices,
normals,
colors,
})
}
}
#[derive(Debug, Clone)]
pub struct SliceMeshData {
pub vertices: Vec<Vec3>,
pub normals: Vec<Vec3>,
pub colors: Vec<Vec4>,
}
impl SliceMeshData {
#[must_use]
pub fn num_triangles(&self) -> usize {
self.vertices.len() / 3
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.vertices.is_empty()
}
}
impl Structure for VolumeMesh {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn name(&self) -> &str {
&self.name
}
fn type_name(&self) -> &'static str {
"VolumeMesh"
}
fn bounding_box(&self) -> Option<(Vec3, Vec3)> {
if self.vertices.is_empty() {
return None;
}
let mut min = Vec3::splat(f32::MAX);
let mut max = Vec3::splat(f32::MIN);
for &v in &self.vertices {
min = min.min(v);
max = max.max(v);
}
Some((min, max))
}
fn length_scale(&self) -> f32 {
self.bounding_box()
.map_or(1.0, |(min, max)| (max - min).length())
}
fn transform(&self) -> Mat4 {
self.transform
}
fn set_transform(&mut self, transform: Mat4) {
self.transform = transform;
self.culling_plane_cache = None;
self.pick_bind_group = None;
self.pick_cell_index_buffer = None;
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn draw(&self, _ctx: &mut dyn RenderContext) {
}
fn draw_pick(&self, _ctx: &mut dyn RenderContext) {
}
fn build_ui(&mut self, _ui: &dyn std::any::Any) {
}
fn build_pick_ui(&self, _ui: &dyn std::any::Any, _pick: &PickResult) {
}
fn clear_gpu_resources(&mut self) {
self.render_data = None;
self.pick_uniform_buffer = None;
self.pick_bind_group = None;
self.pick_cell_index_buffer = None;
self.slice_render_data = None;
self.slice_plane_cache = None;
self.culling_plane_cache = None;
for quantity in &mut self.quantities {
quantity.clear_gpu_resources();
}
}
fn refresh(&mut self) {
self.render_data = None;
self.pick_uniform_buffer = None;
self.pick_bind_group = None;
self.pick_cell_index_buffer = None;
self.slice_render_data = None;
self.slice_plane_cache = None;
self.culling_plane_cache = None;
for quantity in &mut self.quantities {
quantity.refresh();
}
}
}
impl HasQuantities for VolumeMesh {
fn add_quantity(&mut self, quantity: Box<dyn Quantity>) {
self.quantities.push(quantity);
}
fn get_quantity(&self, name: &str) -> Option<&dyn Quantity> {
self.quantities
.iter()
.find(|q| q.name() == name)
.map(std::convert::AsRef::as_ref)
}
fn get_quantity_mut(&mut self, name: &str) -> Option<&mut Box<dyn Quantity>> {
self.quantities.iter_mut().find(|q| q.name() == name)
}
fn remove_quantity(&mut self, name: &str) -> Option<Box<dyn Quantity>> {
let idx = self.quantities.iter().position(|q| q.name() == name)?;
Some(self.quantities.remove(idx))
}
fn quantities(&self) -> &[Box<dyn Quantity>] {
&self.quantities
}
}
use std::collections::HashMap;
fn canonical_face_key(v0: u32, v1: u32, v2: u32, v3: Option<u32>) -> [u32; 4] {
let mut key = [v0, v1, v2, v3.unwrap_or(u32::MAX)];
key.sort_unstable();
key
}
const TET_FACE_STENCIL: [[usize; 3]; 4] = [[0, 2, 1], [0, 1, 3], [0, 3, 2], [1, 2, 3]];
const HEX_FACE_STENCIL: [[[usize; 3]; 2]; 6] = [
[[2, 1, 0], [2, 0, 3]], [[4, 0, 1], [4, 1, 5]], [[5, 1, 2], [5, 2, 6]], [[7, 3, 0], [7, 0, 4]], [[6, 2, 3], [6, 3, 7]], [[7, 4, 5], [7, 5, 6]], ];
pub struct VolumeMeshRenderGeometry {
pub positions: Vec<Vec3>,
pub faces: Vec<[u32; 3]>,
pub normals: Vec<Vec3>,
pub vertex_values: Option<Vec<f32>>,
pub vertex_colors: Option<Vec<Vec3>>,
}
const HEX_TO_TET_PATTERN: [[usize; 4]; 5] = [
[0, 1, 2, 5],
[0, 2, 7, 5],
[0, 2, 3, 7],
[0, 5, 7, 4],
[2, 7, 5, 6],
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_interior_face_detection() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.5, 1.0, 0.0), Vec3::new(0.5, 0.5, 1.0), Vec3::new(0.5, 0.5, -1.0), ];
let tets = vec![[0, 1, 2, 3], [0, 2, 1, 4]];
let mesh = VolumeMesh::new_tet_mesh("test", vertices, tets);
let (_, faces) = mesh.generate_render_geometry();
assert_eq!(faces.len(), 6, "Should only render exterior faces");
}
#[test]
fn test_single_tet_all_exterior() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.5, 1.0, 0.0),
Vec3::new(0.5, 0.5, 1.0),
];
let tets = vec![[0, 1, 2, 3]];
let mesh = VolumeMesh::new_tet_mesh("test", vertices, tets);
let (_, faces) = mesh.generate_render_geometry();
assert_eq!(faces.len(), 4, "Single tet should have 4 exterior faces");
}
#[test]
fn test_single_hex_all_exterior() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(1.0, 1.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(1.0, 0.0, 1.0),
Vec3::new(1.0, 1.0, 1.0),
Vec3::new(0.0, 1.0, 1.0),
];
let hexes = vec![[0, 1, 2, 3, 4, 5, 6, 7]];
let mesh = VolumeMesh::new_hex_mesh("test", vertices, hexes);
let (_, faces) = mesh.generate_render_geometry();
assert_eq!(
faces.len(),
12,
"Single hex should have 12 triangles (6 quads)"
);
}
#[test]
fn test_hex_to_tet_decomposition() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(1.0, 1.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(1.0, 0.0, 1.0),
Vec3::new(1.0, 1.0, 1.0),
Vec3::new(0.0, 1.0, 1.0),
];
let hexes = vec![[0, 1, 2, 3, 4, 5, 6, 7]];
let mesh = VolumeMesh::new_hex_mesh("test", vertices, hexes);
let tets = mesh.decompose_to_tets();
assert_eq!(tets.len(), 5);
for tet in &tets {
assert!(tet[0] < 8);
assert!(tet[1] < 8);
assert!(tet[2] < 8);
assert!(tet[3] < 8);
}
}
#[test]
fn test_quantity_aware_geometry_generation() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.5, 1.0, 0.0),
Vec3::new(0.5, 0.5, 1.0),
];
let tets = vec![[0, 1, 2, 3]];
let mut mesh = VolumeMesh::new_tet_mesh("test", vertices, tets);
mesh.add_vertex_scalar_quantity("temp", vec![0.0, 0.5, 1.0, 0.25]);
if let Some(q) = mesh.get_quantity_mut("temp") {
q.set_enabled(true);
}
let render_data = mesh.generate_render_geometry_with_quantities();
assert!(render_data.vertex_values.is_some());
assert_eq!(
render_data.vertex_values.as_ref().unwrap().len(),
render_data.positions.len()
);
}
}