1use crate::core::{AlphaMode, BoundingBox, DrawCall, Material, PipelineType, RenderData, Vertex};
4use glam::{Vec3, Vec4};
5use std::borrow::Cow;
6use std::collections::{BTreeMap, BTreeSet};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct MeshTriangleRange {
10 pub start: u32,
11 pub count: u32,
12}
13
14impl MeshTriangleRange {
15 pub fn new(start: u32, count: u32) -> Self {
16 Self { start, count }
17 }
18
19 pub fn end_exclusive(&self) -> Option<u32> {
20 self.start.checked_add(self.count)
21 }
22
23 pub fn contains(&self, triangle_index: u32) -> bool {
24 self.end_exclusive()
25 .is_some_and(|end| triangle_index >= self.start && triangle_index < end)
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct MeshRegion {
31 pub region_id: String,
32 pub label: Option<String>,
33 pub tag: Option<String>,
34 pub triangle_ranges: Vec<MeshTriangleRange>,
35}
36
37impl MeshRegion {
38 pub fn new(
39 region_id: impl Into<String>,
40 label: Option<String>,
41 tag: Option<String>,
42 triangle_ranges: Vec<MeshTriangleRange>,
43 ) -> Self {
44 Self {
45 region_id: region_id.into(),
46 label,
47 tag,
48 triangle_ranges,
49 }
50 }
51
52 pub fn contains_triangle(&self, triangle_index: u32) -> bool {
53 self.triangle_ranges
54 .iter()
55 .any(|range| range.contains(triangle_index))
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum MeshFieldLocation {
61 Vertex,
62 Triangle,
63}
64
65impl MeshFieldLocation {
66 pub fn as_str(self) -> &'static str {
67 match self {
68 Self::Vertex => "vertex",
69 Self::Triangle => "triangle",
70 }
71 }
72
73 pub fn parse(value: &str) -> Option<Self> {
74 match value {
75 "vertex" | "vertices" | "node" | "nodes" => Some(Self::Vertex),
76 "triangle" | "triangles" | "face" | "faces" | "element" | "elements" => {
77 Some(Self::Triangle)
78 }
79 _ => None,
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
85pub enum MeshEdgeMode {
86 #[default]
87 All,
88 Feature,
89 None,
90}
91
92impl MeshEdgeMode {
93 pub fn as_str(self) -> &'static str {
94 match self {
95 Self::All => "all",
96 Self::Feature => "feature",
97 Self::None => "none",
98 }
99 }
100
101 pub fn parse(value: &str) -> Option<Self> {
102 match value {
103 "all" | "triangle" | "triangles" => Some(Self::All),
104 "feature" | "features" | "boundary" | "boundaries" | "region" | "regions" => {
105 Some(Self::Feature)
106 }
107 "none" | "off" => Some(Self::None),
108 _ => None,
109 }
110 }
111}
112
113#[derive(Debug, Clone, PartialEq)]
114pub struct MeshScalarField {
115 pub field_id: String,
116 pub label: Option<String>,
117 pub location: MeshFieldLocation,
118 pub values: Vec<f32>,
119 pub color_limits: Option<[f32; 2]>,
120 pub colormap: String,
121 pub alpha: f32,
122}
123
124impl MeshScalarField {
125 pub fn new(field_id: impl Into<String>, location: MeshFieldLocation, values: Vec<f32>) -> Self {
126 Self {
127 field_id: field_id.into(),
128 label: None,
129 location,
130 values,
131 color_limits: None,
132 colormap: "viridis".to_string(),
133 alpha: 1.0,
134 }
135 }
136}
137
138#[derive(Debug, Clone, PartialEq)]
139pub struct MeshVectorField {
140 pub field_id: String,
141 pub label: Option<String>,
142 pub location: MeshFieldLocation,
143 pub vectors: Vec<Vec3>,
144 pub scale: f32,
145 pub stride: usize,
146 pub color: Vec4,
147}
148
149impl MeshVectorField {
150 pub fn new(
151 field_id: impl Into<String>,
152 location: MeshFieldLocation,
153 vectors: Vec<Vec3>,
154 ) -> Self {
155 Self {
156 field_id: field_id.into(),
157 label: None,
158 location,
159 vectors,
160 scale: 1.0,
161 stride: 1,
162 color: Vec4::new(0.95, 0.86, 0.28, 1.0),
163 }
164 }
165}
166
167#[derive(Debug, Clone, PartialEq)]
168pub struct MeshDeformation {
169 pub field_id: String,
170 pub label: Option<String>,
171 pub displacements: Vec<Vec3>,
172 pub scale: f32,
173}
174
175impl MeshDeformation {
176 pub fn new(field_id: impl Into<String>, displacements: Vec<Vec3>) -> Self {
177 Self {
178 field_id: field_id.into(),
179 label: None,
180 displacements,
181 scale: 1.0,
182 }
183 }
184}
185
186#[derive(Debug, Clone)]
187pub struct MeshPlot {
188 mesh_id: Option<String>,
189 vertices: Vec<Vec3>,
190 triangles: Vec<[u32; 3]>,
191 face_color: Vec4,
192 edge_color: Vec4,
193 face_alpha: f32,
194 edge_alpha: f32,
195 edge_width: f32,
196 edge_mode: MeshEdgeMode,
197 feature_edge_groups: Option<Vec<u64>>,
198 vertex_colors: Option<Vec<Vec4>>,
199 triangle_colors: Option<Vec<Vec4>>,
200 label: Option<String>,
201 regions: Vec<MeshRegion>,
202 highlighted_region_id: Option<String>,
203 highlight_color: Vec4,
204 scalar_field: Option<MeshScalarField>,
205 vector_field: Option<MeshVectorField>,
206 deformation: Option<MeshDeformation>,
207 visible: bool,
208 bounds: Option<BoundingBox>,
209 face_vertices: Option<Vec<Vertex>>,
210 face_indices: Option<Vec<u32>>,
211 edge_vertices: Option<Vec<Vertex>>,
212 vector_vertices: Option<Vec<Vertex>>,
213 dirty: bool,
214}
215
216impl MeshPlot {
217 pub fn new(vertices: Vec<Vec3>, triangles: Vec<[u32; 3]>) -> Result<Self, String> {
218 validate_mesh(&vertices, &triangles)?;
219 Ok(Self {
220 mesh_id: None,
221 vertices,
222 triangles,
223 face_color: Vec4::new(0.18, 0.48, 0.86, 1.0),
224 edge_color: Vec4::new(0.78, 0.86, 0.96, 1.0),
225 face_alpha: 1.0,
226 edge_alpha: 0.65,
227 edge_width: 0.5,
228 edge_mode: MeshEdgeMode::All,
229 feature_edge_groups: None,
230 vertex_colors: None,
231 triangle_colors: None,
232 label: None,
233 regions: Vec::new(),
234 highlighted_region_id: None,
235 highlight_color: Vec4::new(0.98, 0.78, 0.22, 1.0),
236 scalar_field: None,
237 vector_field: None,
238 deformation: None,
239 visible: true,
240 bounds: None,
241 face_vertices: None,
242 face_indices: None,
243 edge_vertices: None,
244 vector_vertices: None,
245 dirty: true,
246 })
247 }
248
249 pub fn mesh_id(&self) -> Option<&str> {
250 self.mesh_id.as_deref()
251 }
252
253 pub fn vertices(&self) -> &[Vec3] {
254 &self.vertices
255 }
256
257 pub fn triangles(&self) -> &[[u32; 3]] {
258 &self.triangles
259 }
260
261 pub fn face_color(&self) -> Vec4 {
262 self.face_color
263 }
264
265 pub fn edge_color(&self) -> Vec4 {
266 self.edge_color
267 }
268
269 pub fn face_alpha(&self) -> f32 {
270 self.face_alpha
271 }
272
273 pub fn edge_alpha(&self) -> f32 {
274 self.edge_alpha
275 }
276
277 pub fn edge_width(&self) -> f32 {
278 self.edge_width
279 }
280
281 pub fn edge_mode(&self) -> MeshEdgeMode {
282 self.edge_mode
283 }
284
285 pub fn feature_edge_groups(&self) -> Option<&[u64]> {
286 self.feature_edge_groups.as_deref()
287 }
288
289 pub fn triangle_colors(&self) -> Option<&[Vec4]> {
290 self.triangle_colors.as_deref()
291 }
292
293 pub fn vertex_colors(&self) -> Option<&[Vec4]> {
294 self.vertex_colors.as_deref()
295 }
296
297 pub fn label(&self) -> Option<&str> {
298 self.label.as_deref()
299 }
300
301 pub fn regions(&self) -> &[MeshRegion] {
302 &self.regions
303 }
304
305 pub fn highlighted_region_id(&self) -> Option<&str> {
306 self.highlighted_region_id.as_deref()
307 }
308
309 pub fn highlight_color(&self) -> Vec4 {
310 self.highlight_color
311 }
312
313 pub fn scalar_field(&self) -> Option<&MeshScalarField> {
314 self.scalar_field.as_ref()
315 }
316
317 pub fn vector_field(&self) -> Option<&MeshVectorField> {
318 self.vector_field.as_ref()
319 }
320
321 pub fn deformation(&self) -> Option<&MeshDeformation> {
322 self.deformation.as_ref()
323 }
324
325 pub fn is_visible(&self) -> bool {
326 self.visible
327 }
328
329 pub fn set_mesh_id(&mut self, mesh_id: Option<String>) {
330 self.mesh_id = mesh_id;
331 }
332
333 pub fn set_vertices(&mut self, vertices: Vec<Vec3>) -> Result<(), String> {
334 validate_mesh(&vertices, &self.triangles)?;
335 self.vertices = vertices;
336 self.clear_invalid_vertex_metadata();
337 self.mark_dirty();
338 Ok(())
339 }
340
341 pub fn set_triangles(&mut self, triangles: Vec<[u32; 3]>) -> Result<(), String> {
342 validate_mesh(&self.vertices, &triangles)?;
343 self.triangles = triangles;
344 self.clear_invalid_triangle_metadata();
345 self.mark_dirty();
346 Ok(())
347 }
348
349 pub fn set_face_color(&mut self, color: Vec4) {
350 self.face_color = sanitize_color(color);
351 self.mark_dirty();
352 }
353
354 pub fn set_edge_color(&mut self, color: Vec4) {
355 self.edge_color = sanitize_color(color);
356 self.mark_dirty();
357 }
358
359 pub fn set_face_alpha(&mut self, alpha: f32) {
360 self.face_alpha = sanitize_alpha(alpha);
361 self.mark_dirty();
362 }
363
364 pub fn set_edge_alpha(&mut self, alpha: f32) {
365 self.edge_alpha = sanitize_alpha(alpha);
366 self.mark_dirty();
367 }
368
369 pub fn set_edge_width(&mut self, edge_width: f32) {
370 self.edge_width = if edge_width.is_finite() {
371 edge_width.max(0.0)
372 } else {
373 0.0
374 };
375 self.mark_dirty();
376 }
377
378 pub fn set_edge_mode(&mut self, edge_mode: MeshEdgeMode) {
379 self.edge_mode = edge_mode;
380 self.mark_dirty();
381 }
382
383 pub fn set_feature_edge_groups(&mut self, groups: Option<Vec<u64>>) -> Result<(), String> {
384 if let Some(groups) = groups.as_ref() {
385 if groups.len() != self.triangles.len() {
386 return Err(format!(
387 "mesh feature edge groups: group count must match triangle count ({})",
388 self.triangles.len()
389 ));
390 }
391 }
392 self.feature_edge_groups = groups;
393 self.mark_dirty();
394 Ok(())
395 }
396
397 pub fn set_vertex_colors(&mut self, colors: Option<Vec<Vec4>>) -> Result<(), String> {
398 if let Some(colors) = colors.as_ref() {
399 if colors.len() != self.vertices.len() {
400 return Err(format!(
401 "mesh vertex colors: color count must match vertex count ({})",
402 self.vertices.len()
403 ));
404 }
405 }
406 self.vertex_colors = colors.map(|colors| colors.into_iter().map(sanitize_color).collect());
407 self.mark_dirty();
408 Ok(())
409 }
410
411 pub fn set_triangle_colors(&mut self, colors: Option<Vec<Vec4>>) -> Result<(), String> {
412 if let Some(colors) = colors.as_ref() {
413 if colors.len() != self.triangles.len() {
414 return Err(format!(
415 "mesh triangle colors: color count must match triangle count ({})",
416 self.triangles.len()
417 ));
418 }
419 }
420 self.triangle_colors =
421 colors.map(|colors| colors.into_iter().map(sanitize_color).collect());
422 self.mark_dirty();
423 Ok(())
424 }
425
426 pub fn set_label(&mut self, label: Option<String>) {
427 self.label = label;
428 }
429
430 pub fn set_regions(&mut self, regions: Vec<MeshRegion>) {
431 self.regions = regions;
432 self.mark_dirty();
433 }
434
435 pub fn set_highlighted_region_id(&mut self, region_id: Option<String>) {
436 self.highlighted_region_id = region_id;
437 self.mark_dirty();
438 }
439
440 pub fn set_highlight_color(&mut self, color: Vec4) {
441 self.highlight_color = sanitize_color(color);
442 self.mark_dirty();
443 }
444
445 pub fn set_scalar_field(&mut self, field: Option<MeshScalarField>) -> Result<(), String> {
446 if let Some(field) = field.as_ref() {
447 validate_scalar_field(field, self.vertices.len(), self.triangles.len())?;
448 }
449 self.scalar_field = field.map(sanitize_scalar_field);
450 self.mark_dirty();
451 Ok(())
452 }
453
454 pub fn set_vector_field(&mut self, field: Option<MeshVectorField>) -> Result<(), String> {
455 if let Some(field) = field.as_ref() {
456 validate_vector_field(field, self.vertices.len(), self.triangles.len())?;
457 }
458 self.vector_field = field.map(sanitize_vector_field);
459 self.mark_dirty();
460 Ok(())
461 }
462
463 pub fn set_deformation(&mut self, deformation: Option<MeshDeformation>) -> Result<(), String> {
464 if let Some(deformation) = deformation.as_ref() {
465 validate_deformation(deformation, self.vertices.len())?;
466 }
467 self.deformation = deformation.map(sanitize_deformation);
468 self.mark_dirty();
469 Ok(())
470 }
471
472 pub fn region_for_triangle(&self, triangle_index: u32) -> Option<&MeshRegion> {
473 self.regions
474 .iter()
475 .find(|region| region.contains_triangle(triangle_index))
476 }
477
478 pub fn set_visible(&mut self, visible: bool) {
479 self.visible = visible;
480 }
481
482 pub fn mark_dirty(&mut self) {
483 self.dirty = true;
484 self.bounds = None;
485 self.face_vertices = None;
486 self.face_indices = None;
487 self.edge_vertices = None;
488 self.vector_vertices = None;
489 }
490
491 pub fn effective_face_color(&self) -> Vec4 {
492 let mut color = self.face_color;
493 color.w *= self.face_alpha.clamp(0.0, 1.0);
494 color
495 }
496
497 pub fn effective_edge_color(&self) -> Vec4 {
498 let mut color = self.edge_color;
499 color.w *= self.edge_alpha.clamp(0.0, 1.0);
500 color
501 }
502
503 pub fn bounds(&mut self) -> BoundingBox {
504 if self.dirty || self.bounds.is_none() {
505 let vertices = self.effective_vertices();
506 self.bounds = Some(BoundingBox::from_points(&vertices));
507 }
508 self.bounds.unwrap()
509 }
510
511 pub fn render_data(&mut self) -> RenderData {
512 let bounds = self.bounds();
513 let (vertices, indices) = {
514 let (vertices, indices) = self.generate_face_geometry();
515 (vertices.clone(), indices.clone())
516 };
517 let color = self.effective_face_color();
518 let vertex_count = vertices.len();
519 let index_count = indices.len();
520 RenderData {
521 pipeline_type: PipelineType::Triangles,
522 vertices,
523 indices: Some(indices),
524 gpu_vertices: None,
525 bounds: Some(bounds),
526 material: Material {
527 albedo: color,
528 alpha_mode: if color.w < 1.0 {
529 AlphaMode::Blend
530 } else {
531 AlphaMode::Opaque
532 },
533 double_sided: true,
534 ..Default::default()
535 },
536 draw_calls: vec![DrawCall {
537 vertex_offset: 0,
538 vertex_count,
539 index_offset: Some(0),
540 index_count: Some(index_count),
541 instance_count: 1,
542 }],
543 image: None,
544 }
545 }
546
547 pub fn edge_render_data(&mut self) -> Option<RenderData> {
548 let bounds = self.bounds();
549 if self.edge_width <= 0.0
550 || self.edge_alpha <= 0.0
551 || matches!(self.edge_mode, MeshEdgeMode::None)
552 {
553 return None;
554 }
555 if self.dirty || self.edge_vertices.is_none() {
556 let color = self.effective_edge_color();
557 let vertices_source = self.effective_vertices();
558 let edges = self.renderable_edges();
559 let mut vertices = Vec::with_capacity(edges.len() * 2);
560 for (a, b) in edges {
561 vertices.push(Vertex::new(vertices_source[a as usize], color));
562 vertices.push(Vertex::new(vertices_source[b as usize], color));
563 }
564 self.edge_vertices = Some(vertices);
565 }
566 let vertices = self.edge_vertices.as_ref()?.clone();
567 if vertices.is_empty() {
568 return None;
569 }
570 let vertex_count = vertices.len();
571 Some(RenderData {
572 pipeline_type: PipelineType::Lines,
573 vertices,
574 indices: None,
575 gpu_vertices: None,
576 bounds: Some(bounds),
577 material: Material {
578 albedo: self.effective_edge_color(),
579 roughness: self.edge_width.max(0.1),
580 alpha_mode: if self.edge_alpha < 1.0 {
581 AlphaMode::Blend
582 } else {
583 AlphaMode::Opaque
584 },
585 ..Default::default()
586 },
587 draw_calls: vec![DrawCall {
588 vertex_offset: 0,
589 vertex_count,
590 index_offset: None,
591 index_count: None,
592 instance_count: 1,
593 }],
594 image: None,
595 })
596 }
597
598 pub fn vector_render_data(&mut self) -> Option<RenderData> {
599 let bounds = self.bounds();
600 if self.dirty || self.vector_vertices.is_none() {
601 let vertices_source = self.effective_vertices();
602 let field = self.vector_field.as_ref()?;
603 let color = sanitize_color(field.color);
604 let stride = field.stride.max(1);
605 let mut vertices = Vec::new();
606 match field.location {
607 MeshFieldLocation::Vertex => {
608 for (index, vector) in field.vectors.iter().enumerate().step_by(stride) {
609 if vector.length_squared() <= f32::EPSILON {
610 continue;
611 }
612 let start = vertices_source[index];
613 vertices.push(Vertex::new(start, color));
614 vertices.push(Vertex::new(start + *vector * field.scale, color));
615 }
616 }
617 MeshFieldLocation::Triangle => {
618 for (index, vector) in field.vectors.iter().enumerate().step_by(stride) {
619 if vector.length_squared() <= f32::EPSILON {
620 continue;
621 }
622 let triangle = self.triangles[index];
623 let start = (vertices_source[triangle[0] as usize]
624 + vertices_source[triangle[1] as usize]
625 + vertices_source[triangle[2] as usize])
626 / 3.0;
627 vertices.push(Vertex::new(start, color));
628 vertices.push(Vertex::new(start + *vector * field.scale, color));
629 }
630 }
631 }
632 self.vector_vertices = Some(vertices);
633 }
634 let vertices = self.vector_vertices.as_ref()?.clone();
635 if vertices.is_empty() {
636 return None;
637 }
638 let vertex_count = vertices.len();
639 Some(RenderData {
640 pipeline_type: PipelineType::Lines,
641 vertices,
642 indices: None,
643 gpu_vertices: None,
644 bounds: Some(bounds),
645 material: Material {
646 albedo: self
647 .vector_field
648 .as_ref()
649 .map(|field| sanitize_color(field.color))
650 .unwrap_or(Vec4::ONE),
651 alpha_mode: if self
652 .vector_field
653 .as_ref()
654 .map(|field| field.color.w < 1.0)
655 .unwrap_or(false)
656 {
657 AlphaMode::Blend
658 } else {
659 AlphaMode::Opaque
660 },
661 ..Default::default()
662 },
663 draw_calls: vec![DrawCall {
664 vertex_offset: 0,
665 vertex_count,
666 index_offset: None,
667 index_count: None,
668 instance_count: 1,
669 }],
670 image: None,
671 })
672 }
673
674 pub fn estimated_memory_usage(&self) -> usize {
675 self.vertices.len() * std::mem::size_of::<Vec3>()
676 + self.triangles.len() * std::mem::size_of::<[u32; 3]>()
677 + self
678 .face_vertices
679 .as_ref()
680 .map_or(0, |vertices| vertices.len() * std::mem::size_of::<Vertex>())
681 + self
682 .face_indices
683 .as_ref()
684 .map_or(0, |indices| indices.len() * std::mem::size_of::<u32>())
685 + self
686 .edge_vertices
687 .as_ref()
688 .map_or(0, |vertices| vertices.len() * std::mem::size_of::<Vertex>())
689 + self
690 .feature_edge_groups
691 .as_ref()
692 .map_or(0, |groups| groups.len() * std::mem::size_of::<u64>())
693 + self
694 .vertex_colors
695 .as_ref()
696 .map_or(0, |colors| colors.len() * std::mem::size_of::<Vec4>())
697 + self
698 .triangle_colors
699 .as_ref()
700 .map_or(0, |colors| colors.len() * std::mem::size_of::<Vec4>())
701 + self
702 .vector_vertices
703 .as_ref()
704 .map_or(0, |vertices| vertices.len() * std::mem::size_of::<Vertex>())
705 + self
706 .scalar_field
707 .as_ref()
708 .map_or(0, |field| field.values.len() * std::mem::size_of::<f32>())
709 + self
710 .vector_field
711 .as_ref()
712 .map_or(0, |field| field.vectors.len() * std::mem::size_of::<Vec3>())
713 + self.deformation.as_ref().map_or(0, |field| {
714 field.displacements.len() * std::mem::size_of::<Vec3>()
715 })
716 }
717
718 fn generate_face_geometry(&mut self) -> (&Vec<Vertex>, &Vec<u32>) {
719 if self.dirty || self.face_vertices.is_none() || self.face_indices.is_none() {
720 let color = self.effective_face_color();
721 let vertices_source = self.effective_vertices();
722 let normals = vertex_normals(&vertices_source, &self.triangles);
723 let scalar_limits = self.scalar_field.as_ref().and_then(scalar_limits);
724 let (vertices, indices) = if self.highlighted_region_id.is_some()
725 || self
726 .scalar_field
727 .as_ref()
728 .is_some_and(|field| field.location == MeshFieldLocation::Triangle)
729 || self.triangle_colors.is_some()
730 {
731 let highlight_color = self.highlight_color;
732 let highlighted_region_id = self.highlighted_region_id.as_deref();
733 let highlighted_region = self
734 .regions
735 .iter()
736 .find(|region| Some(region.region_id.as_str()) == highlighted_region_id);
737 let mut vertices = Vec::with_capacity(self.triangles.len() * 3);
738 let mut indices = Vec::with_capacity(self.triangles.len() * 3);
739 for (triangle_index, triangle) in self.triangles.iter().enumerate() {
740 let region_highlighted = if highlighted_region
741 .is_some_and(|region| region.contains_triangle(triangle_index as u32))
742 {
743 Some(highlight_color)
744 } else {
745 None
746 };
747 for vertex_id in triangle {
748 let source_index = *vertex_id as usize;
749 let target_index = vertices.len() as u32;
750 let vertex_color = region_highlighted.unwrap_or_else(|| {
751 self.scalar_color(source_index, triangle_index, scalar_limits)
752 .or_else(|| self.triangle_color(triangle_index))
753 .or_else(|| self.vertex_color(source_index))
754 .unwrap_or(color)
755 });
756 vertices.push(Vertex {
757 position: vertices_source[source_index].to_array(),
758 color: vertex_color.to_array(),
759 normal: normals[source_index].to_array(),
760 tex_coords: [0.0, 0.0],
761 });
762 indices.push(target_index);
763 }
764 }
765 (vertices, indices)
766 } else {
767 let vertices = vertices_source
768 .iter()
769 .zip(normals.iter())
770 .enumerate()
771 .map(|(source_index, (&position, &normal))| {
772 let vertex_color = self
773 .scalar_color(source_index, 0, scalar_limits)
774 .or_else(|| self.vertex_color(source_index))
775 .unwrap_or(color);
776 Vertex {
777 position: position.to_array(),
778 color: vertex_color.to_array(),
779 normal: normal.to_array(),
780 tex_coords: [0.0, 0.0],
781 }
782 })
783 .collect();
784 let indices = self
785 .triangles
786 .iter()
787 .flat_map(|triangle| triangle.iter().copied())
788 .collect();
789 (vertices, indices)
790 };
791 self.face_vertices = Some(vertices);
792 self.face_indices = Some(indices);
793 self.dirty = false;
794 }
795 (
796 self.face_vertices.as_ref().unwrap(),
797 self.face_indices.as_ref().unwrap(),
798 )
799 }
800
801 fn effective_vertices(&self) -> Cow<'_, [Vec3]> {
802 if let Some(deformation) = self.deformation.as_ref() {
803 Cow::Owned(
804 self.vertices
805 .iter()
806 .zip(deformation.displacements.iter())
807 .map(|(&position, &displacement)| position + displacement * deformation.scale)
808 .collect(),
809 )
810 } else {
811 Cow::Borrowed(&self.vertices)
812 }
813 }
814
815 fn scalar_color(
816 &self,
817 vertex_index: usize,
818 triangle_index: usize,
819 limits: Option<[f32; 2]>,
820 ) -> Option<Vec4> {
821 let field = self.scalar_field.as_ref()?;
822 let value = match field.location {
823 MeshFieldLocation::Vertex => *field.values.get(vertex_index)?,
824 MeshFieldLocation::Triangle => *field.values.get(triangle_index)?,
825 };
826 if !value.is_finite() {
827 return None;
828 }
829 let [min, max] = limits?;
830 let t = if max > min {
831 ((value - min) / (max - min)).clamp(0.0, 1.0)
832 } else {
833 0.5
834 };
835 Some(colormap_color(
836 &field.colormap,
837 t,
838 field.alpha * self.face_alpha,
839 ))
840 }
841
842 fn triangle_color(&self, triangle_index: usize) -> Option<Vec4> {
843 let mut color = *self.triangle_colors.as_ref()?.get(triangle_index)?;
844 color.w *= self.face_alpha.clamp(0.0, 1.0);
845 Some(color)
846 }
847
848 fn vertex_color(&self, vertex_index: usize) -> Option<Vec4> {
849 let mut color = *self.vertex_colors.as_ref()?.get(vertex_index)?;
850 color.w *= self.face_alpha.clamp(0.0, 1.0);
851 Some(color)
852 }
853
854 fn renderable_edges(&self) -> Vec<(u32, u32)> {
855 match self.edge_mode {
856 MeshEdgeMode::All => self.all_triangle_edges(),
857 MeshEdgeMode::Feature => self.feature_edges(),
858 MeshEdgeMode::None => Vec::new(),
859 }
860 }
861
862 fn all_triangle_edges(&self) -> Vec<(u32, u32)> {
863 let mut edges = BTreeSet::<(u32, u32)>::new();
864 for &[a, b, c] in &self.triangles {
865 edges.insert(normalized_edge(a, b));
866 edges.insert(normalized_edge(b, c));
867 edges.insert(normalized_edge(c, a));
868 }
869 edges.into_iter().collect()
870 }
871
872 fn feature_edges(&self) -> Vec<(u32, u32)> {
873 let groups = self.feature_edge_groups.as_deref();
874 let mut edges = BTreeMap::<(u32, u32), FeatureEdgeAccumulator>::new();
875 for (triangle_index, &[a, b, c]) in self.triangles.iter().enumerate() {
876 let group = groups
877 .and_then(|groups| groups.get(triangle_index).copied())
878 .unwrap_or(0);
879 accumulate_feature_edge(&mut edges, a, b, group);
880 accumulate_feature_edge(&mut edges, b, c, group);
881 accumulate_feature_edge(&mut edges, c, a, group);
882 }
883 edges
884 .into_iter()
885 .filter_map(|(edge, accumulator)| {
886 (accumulator.count != 2 || accumulator.crosses_group).then_some(edge)
887 })
888 .collect()
889 }
890
891 fn clear_invalid_triangle_metadata(&mut self) {
892 if self
893 .feature_edge_groups
894 .as_ref()
895 .is_some_and(|groups| groups.len() != self.triangles.len())
896 {
897 self.feature_edge_groups = None;
898 }
899 if self
900 .triangle_colors
901 .as_ref()
902 .is_some_and(|colors| colors.len() != self.triangles.len())
903 {
904 self.triangle_colors = None;
905 }
906 }
907
908 fn clear_invalid_vertex_metadata(&mut self) {
909 if self
910 .vertex_colors
911 .as_ref()
912 .is_some_and(|colors| colors.len() != self.vertices.len())
913 {
914 self.vertex_colors = None;
915 }
916 }
917}
918
919fn validate_mesh(vertices: &[Vec3], triangles: &[[u32; 3]]) -> Result<(), String> {
920 if vertices.is_empty() {
921 return Err("mesh: vertices must not be empty".to_string());
922 }
923 if triangles.is_empty() {
924 return Err("mesh: triangles must not be empty".to_string());
925 }
926 if vertices
927 .iter()
928 .any(|vertex| !vertex.x.is_finite() || !vertex.y.is_finite() || !vertex.z.is_finite())
929 {
930 return Err("mesh: vertices must contain finite coordinates".to_string());
931 }
932 let vertex_count = vertices.len();
933 for triangle in triangles {
934 if triangle.iter().any(|index| *index as usize >= vertex_count) {
935 return Err("mesh: triangle index exceeds vertex count".to_string());
936 }
937 }
938 Ok(())
939}
940
941fn validate_scalar_field(
942 field: &MeshScalarField,
943 vertex_count: usize,
944 triangle_count: usize,
945) -> Result<(), String> {
946 if field.field_id.trim().is_empty() {
947 return Err("mesh scalar field: field_id must not be empty".to_string());
948 }
949 validate_field_len(
950 "mesh scalar field",
951 field.location,
952 field.values.len(),
953 vertex_count,
954 triangle_count,
955 )?;
956 if field
957 .color_limits
958 .is_some_and(|[min, max]| !min.is_finite() || !max.is_finite() || max < min)
959 {
960 return Err("mesh scalar field: color_limits must be finite and ordered".to_string());
961 }
962 Ok(())
963}
964
965fn validate_vector_field(
966 field: &MeshVectorField,
967 vertex_count: usize,
968 triangle_count: usize,
969) -> Result<(), String> {
970 if field.field_id.trim().is_empty() {
971 return Err("mesh vector field: field_id must not be empty".to_string());
972 }
973 validate_field_len(
974 "mesh vector field",
975 field.location,
976 field.vectors.len(),
977 vertex_count,
978 triangle_count,
979 )?;
980 if field
981 .vectors
982 .iter()
983 .any(|vector| !vector.x.is_finite() || !vector.y.is_finite() || !vector.z.is_finite())
984 {
985 return Err("mesh vector field: vectors must contain finite components".to_string());
986 }
987 Ok(())
988}
989
990fn validate_deformation(deformation: &MeshDeformation, vertex_count: usize) -> Result<(), String> {
991 if deformation.field_id.trim().is_empty() {
992 return Err("mesh deformation: field_id must not be empty".to_string());
993 }
994 if deformation.displacements.len() != vertex_count {
995 return Err("mesh deformation: displacements must match vertex count".to_string());
996 }
997 if deformation.displacements.iter().any(|displacement| {
998 !displacement.x.is_finite() || !displacement.y.is_finite() || !displacement.z.is_finite()
999 }) {
1000 return Err("mesh deformation: displacements must contain finite components".to_string());
1001 }
1002 Ok(())
1003}
1004
1005fn validate_field_len(
1006 label: &str,
1007 location: MeshFieldLocation,
1008 actual: usize,
1009 vertex_count: usize,
1010 triangle_count: usize,
1011) -> Result<(), String> {
1012 let expected = match location {
1013 MeshFieldLocation::Vertex => vertex_count,
1014 MeshFieldLocation::Triangle => triangle_count,
1015 };
1016 if actual != expected {
1017 return Err(format!(
1018 "{label}: value count must match {} count ({expected})",
1019 location.as_str()
1020 ));
1021 }
1022 Ok(())
1023}
1024
1025fn sanitize_scalar_field(mut field: MeshScalarField) -> MeshScalarField {
1026 field.alpha = sanitize_alpha(field.alpha);
1027 if field.colormap.trim().is_empty() {
1028 field.colormap = "viridis".to_string();
1029 }
1030 field
1031}
1032
1033fn sanitize_vector_field(mut field: MeshVectorField) -> MeshVectorField {
1034 field.scale = if field.scale.is_finite() {
1035 field.scale
1036 } else {
1037 1.0
1038 };
1039 field.stride = field.stride.max(1);
1040 field.color = sanitize_color(field.color);
1041 field
1042}
1043
1044fn sanitize_deformation(mut deformation: MeshDeformation) -> MeshDeformation {
1045 deformation.scale = if deformation.scale.is_finite() {
1046 deformation.scale
1047 } else {
1048 1.0
1049 };
1050 deformation
1051}
1052
1053fn scalar_limits(field: &MeshScalarField) -> Option<[f32; 2]> {
1054 if let Some([min, max]) = field.color_limits {
1055 if min.is_finite() && max.is_finite() && max >= min {
1056 return Some([min, max]);
1057 }
1058 }
1059 let mut min = f32::INFINITY;
1060 let mut max = f32::NEG_INFINITY;
1061 for value in field
1062 .values
1063 .iter()
1064 .copied()
1065 .filter(|value| value.is_finite())
1066 {
1067 min = min.min(value);
1068 max = max.max(value);
1069 }
1070 if min.is_finite() && max.is_finite() {
1071 Some([min, max])
1072 } else {
1073 None
1074 }
1075}
1076
1077fn colormap_color(name: &str, t: f32, alpha: f32) -> Vec4 {
1078 let t = t.clamp(0.0, 1.0);
1079 let alpha = sanitize_alpha(alpha);
1080 match name {
1081 "thermal" | "heat" => Vec4::new(t, 0.22 + 0.5 * t, 1.0 - t, alpha),
1082 "blue_red" | "blue-red" | "diverging" => {
1083 if t < 0.5 {
1084 let local = t * 2.0;
1085 Vec4::new(0.14 + 0.56 * local, 0.34 + 0.36 * local, 0.86, alpha)
1086 } else {
1087 let local = (t - 0.5) * 2.0;
1088 Vec4::new(
1089 0.70 + 0.25 * local,
1090 0.70 - 0.46 * local,
1091 0.86 - 0.66 * local,
1092 alpha,
1093 )
1094 }
1095 }
1096 _ => {
1097 let r = (0.28 + 0.65 * t).clamp(0.0, 1.0);
1098 let g = (0.08 + 0.85 * (1.0 - (t - 0.5).abs() * 2.0)).clamp(0.0, 1.0);
1099 let b = (0.55 + 0.35 * (1.0 - t)).clamp(0.0, 1.0);
1100 Vec4::new(r, g, b, alpha)
1101 }
1102 }
1103}
1104
1105fn vertex_normals(vertices: &[Vec3], triangles: &[[u32; 3]]) -> Vec<Vec3> {
1106 let mut normals = vec![Vec3::ZERO; vertices.len()];
1107 for &[a, b, c] in triangles {
1108 let ia = a as usize;
1109 let ib = b as usize;
1110 let ic = c as usize;
1111 let edge_ab = vertices[ib] - vertices[ia];
1112 let edge_ac = vertices[ic] - vertices[ia];
1113 let normal = edge_ab.cross(edge_ac);
1114 if normal.length_squared() > f32::EPSILON {
1115 normals[ia] += normal;
1116 normals[ib] += normal;
1117 normals[ic] += normal;
1118 }
1119 }
1120 normals
1121 .into_iter()
1122 .map(|normal| {
1123 if normal.length_squared() > f32::EPSILON {
1124 normal.normalize()
1125 } else {
1126 Vec3::Z
1127 }
1128 })
1129 .collect()
1130}
1131
1132#[derive(Debug, Clone, Copy)]
1133struct FeatureEdgeAccumulator {
1134 first_group: u64,
1135 count: u8,
1136 crosses_group: bool,
1137}
1138
1139fn accumulate_feature_edge(
1140 edges: &mut BTreeMap<(u32, u32), FeatureEdgeAccumulator>,
1141 a: u32,
1142 b: u32,
1143 group: u64,
1144) {
1145 let entry = edges
1146 .entry(normalized_edge(a, b))
1147 .or_insert(FeatureEdgeAccumulator {
1148 first_group: group,
1149 count: 0,
1150 crosses_group: false,
1151 });
1152 if entry.first_group != group {
1153 entry.crosses_group = true;
1154 }
1155 entry.count = entry.count.saturating_add(1);
1156}
1157
1158fn normalized_edge(a: u32, b: u32) -> (u32, u32) {
1159 if a <= b {
1160 (a, b)
1161 } else {
1162 (b, a)
1163 }
1164}
1165
1166fn sanitize_color(color: Vec4) -> Vec4 {
1167 Vec4::new(
1168 sanitize_color_component(color.x),
1169 sanitize_color_component(color.y),
1170 sanitize_color_component(color.z),
1171 sanitize_color_component(color.w),
1172 )
1173}
1174
1175fn sanitize_color_component(value: f32) -> f32 {
1176 if value.is_finite() {
1177 value
1178 } else {
1179 0.0
1180 }
1181}
1182
1183fn sanitize_alpha(alpha: f32) -> f32 {
1184 if alpha.is_finite() {
1185 alpha.clamp(0.0, 1.0)
1186 } else {
1187 1.0
1188 }
1189}
1190
1191#[cfg(test)]
1192mod tests {
1193 use super::*;
1194
1195 fn triangle_mesh() -> MeshPlot {
1196 MeshPlot::new(
1197 vec![
1198 Vec3::new(0.0, 0.0, 0.0),
1199 Vec3::new(1.0, 0.0, 0.0),
1200 Vec3::new(0.0, 1.0, 0.0),
1201 ],
1202 vec![[0, 1, 2]],
1203 )
1204 .expect("mesh should be valid")
1205 }
1206
1207 fn square_mesh() -> MeshPlot {
1208 MeshPlot::new(
1209 vec![
1210 Vec3::new(0.0, 0.0, 0.0),
1211 Vec3::new(1.0, 0.0, 0.0),
1212 Vec3::new(1.0, 1.0, 0.0),
1213 Vec3::new(0.0, 1.0, 0.0),
1214 ],
1215 vec![[0, 1, 2], [0, 2, 3]],
1216 )
1217 .expect("mesh should be valid")
1218 }
1219
1220 #[test]
1221 fn scalar_field_colors_mesh_vertices() {
1222 let mut mesh = triangle_mesh();
1223 let mut field = MeshScalarField::new(
1224 "fea.structural.von_mises",
1225 MeshFieldLocation::Vertex,
1226 vec![0.0, 0.5, 1.0],
1227 );
1228 field.color_limits = Some([0.0, 1.0]);
1229 field.alpha = 0.75;
1230 mesh.set_scalar_field(Some(field))
1231 .expect("scalar field should be accepted");
1232
1233 let render = mesh.render_data();
1234 assert_eq!(render.vertices.len(), 3);
1235 assert_ne!(render.vertices[0].color, render.vertices[2].color);
1236 assert!((render.vertices[0].color[3] - 0.75).abs() < f32::EPSILON);
1237 }
1238
1239 #[test]
1240 fn deformation_updates_render_positions_and_bounds() {
1241 let mut mesh = triangle_mesh();
1242 let mut deformation = MeshDeformation::new(
1243 "fea.structural.displacement",
1244 vec![Vec3::ZERO, Vec3::new(0.0, 0.0, 2.0), Vec3::ZERO],
1245 );
1246 deformation.scale = 0.5;
1247 mesh.set_deformation(Some(deformation))
1248 .expect("deformation should be accepted");
1249
1250 let bounds = mesh.bounds();
1251 assert_eq!(bounds.max.z, 1.0);
1252 let render = mesh.render_data();
1253 assert!(render
1254 .vertices
1255 .iter()
1256 .any(|vertex| (vertex.position[2] - 1.0).abs() < f32::EPSILON));
1257 }
1258
1259 #[test]
1260 fn vector_field_generates_line_glyphs() {
1261 let mut mesh = triangle_mesh();
1262 let mut field = MeshVectorField::new(
1263 "fea.em.flux_density",
1264 MeshFieldLocation::Vertex,
1265 vec![Vec3::X, Vec3::ZERO, Vec3::Y],
1266 );
1267 field.scale = 0.25;
1268 mesh.set_vector_field(Some(field))
1269 .expect("vector field should be accepted");
1270
1271 let render = mesh.vector_render_data().expect("vector glyphs");
1272 assert_eq!(render.pipeline_type, PipelineType::Lines);
1273 assert_eq!(render.vertices.len(), 4);
1274 }
1275
1276 #[test]
1277 fn feature_edge_mode_suppresses_internal_edges_with_same_group() {
1278 let mut mesh = square_mesh();
1279 mesh.set_edge_mode(MeshEdgeMode::Feature);
1280 mesh.set_feature_edge_groups(Some(vec![7, 7]))
1281 .expect("feature groups should be accepted");
1282
1283 let render = mesh.edge_render_data().expect("feature edges");
1284
1285 assert_eq!(render.pipeline_type, PipelineType::Lines);
1286 assert_eq!(render.vertices.len(), 8);
1287 }
1288
1289 #[test]
1290 fn feature_edge_mode_keeps_edges_between_groups() {
1291 let mut mesh = square_mesh();
1292 mesh.set_edge_mode(MeshEdgeMode::Feature);
1293 mesh.set_feature_edge_groups(Some(vec![7, 9]))
1294 .expect("feature groups should be accepted");
1295
1296 let render = mesh.edge_render_data().expect("feature edges");
1297
1298 assert_eq!(render.pipeline_type, PipelineType::Lines);
1299 assert_eq!(render.vertices.len(), 10);
1300 }
1301
1302 #[test]
1303 fn triangle_colors_generate_per_face_vertices() {
1304 let mut mesh = square_mesh();
1305 mesh.set_triangle_colors(Some(vec![
1306 Vec4::new(1.0, 0.0, 0.0, 1.0),
1307 Vec4::new(0.0, 0.0, 1.0, 1.0),
1308 ]))
1309 .expect("triangle colors should be accepted");
1310
1311 let render = mesh.render_data();
1312
1313 assert_eq!(render.vertices.len(), 6);
1314 assert_eq!(render.vertices[0].color, [1.0, 0.0, 0.0, 1.0]);
1315 assert_eq!(render.vertices[3].color, [0.0, 0.0, 1.0, 1.0]);
1316 }
1317
1318 #[test]
1319 fn vertex_colors_preserve_indexed_mesh_geometry() {
1320 let mut mesh = square_mesh();
1321 mesh.set_vertex_colors(Some(vec![
1322 Vec4::new(1.0, 0.0, 0.0, 1.0),
1323 Vec4::new(0.0, 1.0, 0.0, 1.0),
1324 Vec4::new(0.0, 0.0, 1.0, 1.0),
1325 Vec4::new(1.0, 1.0, 0.0, 1.0),
1326 ]))
1327 .expect("vertex colors should be accepted");
1328
1329 let render = mesh.render_data();
1330
1331 assert_eq!(render.vertices.len(), 4);
1332 assert_eq!(render.indices.as_ref().unwrap().len(), 6);
1333 assert_eq!(render.vertices[2].color, [0.0, 0.0, 1.0, 1.0]);
1334 }
1335}