use glam::{Vec3, Vec4};
use polyscope_core::quantity::{EdgeQuantity, Quantity, QuantityKind};
use polyscope_render::{VectorRenderData, VectorUniforms};
pub struct MeshOneFormQuantity {
name: String,
structure_name: String,
values: Vec<f32>, orientations: Vec<bool>, enabled: bool,
length_scale: f32,
radius: f32,
color: Vec4,
render_data: Option<VectorRenderData>,
}
impl MeshOneFormQuantity {
pub fn new(
name: impl Into<String>,
structure_name: impl Into<String>,
values: Vec<f32>,
orientations: Vec<bool>,
) -> Self {
Self {
name: name.into(),
structure_name: structure_name.into(),
values,
orientations,
enabled: false,
length_scale: 1.0,
radius: 0.005,
color: Vec4::new(0.2, 0.7, 0.2, 1.0),
render_data: None,
}
}
#[must_use]
pub fn values(&self) -> &[f32] {
&self.values
}
#[must_use]
pub fn orientations(&self) -> &[bool] {
&self.orientations
}
#[must_use]
pub fn length_scale(&self) -> f32 {
self.length_scale
}
pub fn set_length_scale(&mut self, scale: f32) -> &mut Self {
self.length_scale = scale;
self
}
#[must_use]
pub fn radius(&self) -> f32 {
self.radius
}
pub fn set_radius(&mut self, r: f32) -> &mut Self {
self.radius = r;
self
}
#[must_use]
pub fn color(&self) -> Vec4 {
self.color
}
pub fn set_color(&mut self, c: Vec3) -> &mut Self {
self.color = c.extend(1.0);
self
}
#[must_use]
pub fn compute_edge_vectors(
&self,
vertices: &[Vec3],
edges: &[(u32, u32)],
) -> (Vec<Vec3>, Vec<Vec3>) {
let mut positions = Vec::with_capacity(self.values.len());
let mut vectors = Vec::with_capacity(self.values.len());
for (i, &(v0_idx, v1_idx)) in edges.iter().enumerate() {
if i >= self.values.len() {
break;
}
let v0 = vertices[v0_idx as usize];
let v1 = vertices[v1_idx as usize];
let midpoint = (v0 + v1) * 0.5;
let mut direction = (v1 - v0).normalize_or_zero();
if !self.orientations[i] {
direction = -direction;
}
let vector = direction * self.values[i] * self.length_scale;
positions.push(midpoint);
vectors.push(vector);
}
(positions, vectors)
}
pub fn auto_scale(
&mut self,
structure_length_scale: f32,
vertices: &[Vec3],
edges: &[(u32, u32)],
) {
let (_positions, vecs) = self.compute_edge_vectors(vertices, edges);
let avg_length: f32 = if vecs.is_empty() {
1.0
} else {
let sum: f32 = vecs.iter().map(|v| v.length()).sum();
sum / vecs.len() as f32
};
if avg_length > 1e-8 {
self.length_scale = 0.02 * structure_length_scale / avg_length;
}
self.radius = 0.002 * structure_length_scale;
}
pub fn init_gpu_resources(
&mut self,
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
camera_buffer: &wgpu::Buffer,
vertices: &[Vec3],
edges: &[(u32, u32)],
) {
let (positions, vectors) = self.compute_edge_vectors(vertices, edges);
self.render_data = Some(VectorRenderData::new(
device,
bind_group_layout,
camera_buffer,
&positions,
&vectors,
));
}
#[must_use]
pub fn render_data(&self) -> Option<&VectorRenderData> {
self.render_data.as_ref()
}
pub fn update_uniforms(&self, queue: &wgpu::Queue, model: &glam::Mat4) {
if let Some(render_data) = &self.render_data {
let uniforms = VectorUniforms {
model: model.to_cols_array(),
length_scale: self.length_scale,
radius: self.radius,
_padding: [0.0; 2],
color: self.color.to_array(),
};
render_data.update_uniforms(queue, &uniforms);
}
}
pub fn build_egui_ui(&mut self, ui: &mut egui::Ui) -> bool {
let mut color = [self.color.x, self.color.y, self.color.z];
let changed = polyscope_ui::build_vector_quantity_ui(
ui,
&self.name,
&mut self.enabled,
&mut self.length_scale,
&mut self.radius,
&mut color,
);
if changed {
self.color = Vec4::new(color[0], color[1], color[2], self.color.w);
}
changed
}
}
impl Quantity for MeshOneFormQuantity {
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 structure_name(&self) -> &str {
&self.structure_name
}
fn kind(&self) -> QuantityKind {
QuantityKind::Vector
}
fn is_enabled(&self) -> bool {
self.enabled
}
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn build_ui(&mut self, _ui: &dyn std::any::Any) {}
fn refresh(&mut self) {}
fn clear_gpu_resources(&mut self) {
self.render_data = None;
}
fn data_size(&self) -> usize {
self.values.len()
}
}
impl EdgeQuantity for MeshOneFormQuantity {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_one_form_creation() {
let values = vec![1.0, -0.5, 0.3];
let orientations = vec![true, true, false];
let q = MeshOneFormQuantity::new("flow", "mesh", values, orientations);
assert_eq!(q.name(), "flow");
assert_eq!(q.structure_name(), "mesh");
assert_eq!(q.data_size(), 3);
assert_eq!(q.kind(), QuantityKind::Vector);
assert!(!q.is_enabled());
}
#[test]
fn test_edge_vector_computation() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0), Vec3::new(2.0, 0.0, 0.0), Vec3::new(1.0, 2.0, 0.0), ];
let edges: Vec<(u32, u32)> = vec![(0, 1), (0, 2), (1, 2)];
let values = vec![1.0, 0.5, -0.5];
let orientations = vec![true, true, true];
let q = MeshOneFormQuantity::new("test", "mesh", values, orientations);
let (positions, vectors) = q.compute_edge_vectors(&vertices, &edges);
assert_eq!(positions.len(), 3);
assert_eq!(vectors.len(), 3);
assert!((positions[0] - Vec3::new(1.0, 0.0, 0.0)).length() < 1e-5);
assert!((vectors[0] - Vec3::new(1.0, 0.0, 0.0)).length() < 1e-5);
assert!((positions[1] - Vec3::new(0.5, 1.0, 0.0)).length() < 1e-5);
}
#[test]
fn test_edge_vector_orientation_flip() {
let vertices = vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(2.0, 0.0, 0.0)];
let edges: Vec<(u32, u32)> = vec![(0, 1)];
let values = vec![1.0];
let orientations = vec![false];
let q = MeshOneFormQuantity::new("test", "mesh", values, orientations);
let (positions, vectors) = q.compute_edge_vectors(&vertices, &edges);
assert!((positions[0] - Vec3::new(1.0, 0.0, 0.0)).length() < 1e-5);
assert!((vectors[0] - Vec3::new(-1.0, 0.0, 0.0)).length() < 1e-5);
}
#[test]
fn test_edge_vector_negative_value() {
let vertices = vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(2.0, 0.0, 0.0)];
let edges: Vec<(u32, u32)> = vec![(0, 1)];
let values = vec![-1.0];
let orientations = vec![true];
let q = MeshOneFormQuantity::new("test", "mesh", values, orientations);
let (_positions, vectors) = q.compute_edge_vectors(&vertices, &edges);
assert!((vectors[0] - Vec3::new(-1.0, 0.0, 0.0)).length() < 1e-5);
}
}