1#![allow(dead_code)]
12#![allow(unused_imports)]
13#![allow(clippy::too_many_arguments)]
14
15use std::collections::HashMap;
16use std::fmt::Write as FmtWrite;
17
18#[derive(Clone, Debug, PartialEq)]
24pub struct Point3 {
25 pub x: f64,
27 pub y: f64,
29 pub z: f64,
31}
32
33impl Point3 {
34 pub fn new(x: f64, y: f64, z: f64) -> Self {
36 Self { x, y, z }
37 }
38
39 pub fn to_array(&self) -> [f64; 3] {
41 [self.x, self.y, self.z]
42 }
43
44 pub fn dist(&self, other: &Point3) -> f64 {
46 let dx = self.x - other.x;
47 let dy = self.y - other.y;
48 let dz = self.z - other.z;
49 (dx * dx + dy * dy + dz * dz).sqrt()
50 }
51}
52
53#[derive(Clone, Debug)]
55pub struct ScalarField3D {
56 pub nx: usize,
58 pub ny: usize,
60 pub nz: usize,
62 pub dx: f64,
64 pub origin: [f64; 3],
66 pub data: Vec<f64>,
68}
69
70impl ScalarField3D {
71 pub fn new(nx: usize, ny: usize, nz: usize, dx: f64, origin: [f64; 3]) -> Self {
73 Self {
74 nx,
75 ny,
76 nz,
77 dx,
78 origin,
79 data: vec![0.0; nx * ny * nz],
80 }
81 }
82
83 #[inline]
85 pub fn idx(&self, ix: usize, iy: usize, iz: usize) -> usize {
86 ix * self.ny * self.nz + iy * self.nz + iz
87 }
88
89 pub fn set(&mut self, ix: usize, iy: usize, iz: usize, val: f64) {
91 let i = self.idx(ix, iy, iz);
92 self.data[i] = val;
93 }
94
95 pub fn get(&self, ix: usize, iy: usize, iz: usize) -> f64 {
97 self.data[self.idx(ix, iy, iz)]
98 }
99
100 pub fn min_val(&self) -> f64 {
102 self.data.iter().cloned().fold(f64::INFINITY, f64::min)
103 }
104
105 pub fn max_val(&self) -> f64 {
107 self.data.iter().cloned().fold(f64::NEG_INFINITY, f64::max)
108 }
109}
110
111#[derive(Clone, Debug)]
117pub struct ParaviewStateConfig {
118 pub source_file: String,
120 pub colour_array: String,
122 pub colormap: String,
124 pub scalar_range: [f64; 2],
126 pub camera_pos: [f64; 3],
128 pub camera_focal: [f64; 3],
130 pub camera_up: [f64; 3],
132 pub image_width: u32,
134 pub image_height: u32,
136}
137
138impl ParaviewStateConfig {
139 pub fn default_config(source_file: impl Into<String>) -> Self {
141 Self {
142 source_file: source_file.into(),
143 colour_array: "pressure".into(),
144 colormap: "Cool to Warm".into(),
145 scalar_range: [0.0, 1.0],
146 camera_pos: [0.0, 0.0, 5.0],
147 camera_focal: [0.0, 0.0, 0.0],
148 camera_up: [0.0, 1.0, 0.0],
149 image_width: 1920,
150 image_height: 1080,
151 }
152 }
153}
154
155pub fn write_paraview_state(cfg: &ParaviewStateConfig) -> String {
157 let mut out = String::new();
158 let _ = writeln!(out, r#"<?xml version="1.0"?>"#);
159 let _ = writeln!(out, r#"<ParaView>"#);
160 let _ = writeln!(out, r#" <ServerManagerState version="5.10.0">"#);
161 let _ = writeln!(
163 out,
164 r#" <Proxy group="sources" type="XMLUnstructuredGridReader" id="1001">"#
165 );
166 let _ = writeln!(
167 out,
168 r#" <Property name="FileName" number_of_elements="1">"#
169 );
170 let _ = writeln!(
171 out,
172 r#" <Element index="0" value="{}"/>"#,
173 cfg.source_file
174 );
175 let _ = writeln!(out, r#" </Property>"#);
176 let _ = writeln!(out, r#" </Proxy>"#);
177 let _ = writeln!(
179 out,
180 r#" <Proxy group="representations" type="GeometryRepresentation" id="2001">"#
181 );
182 let _ = writeln!(out, r#" <Property name="ColorArrayName">"#);
183 let _ = writeln!(out, r#" <Element index="0" value="POINTS"/>"#);
184 let _ = writeln!(
185 out,
186 r#" <Element index="1" value="{}"/>"#,
187 cfg.colour_array
188 );
189 let _ = writeln!(out, r#" </Property>"#);
190 let _ = writeln!(out, r#" <Property name="LookupTable" value="3001"/>"#);
191 let _ = writeln!(out, r#" </Proxy>"#);
192 let _ = writeln!(
194 out,
195 r#" <Proxy group="lookup_tables" type="PVLookupTable" id="3001">"#
196 );
197 let _ = writeln!(
198 out,
199 r#" <Property name="ColorSpace"><Element index="0" value="HSV"/></Property>"#
200 );
201 let _ = writeln!(
202 out,
203 r#" <Property name="ScalarRangeInitialized"><Element index="0" value="1"/></Property>"#
204 );
205 let _ = writeln!(
206 out,
207 r#" <Property name="RGBPoints" number_of_elements="8">"#
208 );
209 let _ = writeln!(
210 out,
211 r#" <Element index="0" value="{}"/><Element index="1" value="0.23137"/>"#,
212 cfg.scalar_range[0]
213 );
214 let _ = writeln!(
215 out,
216 r#" <Element index="2" value="0.29803"/><Element index="3" value="0.75294"/>"#
217 );
218 let _ = writeln!(
219 out,
220 r#" <Element index="4" value="{}"/><Element index="5" value="0.70588"/>"#,
221 cfg.scalar_range[1]
222 );
223 let _ = writeln!(
224 out,
225 r#" <Element index="6" value="0.01568"/><Element index="7" value="0.14901"/>"#
226 );
227 let _ = writeln!(out, r#" </Property>"#);
228 let _ = writeln!(out, r#" </Proxy>"#);
229 let _ = writeln!(
231 out,
232 r#" <Proxy group="views" type="RenderView" id="4001">"#
233 );
234 let _ = writeln!(
235 out,
236 r#" <Property name="CameraPosition" number_of_elements="3"><Element index="0" value="{}"/><Element index="1" value="{}"/><Element index="2" value="{}"/></Property>"#,
237 cfg.camera_pos[0], cfg.camera_pos[1], cfg.camera_pos[2]
238 );
239 let _ = writeln!(
240 out,
241 r#" <Property name="CameraFocalPoint" number_of_elements="3"><Element index="0" value="{}"/><Element index="1" value="{}"/><Element index="2" value="{}"/></Property>"#,
242 cfg.camera_focal[0], cfg.camera_focal[1], cfg.camera_focal[2]
243 );
244 let _ = writeln!(
245 out,
246 r#" <Property name="ViewSize" number_of_elements="2"><Element index="0" value="{}"/><Element index="1" value="{}"/></Property>"#,
247 cfg.image_width, cfg.image_height
248 );
249 let _ = writeln!(out, r#" </Proxy>"#);
250 let _ = writeln!(out, r#" </ServerManagerState>"#);
251 let _ = writeln!(out, r#"</ParaView>"#);
252 out
253}
254
255#[derive(Clone, Debug)]
261pub struct VisItDatabase {
262 pub basename: String,
264 pub extension: String,
266 pub times: Vec<f64>,
268 pub variables: Vec<String>,
270 pub ndim: u8,
272}
273
274impl VisItDatabase {
275 pub fn new(basename: impl Into<String>, extension: impl Into<String>, ndim: u8) -> Self {
277 Self {
278 basename: basename.into(),
279 extension: extension.into(),
280 times: Vec::new(),
281 variables: Vec::new(),
282 ndim,
283 }
284 }
285
286 pub fn add_time(&mut self, t: f64) {
288 self.times.push(t);
289 }
290
291 pub fn add_variable(&mut self, name: impl Into<String>) {
293 self.variables.push(name.into());
294 }
295
296 pub fn write_visit_index(&self) -> String {
298 let mut out = String::new();
299 let _ = writeln!(out, "!NBLOCKS 1");
300 for (i, _t) in self.times.iter().enumerate() {
301 let _ = writeln!(out, "{}{:06}.{}", self.basename, i, self.extension);
302 }
303 out
304 }
305
306 pub fn write_manifest(&self) -> String {
308 let mut out = String::new();
309 let _ = writeln!(out, "{{");
310 let _ = writeln!(out, r#" "basename": "{}","#, self.basename);
311 let _ = writeln!(out, r#" "ndim": {},"#, self.ndim);
312 let _ = writeln!(out, r#" "n_times": {},"#, self.times.len());
313 let _ = writeln!(out, r#" "variables": ["#);
314 for (i, v) in self.variables.iter().enumerate() {
315 let comma = if i + 1 < self.variables.len() {
316 ","
317 } else {
318 ""
319 };
320 let _ = writeln!(out, r#" "{}"{}"#, v, comma);
321 }
322 let _ = writeln!(out, r#" ],"#);
323 let _ = writeln!(out, r#" "times": ["#);
324 for (i, t) in self.times.iter().enumerate() {
325 let comma = if i + 1 < self.times.len() { "," } else { "" };
326 let _ = writeln!(out, " {}{}", t, comma);
327 }
328 let _ = writeln!(out, r#" ]"#);
329 let _ = writeln!(out, "}}");
330 out
331 }
332}
333
334#[derive(Clone, Debug)]
340pub struct BlenderExportConfig {
341 pub object_name: String,
343 pub output_path: String,
345 pub rigid_body: bool,
347 pub subdivision: bool,
349 pub subdiv_level: u32,
351 pub bg_colour: [f64; 4],
353 pub emission_strength: f64,
355}
356
357impl BlenderExportConfig {
358 pub fn new(object_name: impl Into<String>, output_path: impl Into<String>) -> Self {
360 Self {
361 object_name: object_name.into(),
362 output_path: output_path.into(),
363 rigid_body: false,
364 subdivision: false,
365 subdiv_level: 2,
366 bg_colour: [0.05, 0.05, 0.05, 1.0],
367 emission_strength: 1.0,
368 }
369 }
370
371 pub fn generate_script(&self, mesh_file: &str) -> String {
373 let mut s = String::new();
374 let _ = writeln!(s, "import bpy");
375 let _ = writeln!(s, "import bmesh");
376 let _ = writeln!(s);
377 let _ = writeln!(s, "# Clear existing objects");
378 let _ = writeln!(s, "bpy.ops.object.select_all(action='SELECT')");
379 let _ = writeln!(s, "bpy.ops.object.delete()");
380 let _ = writeln!(s);
381 let _ = writeln!(s, "# Import mesh");
382 let _ = writeln!(s, "bpy.ops.import_scene.obj(filepath='{}')", mesh_file);
383 let _ = writeln!(s, "obj = bpy.context.selected_objects[0]");
384 let _ = writeln!(s, "obj.name = '{}'", self.object_name);
385 if self.rigid_body {
386 let _ = writeln!(s, "bpy.ops.rigidbody.object_add()");
387 let _ = writeln!(s, "obj.rigid_body.type = 'ACTIVE'");
388 }
389 if self.subdivision {
390 let _ = writeln!(s, "mod = obj.modifiers.new(name='Subdiv', type='SUBSURF')");
391 let _ = writeln!(s, "mod.levels = {}", self.subdiv_level);
392 let _ = writeln!(s, "mod.render_levels = {}", self.subdiv_level);
393 }
394 let _ = writeln!(s, "# World background");
395 let _ = writeln!(
396 s,
397 "bpy.context.scene.world.node_tree.nodes['Background'].inputs[0].default_value = ({}, {}, {}, {})",
398 self.bg_colour[0], self.bg_colour[1], self.bg_colour[2], self.bg_colour[3]
399 );
400 let _ = writeln!(s, "# Save blend file");
401 let _ = writeln!(
402 s,
403 "bpy.ops.wm.save_as_mainfile(filepath='{}')",
404 self.output_path
405 );
406 s
407 }
408
409 pub fn generate_volume_script(&self, vdb_file: &str) -> String {
411 let mut s = String::new();
412 let _ = writeln!(s, "import bpy");
413 let _ = writeln!(s, "bpy.ops.object.volume_import(filepath='{}')", vdb_file);
414 let _ = writeln!(s, "vol = bpy.context.active_object");
415 let _ = writeln!(s, "mat = bpy.data.materials.new(name='VolMat')");
416 let _ = writeln!(s, "mat.use_nodes = True");
417 let _ = writeln!(s, "nodes = mat.node_tree.nodes");
418 let _ = writeln!(s, "nodes.clear()");
419 let _ = writeln!(s, "emission = nodes.new('ShaderNodeEmission')");
420 let _ = writeln!(
421 s,
422 "emission.inputs['Strength'].default_value = {}",
423 self.emission_strength
424 );
425 let _ = writeln!(s, "vol.data.materials.append(mat)");
426 s
427 }
428}
429
430#[derive(Clone, Debug)]
436pub struct MatplotlibFigure {
437 pub title: String,
439 pub xlabel: String,
441 pub ylabel: String,
443 pub width: f64,
445 pub height: f64,
447 pub series: Vec<DataSeries>,
449 pub show_grid: bool,
451 pub legend_loc: String,
453 pub dpi: u32,
455}
456
457#[derive(Clone, Debug)]
459pub struct DataSeries {
460 pub label: String,
462 pub x: Vec<f64>,
464 pub y: Vec<f64>,
466 pub linestyle: String,
468 pub colour: String,
470 pub linewidth: f64,
472}
473
474impl DataSeries {
475 pub fn new(label: impl Into<String>, x: Vec<f64>, y: Vec<f64>) -> Self {
477 Self {
478 label: label.into(),
479 x,
480 y,
481 linestyle: "-".into(),
482 colour: "blue".into(),
483 linewidth: 1.5,
484 }
485 }
486}
487
488impl MatplotlibFigure {
489 pub fn new(
491 title: impl Into<String>,
492 xlabel: impl Into<String>,
493 ylabel: impl Into<String>,
494 ) -> Self {
495 Self {
496 title: title.into(),
497 xlabel: xlabel.into(),
498 ylabel: ylabel.into(),
499 width: 8.0,
500 height: 6.0,
501 series: Vec::new(),
502 show_grid: true,
503 legend_loc: "best".into(),
504 dpi: 150,
505 }
506 }
507
508 pub fn add_series(&mut self, s: DataSeries) {
510 self.series.push(s);
511 }
512
513 pub fn to_json(&self) -> String {
515 let mut j = String::new();
516 let _ = writeln!(j, "{{");
517 let _ = writeln!(j, r#" "title": "{}","#, self.title);
518 let _ = writeln!(j, r#" "xlabel": "{}","#, self.xlabel);
519 let _ = writeln!(j, r#" "ylabel": "{}","#, self.ylabel);
520 let _ = writeln!(j, r#" "figsize": [{}, {}],"#, self.width, self.height);
521 let _ = writeln!(j, r#" "dpi": {},"#, self.dpi);
522 let _ = writeln!(j, r#" "grid": {},"#, self.show_grid);
523 let _ = writeln!(j, r#" "legend_loc": "{}","#, self.legend_loc);
524 let _ = writeln!(j, r#" "series": ["#);
525 for (i, s) in self.series.iter().enumerate() {
526 let comma = if i + 1 < self.series.len() { "," } else { "" };
527 let _ = writeln!(j, " {{");
528 let _ = writeln!(j, r#" "label": "{}","#, s.label);
529 let _ = writeln!(j, r#" "linestyle": "{}","#, s.linestyle);
530 let _ = writeln!(j, r#" "color": "{}","#, s.colour);
531 let _ = writeln!(j, r#" "linewidth": {},"#, s.linewidth);
532 let x_str: Vec<String> = s.x.iter().map(|v| v.to_string()).collect();
533 let y_str: Vec<String> = s.y.iter().map(|v| v.to_string()).collect();
534 let _ = writeln!(j, r#" "x": [{}],"#, x_str.join(", "));
535 let _ = writeln!(j, r#" "y": [{}]"#, y_str.join(", "));
536 let _ = writeln!(j, " }}{}", comma);
537 }
538 let _ = writeln!(j, " ]");
539 let _ = writeln!(j, "}}");
540 j
541 }
542
543 pub fn to_python_script(&self) -> String {
545 let mut s = String::new();
546 let _ = writeln!(s, "import matplotlib.pyplot as plt");
547 let _ = writeln!(s, "import numpy as np");
548 let _ = writeln!(
549 s,
550 "fig, ax = plt.subplots(figsize=({}, {}))",
551 self.width, self.height
552 );
553 for series in &self.series {
554 let x_vals: Vec<String> = series.x.iter().map(|v| v.to_string()).collect();
555 let y_vals: Vec<String> = series.y.iter().map(|v| v.to_string()).collect();
556 let _ = writeln!(
557 s,
558 "ax.plot([{}], [{}], '{}', color='{}', linewidth={}, label='{}')",
559 x_vals.join(", "),
560 y_vals.join(", "),
561 series.linestyle,
562 series.colour,
563 series.linewidth,
564 series.label
565 );
566 }
567 let _ = writeln!(s, "ax.set_xlabel('{}')", self.xlabel);
568 let _ = writeln!(s, "ax.set_ylabel('{}')", self.ylabel);
569 let _ = writeln!(s, "ax.set_title('{}')", self.title);
570 if self.show_grid {
571 let _ = writeln!(s, "ax.grid(True)");
572 }
573 let _ = writeln!(s, "ax.legend(loc='{}')", self.legend_loc);
574 let _ = writeln!(s, "plt.tight_layout()");
575 let _ = writeln!(s, "plt.savefig('figure.png', dpi={})", self.dpi);
576 s
577 }
578}
579
580#[derive(Clone, Debug)]
586pub struct D3Node {
587 pub id: String,
589 pub group: u32,
591 pub radius: f64,
593 pub label: String,
595}
596
597#[derive(Clone, Debug)]
599pub struct D3Edge {
600 pub source: String,
602 pub target: String,
604 pub value: f64,
606}
607
608#[derive(Clone, Debug, Default)]
610pub struct D3ForceGraph {
611 pub nodes: Vec<D3Node>,
613 pub edges: Vec<D3Edge>,
615}
616
617impl D3ForceGraph {
618 pub fn new() -> Self {
620 Self::default()
621 }
622
623 pub fn add_node(&mut self, id: impl Into<String>, group: u32, radius: f64) {
625 self.nodes.push(D3Node {
626 id: id.into(),
627 group,
628 radius,
629 label: String::new(),
630 });
631 }
632
633 pub fn add_edge(&mut self, source: impl Into<String>, target: impl Into<String>, value: f64) {
635 self.edges.push(D3Edge {
636 source: source.into(),
637 target: target.into(),
638 value,
639 });
640 }
641
642 pub fn to_json(&self) -> String {
644 let mut j = String::new();
645 let _ = writeln!(j, "{{");
646 let _ = writeln!(j, r#" "nodes": ["#);
647 for (i, n) in self.nodes.iter().enumerate() {
648 let comma = if i + 1 < self.nodes.len() { "," } else { "" };
649 let _ = writeln!(
650 j,
651 r#" {{"id": "{}", "group": {}, "radius": {}}}{}"#,
652 n.id, n.group, n.radius, comma
653 );
654 }
655 let _ = writeln!(j, r#" ],"#);
656 let _ = writeln!(j, r#" "links": ["#);
657 for (i, e) in self.edges.iter().enumerate() {
658 let comma = if i + 1 < self.edges.len() { "," } else { "" };
659 let _ = writeln!(
660 j,
661 r#" {{"source": "{}", "target": "{}", "value": {}}}{}"#,
662 e.source, e.target, e.value, comma
663 );
664 }
665 let _ = writeln!(j, r#" ]"#);
666 let _ = writeln!(j, "}}");
667 j
668 }
669}
670
671#[derive(Clone, Debug)]
673pub struct D3ContourData {
674 pub width: usize,
676 pub height: usize,
678 pub values: Vec<f64>,
680 pub thresholds: Vec<f64>,
682 pub x_extent: [f64; 2],
684 pub y_extent: [f64; 2],
686}
687
688impl D3ContourData {
689 pub fn new(width: usize, height: usize, x_extent: [f64; 2], y_extent: [f64; 2]) -> Self {
691 Self {
692 width,
693 height,
694 values: vec![0.0; width * height],
695 thresholds: Vec::new(),
696 x_extent,
697 y_extent,
698 }
699 }
700
701 pub fn set(&mut self, ix: usize, iy: usize, val: f64) {
703 self.values[iy * self.width + ix] = val;
704 }
705
706 pub fn add_threshold(&mut self, t: f64) {
708 self.thresholds.push(t);
709 }
710
711 pub fn auto_thresholds(&mut self, n: usize) {
713 let vmin = self.values.iter().cloned().fold(f64::INFINITY, f64::min);
714 let vmax = self
715 .values
716 .iter()
717 .cloned()
718 .fold(f64::NEG_INFINITY, f64::max);
719 self.thresholds = (0..n)
720 .map(|i| vmin + (vmax - vmin) * i as f64 / (n - 1).max(1) as f64)
721 .collect();
722 }
723
724 pub fn to_json(&self) -> String {
726 let mut j = String::new();
727 let _ = writeln!(j, "{{");
728 let _ = writeln!(j, r#" "width": {},"#, self.width);
729 let _ = writeln!(j, r#" "height": {},"#, self.height);
730 let _ = writeln!(
731 j,
732 r#" "x_extent": [{}, {}],"#,
733 self.x_extent[0], self.x_extent[1]
734 );
735 let _ = writeln!(
736 j,
737 r#" "y_extent": [{}, {}],"#,
738 self.y_extent[0], self.y_extent[1]
739 );
740 let thresh_str: Vec<String> = self.thresholds.iter().map(|v| v.to_string()).collect();
741 let _ = writeln!(j, r#" "thresholds": [{}],"#, thresh_str.join(", "));
742 let val_str: Vec<String> = self.values.iter().map(|v| v.to_string()).collect();
743 let _ = writeln!(j, r#" "values": [{}]"#, val_str.join(", "));
744 let _ = writeln!(j, "}}");
745 j
746 }
747}
748
749#[derive(Clone, Debug)]
755pub struct WebGlBuffer {
756 pub name: String,
758 pub data: Vec<f32>,
760 pub indices: Vec<u32>,
762 pub stride: u32,
764 pub attributes: Vec<(String, u32, u32)>, }
767
768impl WebGlBuffer {
769 pub fn from_mesh(
771 name: impl Into<String>,
772 positions: &[[f32; 3]],
773 normals: &[[f32; 3]],
774 uvs: &[[f32; 2]],
775 indices: Vec<u32>,
776 ) -> Self {
777 let mut data = Vec::with_capacity(positions.len() * 8);
778 for i in 0..positions.len() {
779 data.extend_from_slice(&positions[i]);
780 if i < normals.len() {
781 data.extend_from_slice(&normals[i]);
782 } else {
783 data.extend_from_slice(&[0.0, 1.0, 0.0]);
784 }
785 if i < uvs.len() {
786 data.extend_from_slice(&uvs[i]);
787 } else {
788 data.extend_from_slice(&[0.0, 0.0]);
789 }
790 }
791 let stride = 8 * 4; let attributes = vec![
793 ("position".to_string(), 0u32, 3u32),
794 ("normal".to_string(), 12u32, 3u32),
795 ("uv".to_string(), 24u32, 2u32),
796 ];
797 Self {
798 name: name.into(),
799 data,
800 indices,
801 stride,
802 attributes,
803 }
804 }
805
806 pub fn to_json_meta(&self) -> String {
808 let mut j = String::new();
809 let _ = writeln!(j, "{{");
810 let _ = writeln!(j, r#" "name": "{}","#, self.name);
811 let _ = writeln!(j, r#" "vertex_count": {},"#, self.data.len() / 8);
812 let _ = writeln!(j, r#" "index_count": {},"#, self.indices.len());
813 let _ = writeln!(j, r#" "stride": {},"#, self.stride);
814 let _ = writeln!(j, r#" "attributes": ["#);
815 for (i, (aname, offset, count)) in self.attributes.iter().enumerate() {
816 let comma = if i + 1 < self.attributes.len() {
817 ","
818 } else {
819 ""
820 };
821 let _ = writeln!(
822 j,
823 r#" {{"name": "{}", "offset": {}, "components": {}}}{}"#,
824 aname, offset, count, comma
825 );
826 }
827 let _ = writeln!(j, " ]");
828 let _ = writeln!(j, "}}");
829 j
830 }
831
832 pub fn to_bytes(&self) -> Vec<u8> {
834 let mut bytes = Vec::with_capacity(self.data.len() * 4);
835 for &v in &self.data {
836 bytes.extend_from_slice(&v.to_le_bytes());
837 }
838 bytes
839 }
840
841 pub fn vertex_count(&self) -> usize {
843 self.data.len() / 8
844 }
845}
846
847#[derive(Clone, Debug)]
853pub struct GltfPhysicsBody {
854 pub node_name: String,
856 pub mass: f64,
858 pub linear_velocity: [f64; 3],
860 pub angular_velocity: [f64; 3],
862 pub is_static: bool,
864 pub friction: f64,
866 pub restitution: f64,
868 pub collider: GltfColliderShape,
870}
871
872#[derive(Clone, Debug)]
874pub enum GltfColliderShape {
875 Sphere(f64),
877 Box([f64; 3]),
879 Capsule(f64, f64),
881 ConvexHull,
883 TriMesh,
885}
886
887impl GltfPhysicsBody {
888 pub fn sphere(node_name: impl Into<String>, mass: f64, radius: f64) -> Self {
890 Self {
891 node_name: node_name.into(),
892 mass,
893 linear_velocity: [0.0; 3],
894 angular_velocity: [0.0; 3],
895 is_static: false,
896 friction: 0.5,
897 restitution: 0.3,
898 collider: GltfColliderShape::Sphere(radius),
899 }
900 }
901
902 pub fn to_gltf_json(&self) -> String {
904 let mut j = String::new();
905 let _ = writeln!(j, "{{");
906 let _ = writeln!(j, r#" "KHR_physics_rigid_bodies": {{"#);
907 let _ = writeln!(j, r#" "motion": {{"#);
908 let _ = writeln!(j, r#" "isKinematic": {},"#, self.is_static);
909 let _ = writeln!(j, r#" "mass": {},"#, self.mass);
910 let _ = writeln!(
911 j,
912 r#" "linearVelocity": [{}, {}, {}],"#,
913 self.linear_velocity[0], self.linear_velocity[1], self.linear_velocity[2]
914 );
915 let _ = writeln!(
916 j,
917 r#" "angularVelocity": [{}, {}, {}]"#,
918 self.angular_velocity[0], self.angular_velocity[1], self.angular_velocity[2]
919 );
920 let _ = writeln!(j, r#" }},"#);
921 let _ = writeln!(j, r#" "collider": {{"#);
922 match &self.collider {
923 GltfColliderShape::Sphere(r) => {
924 let _ = writeln!(j, r#" "shape": "sphere","#);
925 let _ = writeln!(j, r#" "sphere": {{"radius": {}}}"#, r);
926 }
927 GltfColliderShape::Box(he) => {
928 let _ = writeln!(j, r#" "shape": "box","#);
929 let _ = writeln!(
930 j,
931 r#" "box": {{"halfExtents": [{}, {}, {}]}}"#,
932 he[0], he[1], he[2]
933 );
934 }
935 GltfColliderShape::Capsule(r, h) => {
936 let _ = writeln!(j, r#" "shape": "capsule","#);
937 let _ = writeln!(
938 j,
939 r#" "capsule": {{"radius": {}, "height": {}}}"#,
940 r,
941 h * 2.0
942 );
943 }
944 GltfColliderShape::ConvexHull => {
945 let _ = writeln!(j, r#" "shape": "convexHull""#);
946 }
947 GltfColliderShape::TriMesh => {
948 let _ = writeln!(j, r#" "shape": "trimesh""#);
949 }
950 }
951 let _ = writeln!(j, r#" }},"#);
952 let _ = writeln!(
953 j,
954 r#" "physicsMaterial": {{"friction": {}, "restitution": {}}}"#,
955 self.friction, self.restitution
956 );
957 let _ = writeln!(j, r#" }}"#);
958 let _ = writeln!(j, "}}");
959 j
960 }
961}
962
963#[derive(Clone, Debug, Default)]
965pub struct GltfPhysicsScene {
966 pub name: String,
968 pub bodies: Vec<GltfPhysicsBody>,
970 pub gravity: [f64; 3],
972 pub fixed_dt: f64,
974}
975
976impl GltfPhysicsScene {
977 pub fn new(name: impl Into<String>) -> Self {
979 Self {
980 name: name.into(),
981 bodies: Vec::new(),
982 gravity: [0.0, -9.81, 0.0],
983 fixed_dt: 1.0 / 60.0,
984 }
985 }
986
987 pub fn add_body(&mut self, body: GltfPhysicsBody) {
989 self.bodies.push(body);
990 }
991
992 pub fn to_scene_json(&self) -> String {
994 let mut j = String::new();
995 let _ = writeln!(j, "{{");
996 let _ = writeln!(j, r#" "name": "{}","#, self.name);
997 let _ = writeln!(
998 j,
999 r#" "gravity": [{}, {}, {}],"#,
1000 self.gravity[0], self.gravity[1], self.gravity[2]
1001 );
1002 let _ = writeln!(j, r#" "fixedTimestep": {},"#, self.fixed_dt);
1003 let _ = writeln!(j, r#" "bodies": ["#);
1004 for (i, body) in self.bodies.iter().enumerate() {
1005 let comma = if i + 1 < self.bodies.len() { "," } else { "" };
1006 let _ = write!(j, r#" {{"node": "{}"}}{}"#, body.node_name, comma);
1007 let _ = writeln!(j);
1008 }
1009 let _ = writeln!(j, " ]");
1010 let _ = writeln!(j, "}}");
1011 j
1012 }
1013}
1014
1015#[derive(Clone, Debug)]
1021pub struct VdbVoxel {
1022 pub ijk: [i32; 3],
1024 pub value: f64,
1026}
1027
1028#[derive(Clone, Debug)]
1030pub struct VdbSparseGrid {
1031 pub name: String,
1033 pub grid_type: String,
1035 pub voxel_size: f64,
1037 pub background: f64,
1039 pub voxels: Vec<VdbVoxel>,
1041}
1042
1043impl VdbSparseGrid {
1044 pub fn new(name: impl Into<String>, voxel_size: f64, background: f64) -> Self {
1046 Self {
1047 name: name.into(),
1048 grid_type: "float".into(),
1049 voxel_size,
1050 background,
1051 voxels: Vec::new(),
1052 }
1053 }
1054
1055 pub fn add_voxel(&mut self, i: i32, j: i32, k: i32, value: f64) {
1057 self.voxels.push(VdbVoxel {
1058 ijk: [i, j, k],
1059 value,
1060 });
1061 }
1062
1063 pub fn from_scalar_field(
1065 field: &ScalarField3D,
1066 threshold: f64,
1067 name: impl Into<String>,
1068 ) -> Self {
1069 let mut grid = Self::new(name, field.dx, 0.0);
1070 for ix in 0..field.nx {
1071 for iy in 0..field.ny {
1072 for iz in 0..field.nz {
1073 let v = field.get(ix, iy, iz);
1074 if v.abs() > threshold {
1075 grid.add_voxel(ix as i32, iy as i32, iz as i32, v);
1076 }
1077 }
1078 }
1079 }
1080 grid
1081 }
1082
1083 pub fn write_ascii_header(&self) -> String {
1085 let mut s = String::new();
1086 let _ = writeln!(s, "#VDB ASCII v1.0");
1087 let _ = writeln!(s, "name: {}", self.name);
1088 let _ = writeln!(s, "type: {}", self.grid_type);
1089 let _ = writeln!(s, "voxel_size: {}", self.voxel_size);
1090 let _ = writeln!(s, "background: {}", self.background);
1091 let _ = writeln!(s, "n_active: {}", self.voxels.len());
1092 for v in &self.voxels {
1093 let _ = writeln!(s, "v {} {} {} {}", v.ijk[0], v.ijk[1], v.ijk[2], v.value);
1094 }
1095 s
1096 }
1097
1098 pub fn n_active(&self) -> usize {
1100 self.voxels.len()
1101 }
1102
1103 pub fn bbox(&self) -> [(i32, i32); 3] {
1105 if self.voxels.is_empty() {
1106 return [(0, 0); 3];
1107 }
1108 let mut lo = self.voxels[0].ijk;
1109 let mut hi = self.voxels[0].ijk;
1110 for v in &self.voxels {
1111 for d in 0..3 {
1112 if v.ijk[d] < lo[d] {
1113 lo[d] = v.ijk[d];
1114 }
1115 if v.ijk[d] > hi[d] {
1116 hi[d] = v.ijk[d];
1117 }
1118 }
1119 }
1120 [(lo[0], hi[0]), (lo[1], hi[1]), (lo[2], hi[2])]
1121 }
1122}
1123
1124#[derive(Clone, Debug)]
1130pub struct ExrChannel {
1131 pub name: String,
1133 pub data: Vec<f32>,
1135 pub pixel_type: String,
1137}
1138
1139impl ExrChannel {
1140 pub fn new(name: impl Into<String>, width: usize, height: usize) -> Self {
1142 Self {
1143 name: name.into(),
1144 data: vec![0.0; width * height],
1145 pixel_type: "FLOAT".into(),
1146 }
1147 }
1148
1149 pub fn fill_from<F: Fn(usize, usize) -> f32>(&mut self, width: usize, height: usize, f: F) {
1151 for y in 0..height {
1152 for x in 0..width {
1153 self.data[y * width + x] = f(x, y);
1154 }
1155 }
1156 }
1157}
1158
1159#[derive(Clone, Debug)]
1161pub struct OpenExrImage {
1162 pub width: usize,
1164 pub height: usize,
1166 pub channels: Vec<ExrChannel>,
1168 pub compression: String,
1170 pub metadata: HashMap<String, String>,
1172}
1173
1174impl OpenExrImage {
1175 pub fn new(width: usize, height: usize) -> Self {
1177 Self {
1178 width,
1179 height,
1180 channels: Vec::new(),
1181 compression: "PIZ".into(),
1182 metadata: HashMap::new(),
1183 }
1184 }
1185
1186 pub fn add_channel(&mut self, ch: ExrChannel) {
1188 self.channels.push(ch);
1189 }
1190
1191 pub fn set_meta(&mut self, key: impl Into<String>, val: impl Into<String>) {
1193 self.metadata.insert(key.into(), val.into());
1194 }
1195
1196 pub fn write_header(&self) -> String {
1198 let mut h = String::new();
1199 let _ = writeln!(h, "OPENEXR ASCII HEADER");
1200 let _ = writeln!(h, "width: {}", self.width);
1201 let _ = writeln!(h, "height: {}", self.height);
1202 let _ = writeln!(h, "compression: {}", self.compression);
1203 let _ = writeln!(h, "channels:");
1204 for ch in &self.channels {
1205 let _ = writeln!(h, " {} ({})", ch.name, ch.pixel_type);
1206 }
1207 for (k, v) in &self.metadata {
1208 let _ = writeln!(h, "meta {} = {}", k, v);
1209 }
1210 h
1211 }
1212
1213 pub fn total_bytes(&self) -> usize {
1215 self.channels.iter().map(|ch| ch.data.len() * 4).sum()
1216 }
1217
1218 pub fn rgba(width: usize, height: usize) -> Self {
1220 let mut img = Self::new(width, height);
1221 img.add_channel(ExrChannel::new("R", width, height));
1222 img.add_channel(ExrChannel::new("G", width, height));
1223 img.add_channel(ExrChannel::new("B", width, height));
1224 img.add_channel(ExrChannel::new("A", width, height));
1225 img
1226 }
1227}
1228
1229#[derive(Clone, Debug)]
1235pub struct CinemaParameter {
1236 pub name: String,
1238 pub values: Vec<String>,
1240 pub is_numeric: bool,
1242}
1243
1244impl CinemaParameter {
1245 pub fn numeric_range(name: impl Into<String>, values: Vec<f64>) -> Self {
1247 Self {
1248 name: name.into(),
1249 values: values.iter().map(|v| v.to_string()).collect(),
1250 is_numeric: true,
1251 }
1252 }
1253
1254 pub fn string_param(name: impl Into<String>, values: Vec<String>) -> Self {
1256 Self {
1257 name: name.into(),
1258 values,
1259 is_numeric: false,
1260 }
1261 }
1262}
1263
1264#[derive(Clone, Debug)]
1266pub struct CinemaDatabase {
1267 pub name: String,
1269 pub parameters: Vec<CinemaParameter>,
1271 pub file_ext: String,
1273 pub entries: Vec<HashMap<String, String>>,
1275}
1276
1277impl CinemaDatabase {
1278 pub fn new(name: impl Into<String>) -> Self {
1280 Self {
1281 name: name.into(),
1282 parameters: Vec::new(),
1283 file_ext: "png".into(),
1284 entries: Vec::new(),
1285 }
1286 }
1287
1288 pub fn add_parameter(&mut self, param: CinemaParameter) {
1290 self.parameters.push(param);
1291 }
1292
1293 pub fn add_entry(&mut self, param_values: HashMap<String, String>) {
1295 self.entries.push(param_values);
1296 }
1297
1298 pub fn total_images(&self) -> usize {
1300 self.parameters
1301 .iter()
1302 .map(|p| p.values.len().max(1))
1303 .product()
1304 }
1305
1306 pub fn write_csv_header(&self) -> String {
1308 let mut cols: Vec<String> = self.parameters.iter().map(|p| p.name.clone()).collect();
1309 cols.push("FILE".into());
1310 cols.join(",")
1311 }
1312
1313 pub fn write_csv_body(&self) -> String {
1315 let mut rows = Vec::new();
1316 for entry in &self.entries {
1317 let mut row_vals: Vec<String> = self
1318 .parameters
1319 .iter()
1320 .map(|p| entry.get(&p.name).cloned().unwrap_or_default())
1321 .collect();
1322 row_vals.push(entry.get("FILE").cloned().unwrap_or_default());
1323 rows.push(row_vals.join(","));
1324 }
1325 rows.join("\n")
1326 }
1327
1328 pub fn write_csv(&self) -> String {
1330 format!("{}\n{}", self.write_csv_header(), self.write_csv_body())
1331 }
1332
1333 pub fn write_info_json(&self) -> String {
1335 let mut j = String::new();
1336 let _ = writeln!(j, "{{");
1337 let _ = writeln!(j, r#" "name_pattern": "{{FILE}}","#);
1338 let _ = writeln!(j, r#" "arguments": {{"#);
1339 for (i, p) in self.parameters.iter().enumerate() {
1340 let comma = if i + 1 < self.parameters.len() {
1341 ","
1342 } else {
1343 ""
1344 };
1345 let vals: Vec<String> = p.values.iter().map(|v| format!(r#""{}""#, v)).collect();
1346 let _ = writeln!(j, r#" "{}": [{}]{}"#, p.name, vals.join(", "), comma);
1347 }
1348 let _ = writeln!(j, r#" }}"#);
1349 let _ = writeln!(j, "}}");
1350 j
1351 }
1352}
1353
1354#[derive(Clone, Debug, PartialEq)]
1360pub enum ColourMap {
1361 Viridis,
1363 Plasma,
1365 CoolWarm,
1367 Greyscale,
1369 Hot,
1371}
1372
1373impl ColourMap {
1374 pub fn map(&self, t: f64) -> [f64; 4] {
1376 let t = t.clamp(0.0, 1.0);
1377 match self {
1378 ColourMap::Viridis => {
1379 let r = 0.267004 + t * (0.004874 + t * (0.329415 + t * (-0.001674)));
1381 let g = 0.004874 + t * (0.872325 + t * (-0.301631 + t * (-0.1)));
1382 let b = 0.329415 + t * (-0.635877 + t * (0.914499 + t * (-0.401658)));
1383 [r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0), 1.0]
1384 }
1385 ColourMap::Plasma => {
1386 let r = 0.050383 + t * (2.566707 + t * (-2.237019 + t * (0.816839)));
1387 let g = 0.029803 + t * (-0.390895 + t * (1.607541 + t * (-0.892576)));
1388 let b = 0.527975 + t * (1.016567 + t * (-2.476404 + t * (1.476605)));
1389 [r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0), 1.0]
1390 }
1391 ColourMap::CoolWarm => {
1392 let r = if t < 0.5 { 2.0 * t } else { 1.0 };
1393 let b = if t > 0.5 { 2.0 * (1.0 - t) } else { 1.0 };
1394 let g = 1.0 - 2.0 * (t - 0.5).abs();
1395 [r, g.max(0.0), b, 1.0]
1396 }
1397 ColourMap::Greyscale => [t, t, t, 1.0],
1398 ColourMap::Hot => {
1399 let r = (t * 3.0).min(1.0);
1400 let g = (t * 3.0 - 1.0).clamp(0.0, 1.0);
1401 let b = (t * 3.0 - 2.0).clamp(0.0, 1.0);
1402 [r, g, b, 1.0]
1403 }
1404 }
1405 }
1406
1407 pub fn name(&self) -> &str {
1409 match self {
1410 ColourMap::Viridis => "viridis",
1411 ColourMap::Plasma => "plasma",
1412 ColourMap::CoolWarm => "coolwarm",
1413 ColourMap::Greyscale => "greyscale",
1414 ColourMap::Hot => "hot",
1415 }
1416 }
1417}
1418
1419pub fn scalar_field_to_rgba(
1421 slice: &[f64],
1422 width: usize,
1423 height: usize,
1424 vmin: f64,
1425 vmax: f64,
1426 cmap: &ColourMap,
1427) -> Vec<u8> {
1428 let range = (vmax - vmin).max(1e-30);
1429 let mut bytes = Vec::with_capacity(width * height * 4);
1430 for &v in slice {
1431 let t = (v - vmin) / range;
1432 let rgba = cmap.map(t);
1433 for c in &rgba {
1434 bytes.push((c * 255.0).clamp(0.0, 255.0) as u8);
1435 }
1436 }
1437 bytes
1438}
1439
1440#[derive(Clone, Debug)]
1446pub struct AnimFrame {
1447 pub time: f64,
1449 pub index: usize,
1451 pub scalars: HashMap<String, Vec<f64>>,
1453 pub vectors: HashMap<String, Vec<f64>>,
1455}
1456
1457impl AnimFrame {
1458 pub fn new(time: f64, index: usize) -> Self {
1460 Self {
1461 time,
1462 index,
1463 scalars: HashMap::new(),
1464 vectors: HashMap::new(),
1465 }
1466 }
1467
1468 pub fn add_scalar(&mut self, name: impl Into<String>, data: Vec<f64>) {
1470 self.scalars.insert(name.into(), data);
1471 }
1472
1473 pub fn add_vector(&mut self, name: impl Into<String>, data: Vec<f64>) {
1475 self.vectors.insert(name.into(), data);
1476 }
1477}
1478
1479#[derive(Clone, Debug, Default)]
1481pub struct AnimSequence {
1482 pub frames: Vec<AnimFrame>,
1484 pub fps: f64,
1486}
1487
1488impl AnimSequence {
1489 pub fn new(fps: f64) -> Self {
1491 Self {
1492 frames: Vec::new(),
1493 fps,
1494 }
1495 }
1496
1497 pub fn push(&mut self, frame: AnimFrame) {
1499 self.frames.push(frame);
1500 }
1501
1502 pub fn duration(&self) -> f64 {
1504 if self.frames.is_empty() {
1505 0.0
1506 } else {
1507 self.frames
1508 .last()
1509 .expect("collection should not be empty")
1510 .time
1511 }
1512 }
1513
1514 pub fn get_frame(&self, idx: usize) -> Option<&AnimFrame> {
1516 self.frames.get(idx)
1517 }
1518
1519 pub fn write_manifest(&self) -> String {
1521 let mut j = String::new();
1522 let _ = writeln!(j, "{{");
1523 let _ = writeln!(j, r#" "fps": {},"#, self.fps);
1524 let _ = writeln!(j, r#" "n_frames": {},"#, self.frames.len());
1525 let _ = writeln!(j, r#" "duration": {},"#, self.duration());
1526 if !self.frames.is_empty() {
1527 let scalar_names: Vec<String> = self.frames[0].scalars.keys().cloned().collect();
1528 let names_str: Vec<String> =
1529 scalar_names.iter().map(|n| format!(r#""{}""#, n)).collect();
1530 let _ = writeln!(j, r#" "scalar_fields": [{}]"#, names_str.join(", "));
1531 }
1532 let _ = writeln!(j, "}}");
1533 j
1534 }
1535}
1536
1537#[cfg(test)]
1542mod tests {
1543 use super::*;
1544
1545 #[test]
1548 fn test_scalar_field_set_get() {
1549 let mut f = ScalarField3D::new(4, 4, 4, 0.1, [0.0; 3]);
1550 f.set(1, 2, 3, 3.125);
1551 assert!((f.get(1, 2, 3) - 3.125).abs() < 1e-10);
1552 }
1553
1554 #[test]
1555 fn test_scalar_field_min_max() {
1556 let mut f = ScalarField3D::new(2, 2, 2, 0.1, [0.0; 3]);
1557 f.set(0, 0, 0, -5.0);
1558 f.set(1, 1, 1, 10.0);
1559 assert!((f.min_val() - (-5.0)).abs() < 1e-10);
1560 assert!((f.max_val() - 10.0).abs() < 1e-10);
1561 }
1562
1563 #[test]
1564 fn test_scalar_field_default_zero() {
1565 let f = ScalarField3D::new(3, 3, 3, 0.1, [0.0; 3]);
1566 assert_eq!(f.get(1, 1, 1), 0.0);
1567 }
1568
1569 #[test]
1572 fn test_paraview_state_contains_filename() {
1573 let cfg = ParaviewStateConfig::default_config("test.vtu");
1574 let s = write_paraview_state(&cfg);
1575 assert!(s.contains("test.vtu"));
1576 }
1577
1578 #[test]
1579 fn test_paraview_state_contains_xml_header() {
1580 let cfg = ParaviewStateConfig::default_config("test.vtu");
1581 let s = write_paraview_state(&cfg);
1582 assert!(s.contains(r#"<?xml version="1.0"?>"#));
1583 }
1584
1585 #[test]
1586 fn test_paraview_state_contains_camera() {
1587 let mut cfg = ParaviewStateConfig::default_config("test.vtu");
1588 cfg.camera_pos = [1.0, 2.0, 3.0];
1589 let s = write_paraview_state(&cfg);
1590 assert!(s.contains("CameraPosition"));
1591 }
1592
1593 #[test]
1596 fn test_visit_index_contains_files() {
1597 let mut db = VisItDatabase::new("sim", "vtk", 3);
1598 db.add_time(0.0);
1599 db.add_time(0.1);
1600 let idx = db.write_visit_index();
1601 assert!(idx.contains("sim000000.vtk"));
1602 assert!(idx.contains("sim000001.vtk"));
1603 }
1604
1605 #[test]
1606 fn test_visit_manifest_json() {
1607 let mut db = VisItDatabase::new("run", "vtu", 3);
1608 db.add_time(0.0);
1609 db.add_variable("pressure");
1610 let m = db.write_manifest();
1611 assert!(m.contains("\"pressure\""));
1612 assert!(m.contains("\"n_times\": 1"));
1613 }
1614
1615 #[test]
1618 fn test_blender_script_imports_bpy() {
1619 let cfg = BlenderExportConfig::new("obj1", "/tmp/out.blend");
1620 let s = cfg.generate_script("mesh.obj");
1621 assert!(s.contains("import bpy"));
1622 }
1623
1624 #[test]
1625 fn test_blender_script_contains_object_name() {
1626 let cfg = BlenderExportConfig::new("my_obj", "/tmp/out.blend");
1627 let s = cfg.generate_script("mesh.obj");
1628 assert!(s.contains("my_obj"));
1629 }
1630
1631 #[test]
1632 fn test_blender_volume_script() {
1633 let cfg = BlenderExportConfig::new("vol", "/tmp/vol.blend");
1634 let s = cfg.generate_volume_script("smoke.vdb");
1635 assert!(s.contains("smoke.vdb"));
1636 assert!(s.contains("ShaderNodeEmission"));
1637 }
1638
1639 #[test]
1642 fn test_matplotlib_json_contains_title() {
1643 let mut fig = MatplotlibFigure::new("My Plot", "X", "Y");
1644 fig.add_series(DataSeries::new("data", vec![1.0, 2.0], vec![3.0, 4.0]));
1645 let j = fig.to_json();
1646 assert!(j.contains("My Plot"));
1647 }
1648
1649 #[test]
1650 fn test_matplotlib_python_script() {
1651 let fig = MatplotlibFigure::new("Test", "time", "value");
1652 let s = fig.to_python_script();
1653 assert!(s.contains("import matplotlib.pyplot as plt"));
1654 }
1655
1656 #[test]
1657 fn test_matplotlib_series_data_in_json() {
1658 let mut fig = MatplotlibFigure::new("T", "x", "y");
1659 fig.add_series(DataSeries::new("series1", vec![0.0, 1.0], vec![2.0, 3.0]));
1660 let j = fig.to_json();
1661 assert!(j.contains("series1"));
1662 }
1663
1664 #[test]
1667 fn test_d3_graph_json_nodes() {
1668 let mut g = D3ForceGraph::new();
1669 g.add_node("A", 1, 5.0);
1670 g.add_node("B", 2, 3.0);
1671 g.add_edge("A", "B", 1.0);
1672 let j = g.to_json();
1673 assert!(j.contains(r#""id": "A""#));
1674 assert!(j.contains(r#""source": "A""#));
1675 }
1676
1677 #[test]
1678 fn test_d3_contour_json() {
1679 let mut cd = D3ContourData::new(4, 4, [0.0, 1.0], [0.0, 1.0]);
1680 cd.set(2, 2, 1.5);
1681 cd.auto_thresholds(5);
1682 let j = cd.to_json();
1683 assert!(j.contains("\"width\": 4"));
1684 assert!(!cd.thresholds.is_empty());
1685 }
1686
1687 #[test]
1690 fn test_webgl_buffer_vertex_count() {
1691 let pos = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1692 let nor = vec![[0.0f32, 0.0, 1.0]; 3];
1693 let uv = vec![[0.0f32, 0.0]; 3];
1694 let idx = vec![0u32, 1, 2];
1695 let buf = WebGlBuffer::from_mesh("tri", &pos, &nor, &uv, idx);
1696 assert_eq!(buf.vertex_count(), 3);
1697 }
1698
1699 #[test]
1700 fn test_webgl_buffer_bytes_len() {
1701 let pos = vec![[0.0f32, 0.0, 0.0]];
1702 let nor = vec![[0.0f32, 0.0, 1.0]];
1703 let uv = vec![[0.0f32, 0.0]];
1704 let buf = WebGlBuffer::from_mesh("p", &pos, &nor, &uv, vec![0]);
1705 let bytes = buf.to_bytes();
1706 assert_eq!(bytes.len(), 8 * 4); }
1708
1709 #[test]
1710 fn test_webgl_buffer_meta_json() {
1711 let buf = WebGlBuffer::from_mesh("mesh", &[], &[], &[], vec![]);
1712 let j = buf.to_json_meta();
1713 assert!(j.contains("\"name\": \"mesh\""));
1714 }
1715
1716 #[test]
1719 fn test_gltf_physics_body_json_sphere() {
1720 let body = GltfPhysicsBody::sphere("node1", 1.0, 0.5);
1721 let j = body.to_gltf_json();
1722 assert!(j.contains("sphere"));
1723 assert!(j.contains("KHR_physics_rigid_bodies"));
1724 }
1725
1726 #[test]
1727 fn test_gltf_scene_json() {
1728 let mut scene = GltfPhysicsScene::new("test_scene");
1729 scene.add_body(GltfPhysicsBody::sphere("ball", 1.0, 0.5));
1730 let j = scene.to_scene_json();
1731 assert!(j.contains("test_scene"));
1732 assert!(j.contains("ball"));
1733 }
1734
1735 #[test]
1738 fn test_vdb_add_voxel() {
1739 let mut g = VdbSparseGrid::new("smoke", 0.1, 0.0);
1740 g.add_voxel(1, 2, 3, 0.5);
1741 assert_eq!(g.n_active(), 1);
1742 }
1743
1744 #[test]
1745 fn test_vdb_from_scalar_field() {
1746 let mut field = ScalarField3D::new(4, 4, 4, 0.1, [0.0; 3]);
1747 field.set(2, 2, 2, 5.0);
1748 let grid = VdbSparseGrid::from_scalar_field(&field, 1.0, "f");
1749 assert_eq!(grid.n_active(), 1);
1750 }
1751
1752 #[test]
1753 fn test_vdb_ascii_header() {
1754 let mut g = VdbSparseGrid::new("density", 0.05, 0.0);
1755 g.add_voxel(0, 0, 0, 1.0);
1756 let h = g.write_ascii_header();
1757 assert!(h.contains("#VDB ASCII"));
1758 assert!(h.contains("density"));
1759 }
1760
1761 #[test]
1762 fn test_vdb_bbox() {
1763 let mut g = VdbSparseGrid::new("vol", 0.1, 0.0);
1764 g.add_voxel(-1, 0, 0, 1.0);
1765 g.add_voxel(5, 3, 2, 2.0);
1766 let bb = g.bbox();
1767 assert_eq!(bb[0], (-1, 5));
1768 }
1769
1770 #[test]
1773 fn test_exr_rgba_channels() {
1774 let img = OpenExrImage::rgba(8, 8);
1775 assert_eq!(img.channels.len(), 4);
1776 }
1777
1778 #[test]
1779 fn test_exr_total_bytes() {
1780 let img = OpenExrImage::rgba(4, 4);
1781 assert_eq!(img.total_bytes(), 4 * 4 * 4 * 4); }
1783
1784 #[test]
1785 fn test_exr_header_contains_channels() {
1786 let img = OpenExrImage::rgba(2, 2);
1787 let h = img.write_header();
1788 assert!(h.contains("R"));
1789 assert!(h.contains("G"));
1790 }
1791
1792 #[test]
1793 fn test_exr_channel_fill() {
1794 let mut ch = ExrChannel::new("Z", 3, 3);
1795 ch.fill_from(3, 3, |x, y| (x + y) as f32);
1796 assert!((ch.data[4] - 2.0).abs() < 1e-6); }
1798
1799 #[test]
1802 fn test_cinema_total_images() {
1803 let mut db = CinemaDatabase::new("sim");
1804 db.add_parameter(CinemaParameter::numeric_range(
1805 "phi",
1806 vec![0.0, 90.0, 180.0, 270.0],
1807 ));
1808 db.add_parameter(CinemaParameter::numeric_range(
1809 "theta",
1810 vec![0.0, 45.0, 90.0],
1811 ));
1812 assert_eq!(db.total_images(), 12);
1813 }
1814
1815 #[test]
1816 fn test_cinema_csv_header() {
1817 let mut db = CinemaDatabase::new("test");
1818 db.add_parameter(CinemaParameter::numeric_range("time", vec![0.0, 1.0]));
1819 let h = db.write_csv_header();
1820 assert!(h.contains("time"));
1821 assert!(h.contains("FILE"));
1822 }
1823
1824 #[test]
1825 fn test_cinema_info_json() {
1826 let mut db = CinemaDatabase::new("db");
1827 db.add_parameter(CinemaParameter::numeric_range("angle", vec![0.0, 90.0]));
1828 let j = db.write_info_json();
1829 assert!(j.contains("\"angle\""));
1830 }
1831
1832 #[test]
1835 fn test_colourmap_greyscale_zero() {
1836 let rgba = ColourMap::Greyscale.map(0.0);
1837 for &c in &rgba[..3] {
1838 assert!((c - 0.0).abs() < 1e-10);
1839 }
1840 }
1841
1842 #[test]
1843 fn test_colourmap_greyscale_one() {
1844 let rgba = ColourMap::Greyscale.map(1.0);
1845 for &c in &rgba[..3] {
1846 assert!((c - 1.0).abs() < 1e-10);
1847 }
1848 }
1849
1850 #[test]
1851 fn test_colourmap_hot_red_at_quarter() {
1852 let rgba = ColourMap::Hot.map(0.4);
1853 assert!(rgba[0] > 0.5); }
1855
1856 #[test]
1857 fn test_scalar_to_rgba_len() {
1858 let data = vec![0.0, 0.5, 1.0, 0.25];
1859 let bytes = scalar_field_to_rgba(&data, 2, 2, 0.0, 1.0, &ColourMap::CoolWarm);
1860 assert_eq!(bytes.len(), 16); }
1862
1863 #[test]
1866 fn test_anim_sequence_duration() {
1867 let mut seq = AnimSequence::new(24.0);
1868 seq.push(AnimFrame::new(0.0, 0));
1869 seq.push(AnimFrame::new(1.0, 1));
1870 assert!((seq.duration() - 1.0).abs() < 1e-10);
1871 }
1872
1873 #[test]
1874 fn test_anim_frame_scalars() {
1875 let mut frame = AnimFrame::new(0.0, 0);
1876 frame.add_scalar("pressure", vec![1.0, 2.0, 3.0]);
1877 assert_eq!(frame.scalars["pressure"].len(), 3);
1878 }
1879
1880 #[test]
1881 fn test_anim_manifest_json() {
1882 let mut seq = AnimSequence::new(30.0);
1883 let mut f = AnimFrame::new(0.0, 0);
1884 f.add_scalar("vel", vec![0.0]);
1885 seq.push(f);
1886 let m = seq.write_manifest();
1887 assert!(m.contains("\"n_frames\": 1"));
1888 }
1889
1890 #[test]
1893 fn test_point3_dist() {
1894 let a = Point3::new(0.0, 0.0, 0.0);
1895 let b = Point3::new(3.0, 4.0, 0.0);
1896 assert!((a.dist(&b) - 5.0).abs() < 1e-10);
1897 }
1898
1899 #[test]
1900 fn test_point3_to_array() {
1901 let p = Point3::new(1.0, 2.0, 3.0);
1902 assert_eq!(p.to_array(), [1.0, 2.0, 3.0]);
1903 }
1904}