1use crate::types::BoundingBox;
2
3use crate::data::{CellArray, DataSetAttributes, FieldData, Points};
4use crate::data::traits::{DataObject, DataSet};
5
6#[derive(Debug, Clone, Default, PartialEq)]
25pub struct PolyData {
26 pub points: Points<f64>,
27 pub verts: CellArray,
28 pub lines: CellArray,
29 pub polys: CellArray,
30 pub strips: CellArray,
31 point_data: DataSetAttributes,
32 cell_data: DataSetAttributes,
33 field_data: FieldData,
34}
35
36impl PolyData {
37 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn from_triangles(points: Vec<[f64; 3]>, triangles: Vec<[i64; 3]>) -> Self {
45 let pts = Points::from_vec(points);
46 let mut polys = CellArray::new();
47 for tri in &triangles {
48 polys.push_cell(&[tri[0], tri[1], tri[2]]);
49 }
50 Self {
51 points: pts,
52 polys,
53 ..Default::default()
54 }
55 }
56
57 pub fn from_polygons(points: Vec<[f64; 3]>, cells: Vec<Vec<i64>>) -> Self {
62 let pts = Points::from_vec(points);
63 let mut polys = CellArray::new();
64 for cell in &cells {
65 polys.push_cell(cell);
66 }
67 Self { points: pts, polys, ..Default::default() }
68 }
69
70 pub fn from_quads(points: Vec<[f64; 3]>, quads: Vec<[i64; 4]>) -> Self {
74 let pts = Points::from_vec(points);
75 let mut polys = CellArray::new();
76 for q in &quads {
77 polys.push_cell(&[q[0], q[1], q[2], q[3]]);
78 }
79 Self { points: pts, polys, ..Default::default() }
80 }
81
82 pub fn from_lines(points: Vec<[f64; 3]>, segments: Vec<[i64; 2]>) -> Self {
86 let pts = Points::from_vec(points);
87 let mut lines = CellArray::new();
88 for seg in &segments {
89 lines.push_cell(&[seg[0], seg[1]]);
90 }
91 Self { points: pts, lines, ..Default::default() }
92 }
93
94 pub fn from_flat_arrays(coords: &[f64], indices: &[i64]) -> Self {
113 assert!(coords.len() % 3 == 0, "coords length must be divisible by 3");
114 assert!(indices.len() % 3 == 0, "indices length must be divisible by 3");
115
116 let pts: Vec<[f64; 3]> = coords.chunks_exact(3)
117 .map(|c| [c[0], c[1], c[2]])
118 .collect();
119 let tris: Vec<[i64; 3]> = indices.chunks_exact(3)
120 .map(|c| [c[0], c[1], c[2]])
121 .collect();
122 Self::from_triangles(pts, tris)
123 }
124
125 pub fn from_points(points: Vec<[f64; 3]>) -> Self {
129 Self { points: Points::from_vec(points), ..Default::default() }
130 }
131
132 pub fn from_vertices(points: Vec<[f64; 3]>) -> Self {
134 let n = points.len();
135 let pts = Points::from_vec(points);
136 let mut verts = CellArray::new();
137 for i in 0..n {
138 verts.push_cell(&[i as i64]);
139 }
140 Self { points: pts, verts, ..Default::default() }
141 }
142
143 pub fn from_polyline(points: Vec<[f64; 3]>) -> Self {
145 let n = points.len();
146 let pts = Points::from_vec(points);
147 let mut lines = CellArray::new();
148 let ids: Vec<i64> = (0..n as i64).collect();
149 lines.push_cell(&ids);
150 Self { points: pts, lines, ..Default::default() }
151 }
152
153 pub fn push_point(&mut self, point: [f64; 3]) -> i64 {
155 let idx = self.points.len() as i64;
156 self.points.push(point);
157 idx
158 }
159
160 pub fn push_triangle(&mut self, i0: i64, i1: i64, i2: i64) {
162 self.polys.push_cell(&[i0, i1, i2]);
163 }
164
165 pub fn push_quad(&mut self, i0: i64, i1: i64, i2: i64, i3: i64) {
167 self.polys.push_cell(&[i0, i1, i2, i3]);
168 }
169
170 pub fn push_line(&mut self, i0: i64, i1: i64) {
172 self.lines.push_cell(&[i0, i1]);
173 }
174
175 pub fn total_cells(&self) -> usize {
177 self.verts.num_cells()
178 + self.lines.num_cells()
179 + self.polys.num_cells()
180 + self.strips.num_cells()
181 }
182
183 pub fn point_data(&self) -> &DataSetAttributes {
184 &self.point_data
185 }
186
187 pub fn point_data_mut(&mut self) -> &mut DataSetAttributes {
188 &mut self.point_data
189 }
190
191 pub fn cell_data(&self) -> &DataSetAttributes {
192 &self.cell_data
193 }
194
195 pub fn cell_data_mut(&mut self) -> &mut DataSetAttributes {
196 &mut self.cell_data
197 }
198
199 pub fn field_data(&self) -> &FieldData {
201 &self.field_data
202 }
203
204 pub fn field_data_mut(&mut self) -> &mut FieldData {
206 &mut self.field_data
207 }
208
209 pub fn reverse_cells(&mut self) {
213 let mut new_polys = CellArray::new();
214 for cell in self.polys.iter() {
215 let reversed: Vec<i64> = cell.iter().copied().rev().collect();
216 new_polys.push_cell(&reversed);
217 }
218 self.polys = new_polys;
219 }
220
221 pub fn with_point_array(mut self, array: crate::data::AnyDataArray) -> Self {
223 let name = array.name().to_string();
224 self.point_data.add_array(array);
225 if self.point_data.scalars().is_none() {
226 self.point_data.set_active_scalars(&name);
227 }
228 self
229 }
230
231 pub fn with_cell_array(mut self, array: crate::data::AnyDataArray) -> Self {
233 self.cell_data.add_array(array);
234 self
235 }
236
237 pub fn with_active_scalars(mut self, name: &str) -> Self {
239 self.point_data.set_active_scalars(name);
240 self
241 }
242
243 pub fn from_xyz_arrays(
245 x: &[f64], y: &[f64], z: &[f64],
246 triangles: &[[i64; 3]],
247 ) -> Self {
248 assert_eq!(x.len(), y.len());
249 assert_eq!(x.len(), z.len());
250 let pts: Vec<[f64; 3]> = x.iter().zip(y.iter()).zip(z.iter())
251 .map(|((&xi, &yi), &zi)| [xi, yi, zi])
252 .collect();
253 Self::from_triangles(pts, triangles.to_vec())
254 }
255
256 pub fn append(&mut self, other: &PolyData) {
258 let base = self.points.len() as i64;
259 for p in &other.points {
260 self.points.push(p);
261 }
262 for cell in other.polys.iter() {
263 let offset: Vec<i64> = cell.iter().map(|&id| id + base).collect();
264 self.polys.push_cell(&offset);
265 }
266 for cell in other.lines.iter() {
267 let offset: Vec<i64> = cell.iter().map(|&id| id + base).collect();
268 self.lines.push_cell(&offset);
269 }
270 for cell in other.verts.iter() {
271 let offset: Vec<i64> = cell.iter().map(|&id| id + base).collect();
272 self.verts.push_cell(&offset);
273 }
274 for cell in other.strips.iter() {
275 let offset: Vec<i64> = cell.iter().map(|&id| id + base).collect();
276 self.strips.push_cell(&offset);
277 }
278 }
279
280 pub fn num_polys(&self) -> usize {
282 self.polys.num_cells()
283 }
284
285 pub fn num_triangles(&self) -> usize {
287 self.polys.iter().filter(|c| c.len() == 3).count()
288 }
289
290 pub fn centroid(&self) -> [f64; 3] {
292 self.points.centroid()
293 }
294
295 pub fn bounding_sphere(&self) -> ([f64; 3], f64) {
297 let center = self.centroid();
298 let mut max_r2 = 0.0f64;
299 for p in &self.points {
300 let dx = p[0] - center[0];
301 let dy = p[1] - center[1];
302 let dz = p[2] - center[2];
303 max_r2 = max_r2.max(dx*dx + dy*dy + dz*dz);
304 }
305 (center, max_r2.sqrt())
306 }
307
308 pub fn is_all_triangles(&self) -> bool {
310 self.polys.num_cells() > 0 && self.polys.iter().all(|c| c.len() == 3)
311 }
312
313 pub fn num_lines(&self) -> usize {
315 self.lines.num_cells()
316 }
317
318 pub fn num_verts(&self) -> usize {
320 self.verts.num_cells()
321 }
322
323 pub fn num_edges(&self) -> usize {
325 let mut edges = std::collections::HashSet::new();
326 for cell in self.polys.iter() {
327 let n = cell.len();
328 for i in 0..n {
329 let a = cell[i] as usize;
330 let b = cell[(i + 1) % n] as usize;
331 edges.insert(if a < b { (a, b) } else { (b, a) });
332 }
333 }
334 edges.len()
335 }
336
337 pub fn add_scalars(&mut self, name: &str, values: Vec<f64>) {
350 let arr = crate::data::DataArray::from_vec(name, values, 1);
351 self.point_data.add_array(crate::data::AnyDataArray::F64(arr));
352 self.point_data.set_active_scalars(name);
353 }
354
355 pub fn add_vectors(&mut self, name: &str, values: Vec<[f64; 3]>) {
357 let flat: Vec<f64> = values.into_iter().flat_map(|v| v).collect();
358 let arr = crate::data::DataArray::from_vec(name, flat, 3);
359 self.point_data.add_array(crate::data::AnyDataArray::F64(arr));
360 self.point_data.set_active_vectors(name);
361 }
362
363 pub fn get_scalars(&self, name: &str) -> Option<Vec<f64>> {
365 self.point_data.get_array(name).map(|a| a.to_f64_vec())
366 }
367
368 pub fn get_vectors(&self, name: &str) -> Option<Vec<[f64; 3]>> {
370 let arr = self.point_data.get_array(name)?;
371 if arr.num_components() != 3 { return None; }
372 let mut result = Vec::with_capacity(arr.num_tuples());
373 let mut buf = [0.0f64; 3];
374 for i in 0..arr.num_tuples() {
375 arr.tuple_as_f64(i, &mut buf);
376 result.push(buf);
377 }
378 Some(result)
379 }
380
381 pub fn summary(&self) -> String {
383 format!(
384 "PolyData: {} points, {} polys, {} lines, {} verts, {} point arrays, {} cell arrays",
385 self.points.len(),
386 self.polys.num_cells(),
387 self.lines.num_cells(),
388 self.verts.num_cells(),
389 self.point_data.num_arrays(),
390 self.cell_data.num_arrays(),
391 )
392 }
393
394 pub fn to_table(&self) -> crate::data::Table {
399 let n = self.points.len();
400 let mut x = Vec::with_capacity(n);
401 let mut y = Vec::with_capacity(n);
402 let mut z = Vec::with_capacity(n);
403 for p in &self.points {
404 x.push(p[0]);
405 y.push(p[1]);
406 z.push(p[2]);
407 }
408 let mut table = crate::data::Table::new();
409 table.add_column(crate::data::AnyDataArray::F64(crate::data::DataArray::from_vec("x", x, 1)));
410 table.add_column(crate::data::AnyDataArray::F64(crate::data::DataArray::from_vec("y", y, 1)));
411 table.add_column(crate::data::AnyDataArray::F64(crate::data::DataArray::from_vec("z", z, 1)));
412
413 for i in 0..self.point_data.num_arrays() {
414 if let Some(arr) = self.point_data.get_array_by_index(i) {
415 table.add_column(arr.clone());
416 }
417 }
418 table
419 }
420
421 pub fn approx_eq(&self, other: &PolyData, tolerance: f64) -> bool {
423 if self.points.len() != other.points.len() { return false; }
424 if self.polys.num_cells() != other.polys.num_cells() { return false; }
425 for i in 0..self.points.len() {
426 let a = self.points.get(i);
427 let b = other.points.get(i);
428 if (a[0]-b[0]).abs() > tolerance || (a[1]-b[1]).abs() > tolerance || (a[2]-b[2]).abs() > tolerance {
429 return false;
430 }
431 }
432 for (ca, cb) in self.polys.iter().zip(other.polys.iter()) {
433 if ca != cb { return false; }
434 }
435 true
436 }
437}
438
439impl DataObject for PolyData {
440 fn field_data(&self) -> &FieldData {
441 &self.field_data
442 }
443
444 fn field_data_mut(&mut self) -> &mut FieldData {
445 &mut self.field_data
446 }
447}
448
449impl DataSet for PolyData {
450 fn num_points(&self) -> usize {
451 self.points.len()
452 }
453
454 fn num_cells(&self) -> usize {
455 self.total_cells()
456 }
457
458 fn point(&self, idx: usize) -> [f64; 3] {
459 self.points.get(idx)
460 }
461
462 fn bounds(&self) -> BoundingBox {
463 self.points.bounds()
464 }
465
466 fn point_data(&self) -> &DataSetAttributes {
467 &self.point_data
468 }
469
470 fn point_data_mut(&mut self) -> &mut DataSetAttributes {
471 &mut self.point_data
472 }
473
474 fn cell_data(&self) -> &DataSetAttributes {
475 &self.cell_data
476 }
477
478 fn cell_data_mut(&mut self) -> &mut DataSetAttributes {
479 &mut self.cell_data
480 }
481}
482
483impl std::fmt::Display for PolyData {
484 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
485 write!(f, "{}", self.summary())
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn from_triangles() {
495 let pd = PolyData::from_triangles(
496 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]],
497 vec![[0, 1, 2]],
498 );
499 assert_eq!(pd.num_points(), 3);
500 assert_eq!(pd.num_cells(), 1);
501 assert_eq!(pd.polys.cell(0), &[0, 1, 2]);
502 }
503
504 #[test]
505 fn bounds() {
506 let pd = PolyData::from_triangles(
507 vec![[0.0, 0.0, 0.0], [1.0, 2.0, 3.0], [0.5, 1.0, 1.5]],
508 vec![[0, 1, 2]],
509 );
510 let bb = pd.bounds();
511 assert_eq!(bb.x_min, 0.0);
512 assert_eq!(bb.x_max, 1.0);
513 assert_eq!(bb.y_max, 2.0);
514 assert_eq!(bb.z_max, 3.0);
515 }
516
517 #[test]
518 fn empty_poly_data() {
519 let pd = PolyData::new();
520 assert_eq!(pd.num_points(), 0);
521 assert_eq!(pd.num_cells(), 0);
522 }
523
524 #[test]
525 fn from_quads() {
526 let pd = PolyData::from_quads(
527 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]],
528 vec![[0, 1, 2, 3]],
529 );
530 assert_eq!(pd.num_points(), 4);
531 assert_eq!(pd.polys.num_cells(), 1);
532 assert_eq!(pd.polys.cell(0).len(), 4);
533 }
534
535 #[test]
536 fn from_lines() {
537 let pd = PolyData::from_lines(
538 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]],
539 vec![[0, 1], [1, 2]],
540 );
541 assert_eq!(pd.num_points(), 3);
542 assert_eq!(pd.lines.num_cells(), 2);
543 }
544
545 #[test]
546 fn from_vertices() {
547 let pd = PolyData::from_vertices(vec![[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]]);
548 assert_eq!(pd.num_points(), 2);
549 assert_eq!(pd.verts.num_cells(), 2);
550 }
551
552 #[test]
553 fn from_polyline() {
554 let pd = PolyData::from_polyline(vec![
555 [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 1.0, 0.0],
556 ]);
557 assert_eq!(pd.num_points(), 3);
558 assert_eq!(pd.lines.num_cells(), 1);
559 assert_eq!(pd.lines.cell(0).len(), 3);
560 }
561
562 #[test]
563 fn from_xyz_arrays() {
564 let x = vec![0.0, 1.0, 0.0];
565 let y = vec![0.0, 0.0, 1.0];
566 let z = vec![0.0, 0.0, 0.0];
567 let pd = PolyData::from_xyz_arrays(&x, &y, &z, &[[0, 1, 2]]);
568 assert_eq!(pd.num_points(), 3);
569 assert_eq!(pd.num_polys(), 1);
570 }
571
572 #[test]
573 fn append() {
574 let mut a = PolyData::from_triangles(
575 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
576 vec![[0, 1, 2]],
577 );
578 let b = PolyData::from_triangles(
579 vec![[5.0, 0.0, 0.0], [6.0, 0.0, 0.0], [5.0, 1.0, 0.0]],
580 vec![[0, 1, 2]],
581 );
582 a.append(&b);
583 assert_eq!(a.points.len(), 6);
584 assert_eq!(a.polys.num_cells(), 2);
585 assert_eq!(a.polys.cell(1), &[3, 4, 5]);
586 }
587
588 #[test]
589 fn edge_count() {
590 let pd = PolyData::from_triangles(
591 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
592 vec![[0, 1, 2]],
593 );
594 assert_eq!(pd.num_edges(), 3);
595 }
596
597 #[test]
598 fn summary() {
599 let pd = PolyData::from_triangles(
600 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
601 vec![[0, 1, 2]],
602 );
603 let s = pd.summary();
604 assert!(s.contains("3 points"));
605 assert!(s.contains("1 polys"));
606 }
607
608 #[test]
609 fn approx_eq() {
610 let a = PolyData::from_triangles(
611 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
612 vec![[0, 1, 2]],
613 );
614 let b = a.clone();
615 assert!(a.approx_eq(&b, 1e-10));
616 }
617
618 #[test]
619 fn num_convenience() {
620 let pd = PolyData::from_triangles(
621 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
622 vec![[0, 1, 2]],
623 );
624 assert_eq!(pd.num_polys(), 1);
625 assert_eq!(pd.num_lines(), 0);
626 assert_eq!(pd.num_verts(), 0);
627 }
628
629 #[test]
630 fn display() {
631 let pd = PolyData::from_triangles(
632 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
633 vec![[0, 1, 2]],
634 );
635 let s = format!("{pd}");
636 assert!(s.contains("3 points"));
637 }
638
639 #[test]
640 fn to_table() {
641 let mut pd = PolyData::from_triangles(
642 vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]],
643 vec![[0, 1, 2]],
644 );
645 let s = crate::data::DataArray::from_vec("temp", vec![10.0f64, 20.0, 30.0], 1);
646 pd.point_data_mut().add_array(crate::data::AnyDataArray::F64(s));
647
648 let table = pd.to_table();
649 assert_eq!(table.num_rows(), 3);
650 assert_eq!(table.num_columns(), 4); assert_eq!(table.value_f64(0, "x"), Some(1.0));
652 assert_eq!(table.value_f64(1, "y"), Some(5.0));
653 assert_eq!(table.value_f64(2, "temp"), Some(30.0));
654 }
655
656 #[test]
657 fn from_points_no_cells() {
658 let pd = PolyData::from_points(vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]);
659 assert_eq!(pd.points.len(), 2);
660 assert_eq!(pd.polys.num_cells(), 0);
661 assert_eq!(pd.verts.num_cells(), 0);
662 }
663
664 #[test]
665 fn from_polygons_mixed() {
666 let pd = PolyData::from_polygons(
667 vec![[0.0,0.0,0.0],[1.0,0.0,0.0],[1.0,1.0,0.0],[0.0,1.0,0.0],[2.0,0.0,0.0]],
668 vec![vec![0, 1, 2], vec![0, 2, 3], vec![1, 4, 2]],
669 );
670 assert_eq!(pd.points.len(), 5);
671 assert_eq!(pd.polys.num_cells(), 3);
672 }
673
674 #[test]
675 fn triangle_counting() {
676 let pd = PolyData::from_polygons(
677 vec![[0.0,0.0,0.0],[1.0,0.0,0.0],[1.0,1.0,0.0],[0.0,1.0,0.0]],
678 vec![vec![0,1,2], vec![0,1,2,3]],
679 );
680 assert_eq!(pd.num_triangles(), 1);
681 assert!(!pd.is_all_triangles());
682
683 let tri = PolyData::from_triangles(
684 vec![[0.0,0.0,0.0],[1.0,0.0,0.0],[0.0,1.0,0.0]],
685 vec![[0,1,2]],
686 );
687 assert!(tri.is_all_triangles());
688 }
689
690 #[test]
691 fn add_get_scalars() {
692 let mut pd = PolyData::from_triangles(
693 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
694 vec![[0, 1, 2]],
695 );
696 pd.add_scalars("temp", vec![10.0, 20.0, 30.0]);
697 let vals = pd.get_scalars("temp").unwrap();
698 assert_eq!(vals, vec![10.0, 20.0, 30.0]);
699 assert!(pd.get_scalars("nonexistent").is_none());
700 }
701
702 #[test]
703 fn add_get_vectors() {
704 let mut pd = PolyData::from_triangles(
705 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
706 vec![[0, 1, 2]],
707 );
708 pd.add_vectors("velocity", vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]);
709 let vecs = pd.get_vectors("velocity").unwrap();
710 assert_eq!(vecs.len(), 3);
711 assert_eq!(vecs[0], [1.0, 0.0, 0.0]);
712 }
713
714 #[test]
715 fn incremental_building() {
716 let mut pd = PolyData::new();
717 let i0 = pd.push_point([0.0, 0.0, 0.0]);
718 let i1 = pd.push_point([1.0, 0.0, 0.0]);
719 let i2 = pd.push_point([0.0, 1.0, 0.0]);
720 let i3 = pd.push_point([1.0, 1.0, 0.0]);
721 pd.push_triangle(i0, i1, i2);
722 pd.push_triangle(i1, i3, i2);
723 pd.push_line(i0, i1);
724
725 assert_eq!(pd.points.len(), 4);
726 assert_eq!(pd.num_polys(), 2);
727 assert_eq!(pd.num_lines(), 1);
728 assert_eq!(pd.polys.cell(0), &[0, 1, 2]);
729 }
730
731 #[test]
732 fn reverse_cells() {
733 let mut pd = PolyData::from_triangles(
734 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
735 vec![[0, 1, 2]],
736 );
737 pd.reverse_cells();
738 assert_eq!(pd.polys.cell(0), &[2, 1, 0]);
739 }
740
741 #[test]
742 fn field_data_access() {
743 let mut pd = PolyData::new();
744 assert!(pd.field_data().is_empty());
745 pd.field_data_mut().add_array(crate::data::AnyDataArray::F64(
746 crate::data::DataArray::from_vec("meta", vec![42.0], 1),
747 ));
748 assert!(pd.field_data().has_array("meta"));
749 }
750}