mod geometry;
mod intrinsic_vector_quantity;
mod one_form_quantity;
mod parameterization_quantity;
mod quantities;
mod quantity_methods;
pub use intrinsic_vector_quantity::*;
pub use one_form_quantity::*;
pub use parameterization_quantity::*;
pub use quantities::*;
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::{ColorMapRegistry, MeshPickUniforms, MeshUniforms, SurfaceMeshRenderData};
use std::ops::Range;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ShadeStyle {
#[default]
Smooth,
Flat,
TriFlat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BackfacePolicy {
#[default]
Identical,
Different,
Custom,
Cull,
}
pub struct SurfaceMesh {
name: String,
vertices: Vec<Vec3>,
faces: Vec<Vec<u32>>, enabled: bool,
transform: Mat4,
quantities: Vec<Box<dyn Quantity>>,
triangulation: Vec<[u32; 3]>,
face_to_tri_range: Vec<Range<usize>>,
vertex_normals: Vec<Vec3>,
face_normals: Vec<Vec3>,
corner_normals: Vec<Vec3>,
edge_is_real: Vec<Vec3>,
edges: Vec<(u32, u32)>,
needs_recompute: bool,
material: String,
shade_style: ShadeStyle,
edge_width: f32,
edge_color: Vec4,
show_edges: bool,
backface_policy: BackfacePolicy,
backface_color: Vec4,
surface_color: Vec4,
transparency: f32,
render_data: Option<SurfaceMeshRenderData>,
pick_uniform_buffer: Option<wgpu::Buffer>,
pick_bind_group: Option<wgpu::BindGroup>,
pick_face_index_buffer: Option<wgpu::Buffer>,
global_start: u32,
}
impl SurfaceMesh {
pub fn new(name: impl Into<String>, vertices: Vec<Vec3>, faces: Vec<Vec<u32>>) -> Self {
let mut mesh = Self {
name: name.into(),
vertices,
faces,
enabled: true,
transform: Mat4::IDENTITY,
quantities: Vec::new(),
triangulation: Vec::new(),
face_to_tri_range: Vec::new(),
vertex_normals: Vec::new(),
face_normals: Vec::new(),
corner_normals: Vec::new(),
edge_is_real: Vec::new(),
edges: Vec::new(),
needs_recompute: true,
material: "clay".to_string(),
shade_style: ShadeStyle::default(),
edge_width: 1.0,
edge_color: Vec4::new(0.0, 0.0, 0.0, 1.0),
show_edges: false,
backface_policy: BackfacePolicy::default(),
backface_color: Vec4::new(0.3, 0.3, 0.3, 1.0),
surface_color: Vec4::new(0.5, 0.5, 0.8, 1.0),
transparency: 0.0,
render_data: None,
pick_uniform_buffer: None,
pick_bind_group: None,
pick_face_index_buffer: None,
global_start: 0,
};
mesh.recompute();
mesh
}
pub fn from_triangles(
name: impl Into<String>,
vertices: Vec<Vec3>,
triangles: Vec<[u32; 3]>,
) -> Self {
let faces: Vec<Vec<u32>> = triangles.into_iter().map(|t| t.to_vec()).collect();
Self::new(name, vertices, faces)
}
#[must_use]
pub fn num_vertices(&self) -> usize {
self.vertices.len()
}
#[must_use]
pub fn num_faces(&self) -> usize {
self.faces.len()
}
#[must_use]
pub fn num_triangles(&self) -> usize {
self.triangulation.len()
}
#[must_use]
pub fn num_edges(&self) -> usize {
self.edges.len()
}
#[must_use]
pub fn vertices(&self) -> &[Vec3] {
&self.vertices
}
#[must_use]
pub fn faces(&self) -> &[Vec<u32>] {
&self.faces
}
#[must_use]
pub fn triangulation(&self) -> &[[u32; 3]] {
&self.triangulation
}
#[must_use]
pub fn face_to_tri_range(&self) -> &[Range<usize>] {
&self.face_to_tri_range
}
#[must_use]
pub fn vertex_normals(&self) -> &[Vec3] {
&self.vertex_normals
}
#[must_use]
pub fn face_normals(&self) -> &[Vec3] {
&self.face_normals
}
#[must_use]
pub fn corner_normals(&self) -> &[Vec3] {
&self.corner_normals
}
#[must_use]
pub fn edge_is_real(&self) -> &[Vec3] {
&self.edge_is_real
}
#[must_use]
pub fn edges(&self) -> &[(u32, u32)] {
&self.edges
}
pub fn update_vertices(&mut self, vertices: Vec<Vec3>) {
self.vertices = vertices;
self.needs_recompute = true;
self.refresh();
}
pub fn update_faces(&mut self, faces: Vec<Vec<u32>>) {
self.faces = faces;
self.needs_recompute = true;
self.refresh();
}
#[must_use]
pub fn shade_style(&self) -> ShadeStyle {
self.shade_style
}
pub fn set_shade_style(&mut self, style: ShadeStyle) {
self.shade_style = style;
}
#[must_use]
pub fn edge_width(&self) -> f32 {
self.edge_width
}
pub fn set_edge_width(&mut self, width: f32) {
self.edge_width = width;
}
#[must_use]
pub fn edge_color(&self) -> Vec4 {
self.edge_color
}
pub fn set_edge_color(&mut self, color: Vec3) {
self.edge_color = color.extend(1.0);
}
#[must_use]
pub fn show_edges(&self) -> bool {
self.show_edges
}
pub fn set_show_edges(&mut self, show: bool) {
self.show_edges = show;
}
#[must_use]
pub fn backface_policy(&self) -> BackfacePolicy {
self.backface_policy
}
pub fn set_backface_policy(&mut self, policy: BackfacePolicy) {
self.backface_policy = policy;
}
#[must_use]
pub fn backface_color(&self) -> Vec4 {
self.backface_color
}
pub fn set_backface_color(&mut self, color: Vec3) {
self.backface_color = color.extend(1.0);
}
#[must_use]
pub fn surface_color(&self) -> Vec4 {
self.surface_color
}
pub fn set_surface_color(&mut self, color: Vec3) {
self.surface_color = color.extend(1.0);
}
#[must_use]
pub fn transparency(&self) -> f32 {
self.transparency
}
pub fn set_transparency(&mut self, transparency: f32) {
self.transparency = transparency.clamp(0.0, 1.0);
}
pub fn build_egui_ui(&mut self, ui: &mut egui::Ui, available_materials: &[&str]) {
let mut shade_style = self.shade_style as u32;
let mut color = [
self.surface_color.x,
self.surface_color.y,
self.surface_color.z,
];
let mut transparency = self.transparency;
let mut show_edges = self.show_edges;
let mut edge_width = self.edge_width;
let mut edge_color = [self.edge_color.x, self.edge_color.y, self.edge_color.z];
let mut backface_policy = self.backface_policy as u32;
if polyscope_ui::build_surface_mesh_ui(
ui,
self.num_vertices(),
self.num_faces(),
self.num_edges(),
&mut shade_style,
&mut color,
&mut transparency,
&mut show_edges,
&mut edge_width,
&mut edge_color,
&mut backface_policy,
&mut self.material,
available_materials,
) {
self.shade_style = match shade_style {
0 => ShadeStyle::Smooth,
1 => ShadeStyle::Flat,
_ => ShadeStyle::TriFlat,
};
self.surface_color = Vec4::new(color[0], color[1], color[2], self.surface_color.w);
self.transparency = transparency;
self.show_edges = show_edges;
self.edge_width = edge_width;
self.edge_color = Vec4::new(
edge_color[0],
edge_color[1],
edge_color[2],
self.edge_color.w,
);
self.backface_policy = match backface_policy {
0 => BackfacePolicy::Identical,
1 => BackfacePolicy::Different,
2 => BackfacePolicy::Custom,
_ => BackfacePolicy::Cull,
};
}
if !self.quantities.is_empty() {
ui.separator();
ui.label("Quantities:");
for quantity in &mut self.quantities {
if let Some(sq) = quantity
.as_any_mut()
.downcast_mut::<MeshVertexScalarQuantity>()
{
sq.build_egui_ui(ui);
} else if let Some(sq) = quantity
.as_any_mut()
.downcast_mut::<MeshFaceScalarQuantity>()
{
sq.build_egui_ui(ui);
} else if let Some(cq) = quantity
.as_any_mut()
.downcast_mut::<MeshVertexColorQuantity>()
{
cq.build_egui_ui(ui);
} else if let Some(cq) = quantity
.as_any_mut()
.downcast_mut::<MeshFaceColorQuantity>()
{
cq.build_egui_ui(ui);
} else if let Some(vq) = quantity
.as_any_mut()
.downcast_mut::<MeshVertexVectorQuantity>()
{
vq.build_egui_ui(ui);
} else if let Some(vq) = quantity
.as_any_mut()
.downcast_mut::<MeshFaceVectorQuantity>()
{
vq.build_egui_ui(ui);
} else if let Some(pq) = quantity
.as_any_mut()
.downcast_mut::<MeshVertexParameterizationQuantity>()
{
pq.build_egui_ui(ui);
} else if let Some(pq) = quantity
.as_any_mut()
.downcast_mut::<MeshCornerParameterizationQuantity>()
{
pq.build_egui_ui(ui);
} else if let Some(iq) = quantity
.as_any_mut()
.downcast_mut::<MeshVertexIntrinsicVectorQuantity>()
{
iq.build_egui_ui(ui);
} else if let Some(iq) = quantity
.as_any_mut()
.downcast_mut::<MeshFaceIntrinsicVectorQuantity>()
{
iq.build_egui_ui(ui);
} else if let Some(oq) = quantity.as_any_mut().downcast_mut::<MeshOneFormQuantity>()
{
oq.build_egui_ui(ui);
}
}
}
}
pub fn init_gpu_resources(
&mut self,
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
camera_buffer: &wgpu::Buffer,
) {
self.render_data = Some(SurfaceMeshRenderData::new(
device,
bind_group_layout,
camera_buffer,
&self.vertices,
&self.triangulation,
&self.vertex_normals,
&self.edge_is_real,
));
}
#[must_use]
pub fn render_data(&self) -> Option<&SurfaceMeshRenderData> {
self.render_data.as_ref()
}
pub fn init_shadow_resources(
&mut self,
device: &wgpu::Device,
shadow_bind_group_layout: &wgpu::BindGroupLayout,
light_buffer: &wgpu::Buffer,
) {
if let Some(render_data) = &mut self.render_data {
render_data.init_shadow_resources(device, shadow_bind_group_layout, light_buffer);
}
}
#[must_use]
pub fn shadow_bind_group(&self) -> Option<&wgpu::BindGroup> {
self.render_data.as_ref()?.shadow_bind_group.as_ref()
}
#[must_use]
pub fn has_shadow_resources(&self) -> bool {
self.render_data
.as_ref()
.is_some_and(polyscope_render::SurfaceMeshRenderData::has_shadow_resources)
}
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("mesh pick uniforms"),
contents: bytemuck::cast_slice(&[pick_uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let mut face_index_data: Vec<u32> = Vec::with_capacity(self.triangulation.len());
for (face_idx, range) in self.face_to_tri_range.iter().enumerate() {
for _ in range.clone() {
face_index_data.push(face_idx as u32);
}
}
let pick_face_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("mesh pick face indices"),
contents: bytemuck::cast_slice(&face_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("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_face_index_buffer.as_entire_binding(),
},
],
});
self.pick_bind_group = Some(pick_bind_group);
}
self.pick_uniform_buffer = Some(pick_uniform_buffer);
self.pick_face_index_buffer = Some(pick_face_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_triangulation_vertices(&self) -> u32 {
(self.triangulation.len() * 3) as u32
}
pub fn update_gpu_buffers(&self, queue: &wgpu::Queue, color_maps: &ColorMapRegistry) {
let Some(render_data) = &self.render_data else {
return;
};
let model_matrix = self.transform.to_cols_array_2d();
let mut use_vertex_color = false;
if let Some(pq) = self.active_vertex_parameterization_quantity() {
use_vertex_color = true;
let colors = pq.compute_colors();
render_data.update_colors(queue, &colors, &self.triangulation);
} else if let Some(pq) = self.active_corner_parameterization_quantity() {
use_vertex_color = true;
let corner_colors = pq.compute_colors();
let mut vertex_colors = vec![Vec4::splat(0.5); self.vertices.len()];
let mut counts = vec![0u32; self.vertices.len()];
let mut corner_idx = 0;
for face in &self.faces {
for &vi in face {
if corner_idx < corner_colors.len() {
vertex_colors[vi as usize] += corner_colors[corner_idx];
counts[vi as usize] += 1;
corner_idx += 1;
}
}
}
for (i, count) in counts.iter().enumerate() {
if *count > 0 {
vertex_colors[i] /= *count as f32;
}
}
render_data.update_colors(queue, &vertex_colors, &self.triangulation);
} else if let Some(cq) = self.active_vertex_color_quantity() {
use_vertex_color = true;
render_data.update_colors(queue, cq.colors(), &self.triangulation);
} else if let Some(cq) = self.active_face_color_quantity() {
use_vertex_color = true;
let colors = cq.compute_vertex_colors(&self.faces, self.vertices.len());
render_data.update_colors(queue, &colors, &self.triangulation);
} else if let Some(sq) = self.active_vertex_scalar_quantity() {
use_vertex_color = true;
if let Some(colormap) = color_maps.get(sq.colormap_name()) {
let colors = sq.compute_colors(colormap);
render_data.update_colors(queue, &colors, &self.triangulation);
}
} else if let Some(sq) = self.active_face_scalar_quantity() {
use_vertex_color = true;
if let Some(colormap) = color_maps.get(sq.colormap_name()) {
let colors = sq.compute_vertex_colors(&self.faces, self.vertices.len(), colormap);
render_data.update_colors(queue, &colors, &self.triangulation);
}
} else {
render_data.clear_colors(queue);
}
let uniforms = MeshUniforms {
model_matrix,
shade_style: self.shade_style as u32,
show_edges: u32::from(self.show_edges),
edge_width: self.edge_width,
transparency: self.transparency,
surface_color: self.surface_color.to_array(),
edge_color: self.edge_color.to_array(),
backface_policy: self.backface_policy as u32,
slice_planes_enabled: 1,
use_vertex_color: u32::from(use_vertex_color),
_pad1: 0.0,
_pad2: [0.0; 3],
_pad3: 0.0,
backface_color: self.backface_color.to_array(),
};
render_data.update_uniforms(queue, &uniforms);
render_data.update_shadow_model(queue, model_matrix);
}
}
impl Structure for SurfaceMesh {
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 {
"SurfaceMesh"
}
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);
}
let transform = self.transform;
let corners = [
transform.transform_point3(Vec3::new(min.x, min.y, min.z)),
transform.transform_point3(Vec3::new(max.x, min.y, min.z)),
transform.transform_point3(Vec3::new(min.x, max.y, min.z)),
transform.transform_point3(Vec3::new(max.x, max.y, min.z)),
transform.transform_point3(Vec3::new(min.x, min.y, max.z)),
transform.transform_point3(Vec3::new(max.x, min.y, max.z)),
transform.transform_point3(Vec3::new(min.x, max.y, max.z)),
transform.transform_point3(Vec3::new(max.x, max.y, max.z)),
];
let mut world_min = Vec3::splat(f32::MAX);
let mut world_max = Vec3::splat(f32::MIN);
for corner in corners {
world_min = world_min.min(corner);
world_max = world_max.max(corner);
}
Some((world_min, world_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;
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn material(&self) -> &str {
&self.material
}
fn set_material(&mut self, material: &str) {
self.material = material.to_string();
}
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_face_index_buffer = None;
for quantity in &mut self.quantities {
quantity.clear_gpu_resources();
}
}
fn refresh(&mut self) {
self.recompute();
for quantity in &mut self.quantities {
quantity.refresh();
}
}
}
impl HasQuantities for SurfaceMesh {
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
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_surface_mesh_creation_and_triangulation() {
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),
];
let mesh = SurfaceMesh::new("test_tri", vertices, vec![vec![0, 1, 2]]);
assert_eq!(mesh.num_vertices(), 3);
assert_eq!(mesh.num_faces(), 1);
assert_eq!(mesh.num_triangles(), 1);
assert_eq!(mesh.triangulation()[0], [0, 1, 2]);
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),
];
let mesh = SurfaceMesh::new("test_quad", vertices, vec![vec![0, 1, 2, 3]]);
assert_eq!(mesh.num_vertices(), 4);
assert_eq!(mesh.num_faces(), 1);
assert_eq!(mesh.num_triangles(), 2);
assert_eq!(mesh.triangulation()[0], [0, 1, 2]);
assert_eq!(mesh.triangulation()[1], [0, 2, 3]);
assert_eq!(mesh.face_to_tri_range()[0], 0..2);
}
#[test]
fn test_face_normals() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
];
let faces = vec![vec![0, 1, 2]];
let mesh = SurfaceMesh::new("test_normal", vertices, faces);
assert_eq!(mesh.face_normals().len(), 1);
let normal = mesh.face_normals()[0];
assert!(
(normal.z - 1.0).abs() < 1e-6,
"Normal Z should be 1.0, got {}",
normal.z
);
assert!(
normal.x.abs() < 1e-6,
"Normal X should be 0.0, got {}",
normal.x
);
assert!(
normal.y.abs() < 1e-6,
"Normal Y should be 0.0, got {}",
normal.y
);
}
#[test]
fn test_edges() {
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),
];
let faces = vec![vec![0, 1, 2]];
let mesh = SurfaceMesh::new("test_edges", vertices, faces);
assert_eq!(mesh.num_edges(), 3);
let edges = mesh.edges();
assert!(edges.contains(&(0, 1)));
assert!(edges.contains(&(1, 2)));
assert!(edges.contains(&(0, 2)));
}
#[test]
fn test_vertex_normals() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
];
let faces = vec![vec![0, 1, 2]];
let mesh = SurfaceMesh::new("test_vnormals", vertices, faces);
assert_eq!(mesh.vertex_normals().len(), 3);
for normal in mesh.vertex_normals() {
assert!(
(normal.z - 1.0).abs() < 1e-6,
"Vertex normal Z should be 1.0"
);
}
}
#[test]
fn test_pentagon_triangulation() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(1.5, 0.5, 0.0),
Vec3::new(0.75, 1.0, 0.0),
Vec3::new(-0.25, 0.5, 0.0),
];
let faces = vec![vec![0, 1, 2, 3, 4]];
let mesh = SurfaceMesh::new("test_pentagon", vertices, faces);
assert_eq!(mesh.num_triangles(), 3);
assert_eq!(mesh.triangulation()[0], [0, 1, 2]);
assert_eq!(mesh.triangulation()[1], [0, 2, 3]);
assert_eq!(mesh.triangulation()[2], [0, 3, 4]);
assert_eq!(mesh.num_edges(), 5);
}
#[test]
fn test_shared_edges() {
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, -1.0, 0.0),
];
let faces = vec![vec![0, 1, 2], vec![0, 3, 1]];
let mesh = SurfaceMesh::new("test_shared", vertices, faces);
assert_eq!(mesh.num_triangles(), 2);
assert_eq!(mesh.num_edges(), 5);
}
#[test]
fn test_render_options() {
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),
];
let faces = vec![vec![0, 1, 2]];
let mut mesh = SurfaceMesh::new("test_options", vertices, faces);
assert_eq!(mesh.shade_style(), ShadeStyle::Smooth);
assert_eq!(mesh.backface_policy(), BackfacePolicy::Identical);
assert!(!mesh.show_edges());
assert_eq!(mesh.transparency(), 0.0);
mesh.set_shade_style(ShadeStyle::Flat);
assert_eq!(mesh.shade_style(), ShadeStyle::Flat);
mesh.set_backface_policy(BackfacePolicy::Cull);
assert_eq!(mesh.backface_policy(), BackfacePolicy::Cull);
mesh.set_show_edges(true);
assert!(mesh.show_edges());
mesh.set_edge_width(2.0);
assert_eq!(mesh.edge_width(), 2.0);
mesh.set_edge_color(Vec3::new(1.0, 0.0, 0.0));
assert_eq!(mesh.edge_color(), Vec4::new(1.0, 0.0, 0.0, 1.0));
mesh.set_surface_color(Vec3::new(0.0, 1.0, 0.0));
assert_eq!(mesh.surface_color(), Vec4::new(0.0, 1.0, 0.0, 1.0));
mesh.set_backface_color(Vec3::new(0.0, 0.0, 1.0));
assert_eq!(mesh.backface_color(), Vec4::new(0.0, 0.0, 1.0, 1.0));
mesh.set_transparency(0.5);
assert_eq!(mesh.transparency(), 0.5);
mesh.set_transparency(1.5);
assert_eq!(mesh.transparency(), 1.0);
mesh.set_transparency(-0.5);
assert_eq!(mesh.transparency(), 0.0);
}
#[test]
fn test_surface_mesh_quantities() {
use polyscope_core::quantity::QuantityKind;
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
];
let faces = vec![vec![0, 1, 2]];
let mut mesh = SurfaceMesh::new("test", vertices, faces);
mesh.add_vertex_scalar_quantity("height", vec![0.0, 0.5, 1.0]);
mesh.add_vertex_color_quantity("colors", vec![Vec3::X, Vec3::Y, Vec3::Z]);
mesh.add_vertex_vector_quantity("normals", vec![Vec3::Z, Vec3::Z, Vec3::Z]);
mesh.add_face_scalar_quantity("area", vec![1.0]);
mesh.add_face_color_quantity("face_colors", vec![Vec3::new(1.0, 0.0, 0.0)]);
mesh.add_face_vector_quantity("face_normals", vec![Vec3::Z]);
let cases: &[(&str, usize, QuantityKind)] = &[
("height", 3, QuantityKind::Scalar),
("colors", 3, QuantityKind::Color),
("normals", 3, QuantityKind::Vector),
("area", 1, QuantityKind::Scalar),
("face_colors", 1, QuantityKind::Color),
("face_normals", 1, QuantityKind::Vector),
];
for (name, expected_size, expected_kind) in cases {
let q = mesh
.get_quantity(name)
.unwrap_or_else(|| panic!("quantity '{name}' not found"));
assert_eq!(
q.data_size(),
*expected_size,
"data_size mismatch for {name}"
);
assert_eq!(q.kind(), *expected_kind, "kind mismatch for {name}");
}
}
#[test]
fn test_face_scalar_compute_vertex_colors() {
let fsq = MeshFaceScalarQuantity::new("test", "mesh", vec![0.0, 1.0]);
let faces = vec![vec![0, 1, 2], vec![2, 1, 3]];
let colormap = polyscope_render::ColorMap::new("test", vec![Vec3::ZERO, Vec3::ONE]);
let colors = fsq.compute_vertex_colors(&faces, 4, &colormap);
assert_eq!(colors.len(), 4);
assert!((colors[0] - Vec4::new(0.0, 0.0, 0.0, 1.0)).length() < 1e-5);
assert!((colors[3] - Vec4::new(1.0, 1.0, 1.0, 1.0)).length() < 1e-5);
}
#[test]
fn test_face_color_compute_vertex_colors() {
let fcq = MeshFaceColorQuantity::new(
"test",
"mesh",
vec![Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0)],
);
let faces = vec![vec![0, 1, 2], vec![2, 1, 3]];
let colors = fcq.compute_vertex_colors(&faces, 4);
assert_eq!(colors.len(), 4);
assert_eq!(colors[0], Vec4::new(1.0, 0.0, 0.0, 1.0));
assert_eq!(colors[3], Vec4::new(0.0, 1.0, 0.0, 1.0));
}
#[test]
fn test_edge_is_real_triangle() {
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),
];
let faces = vec![vec![0, 1, 2]];
let mesh = SurfaceMesh::new("test_tri", vertices, faces);
assert_eq!(mesh.edge_is_real().len(), 3);
let expected = Vec3::new(1.0, 1.0, 1.0);
for edge_real in mesh.edge_is_real() {
assert_eq!(*edge_real, expected);
}
}
#[test]
fn test_edge_is_real_quad() {
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),
];
let faces = vec![vec![0, 1, 2, 3]];
let mesh = SurfaceMesh::new("test_quad", vertices, faces);
assert_eq!(mesh.edge_is_real().len(), 6);
let tri0_expected = Vec3::new(1.0, 1.0, 0.0);
let tri1_expected = Vec3::new(0.0, 1.0, 1.0);
assert_eq!(mesh.edge_is_real()[0], tri0_expected);
assert_eq!(mesh.edge_is_real()[1], tri0_expected);
assert_eq!(mesh.edge_is_real()[2], tri0_expected);
assert_eq!(mesh.edge_is_real()[3], tri1_expected);
assert_eq!(mesh.edge_is_real()[4], tri1_expected);
assert_eq!(mesh.edge_is_real()[5], tri1_expected);
}
#[test]
fn test_edge_is_real_pentagon() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(1.5, 0.5, 0.0),
Vec3::new(0.75, 1.0, 0.0),
Vec3::new(-0.25, 0.5, 0.0),
];
let faces = vec![vec![0, 1, 2, 3, 4]];
let mesh = SurfaceMesh::new("test_pentagon", vertices, faces);
assert_eq!(mesh.edge_is_real().len(), 9);
let tri0_expected = Vec3::new(1.0, 1.0, 0.0);
let tri1_expected = Vec3::new(0.0, 1.0, 0.0);
let tri2_expected = Vec3::new(0.0, 1.0, 1.0);
assert_eq!(mesh.edge_is_real()[0], tri0_expected);
assert_eq!(mesh.edge_is_real()[1], tri0_expected);
assert_eq!(mesh.edge_is_real()[2], tri0_expected);
assert_eq!(mesh.edge_is_real()[3], tri1_expected);
assert_eq!(mesh.edge_is_real()[4], tri1_expected);
assert_eq!(mesh.edge_is_real()[5], tri1_expected);
assert_eq!(mesh.edge_is_real()[6], tri2_expected);
assert_eq!(mesh.edge_is_real()[7], tri2_expected);
assert_eq!(mesh.edge_is_real()[8], tri2_expected);
}
}