#[allow(unused_imports)]
use super::functions::*;
use crate::Result;
use oxiphysics_core::math::Vec3;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
pub struct VtuGrid {
pub points: Vec<[f64; 3]>,
pub cells: Vec<Vec<usize>>,
pub cell_types: Vec<VtkCellType>,
pub point_data: Vec<VtkDataArray>,
pub cell_data: Vec<VtkDataArray>,
}
impl VtuGrid {
pub fn new() -> Self {
Self {
points: Vec::new(),
cells: Vec::new(),
cell_types: Vec::new(),
point_data: Vec::new(),
cell_data: Vec::new(),
}
}
pub fn add_point(&mut self, p: [f64; 3]) -> usize {
let idx = self.points.len();
self.points.push(p);
idx
}
pub fn add_cell(&mut self, connectivity: Vec<usize>, cell_type: VtkCellType) {
self.cells.push(connectivity);
self.cell_types.push(cell_type);
}
pub fn add_point_scalar(&mut self, name: &str, values: Vec<f64>) {
self.point_data.push(VtkDataArray::Scalar {
name: name.to_owned(),
values,
});
}
pub fn add_point_vector(&mut self, name: &str, values: Vec<[f64; 3]>) {
self.point_data.push(VtkDataArray::Vector3 {
name: name.to_owned(),
values,
});
}
pub fn add_cell_scalar(&mut self, name: &str, values: Vec<f64>) {
self.cell_data.push(VtkDataArray::Scalar {
name: name.to_owned(),
values,
});
}
pub fn n_points(&self) -> usize {
self.points.len()
}
pub fn n_cells(&self) -> usize {
self.cells.len()
}
pub fn to_vtu_string(&self) -> String {
let mut s = String::new();
s.push_str("<?xml version=\"1.0\"?>\n");
s.push_str(
"<VTKFile type=\"UnstructuredGrid\" version=\"0.1\" byte_order=\"LittleEndian\">\n",
);
s.push_str(" <UnstructuredGrid>\n");
s.push_str(&format!(
" <Piece NumberOfPoints=\"{}\" NumberOfCells=\"{}\">\n",
self.n_points(),
self.n_cells()
));
s.push_str(" <Points>\n");
s.push_str(
" <DataArray type=\"Float64\" NumberOfComponents=\"3\" format=\"ascii\">\n",
);
for p in &self.points {
s.push_str(&format!(" {} {} {}\n", p[0], p[1], p[2]));
}
s.push_str(" </DataArray>\n");
s.push_str(" </Points>\n");
s.push_str(" <Cells>\n");
s.push_str(" <DataArray type=\"Int64\" Name=\"connectivity\" format=\"ascii\">\n");
s.push_str(" ");
let mut first = true;
for conn in &self.cells {
for &idx in conn {
if !first {
s.push(' ');
}
s.push_str(&idx.to_string());
first = false;
}
}
s.push('\n');
s.push_str(" </DataArray>\n");
s.push_str(" <DataArray type=\"Int64\" Name=\"offsets\" format=\"ascii\">\n");
s.push_str(" ");
let mut offset: usize = 0;
for (i, conn) in self.cells.iter().enumerate() {
if i > 0 {
s.push(' ');
}
offset += conn.len();
s.push_str(&offset.to_string());
}
s.push('\n');
s.push_str(" </DataArray>\n");
s.push_str(" <DataArray type=\"UInt8\" Name=\"types\" format=\"ascii\">\n");
s.push_str(" ");
for (i, ct) in self.cell_types.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&(*ct as u8).to_string());
}
s.push('\n');
s.push_str(" </DataArray>\n");
s.push_str(" </Cells>\n");
if !self.point_data.is_empty() {
s.push_str(" <PointData>\n");
for arr in &self.point_data {
s.push_str(&Self::data_array_xml(arr));
}
s.push_str(" </PointData>\n");
}
if !self.cell_data.is_empty() {
s.push_str(" <CellData>\n");
for arr in &self.cell_data {
s.push_str(&Self::data_array_xml(arr));
}
s.push_str(" </CellData>\n");
}
s.push_str(" </Piece>\n");
s.push_str(" </UnstructuredGrid>\n");
s.push_str("</VTKFile>\n");
s
}
fn data_array_xml(arr: &VtkDataArray) -> String {
let mut s = String::new();
match arr {
VtkDataArray::Scalar { name, values } => {
s.push_str(
&format!(
" <DataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"1\" format=\"ascii\">\n",
name
),
);
s.push_str(" ");
for (i, v) in values.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&v.to_string());
}
s.push('\n');
s.push_str(" </DataArray>\n");
}
VtkDataArray::Vector3 { name, values } => {
s.push_str(
&format!(
" <DataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"3\" format=\"ascii\">\n",
name
),
);
for v in values {
s.push_str(&format!(" {} {} {}\n", v[0], v[1], v[2]));
}
s.push_str(" </DataArray>\n");
}
VtkDataArray::Integer { name, values } => {
s.push_str(
&format!(
" <DataArray type=\"Int64\" Name=\"{}\" NumberOfComponents=\"1\" format=\"ascii\">\n",
name
),
);
s.push_str(" ");
for (i, v) in values.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&v.to_string());
}
s.push('\n');
s.push_str(" </DataArray>\n");
}
}
s
}
pub fn write_pvd_collection(base_name: &str, time_steps: &[(f64, String)]) -> String {
let mut s = String::new();
s.push_str("<?xml version=\"1.0\"?>\n");
s.push_str(
&format!(
"<VTKFile type=\"Collection\" version=\"0.1\" byte_order=\"LittleEndian\">\n <!-- {} -->\n",
base_name
),
);
s.push_str(" <Collection>\n");
for (time, filename) in time_steps {
s.push_str(&format!(
" <DataSet timestep=\"{}\" group=\"\" part=\"0\" file=\"{}\"/>\n",
time, filename
));
}
s.push_str(" </Collection>\n");
s.push_str("</VTKFile>\n");
s
}
pub fn from_points(positions: &[[f64; 3]]) -> Self {
let mut grid = Self::new();
for &p in positions {
let idx = grid.add_point(p);
grid.add_cell(vec![idx], VtkCellType::Vertex);
}
grid
}
pub fn from_tet_mesh(nodes: &[[f64; 3]], elements: &[[usize; 4]]) -> Self {
let mut grid = Self::new();
for &n in nodes {
grid.add_point(n);
}
for &[a, b, c, d] in elements {
grid.add_cell(vec![a, b, c, d], VtkCellType::Tetra);
}
grid
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct VtkFieldRecord {
pub name: String,
pub values: Vec<f64>,
}
impl VtkFieldRecord {
pub fn new(name: impl Into<String>, values: Vec<f64>) -> Self {
Self {
name: name.into(),
values,
}
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
pub enum VtkCellType {
Vertex = 1,
Line = 3,
Triangle = 5,
Quad = 9,
Tetra = 10,
Hexahedron = 12,
Wedge = 13,
Pyramid = 14,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct VtkLegacyData {
pub title: String,
pub dataset_type: String,
pub points: Vec<[f64; 3]>,
pub point_scalars: Vec<(String, Vec<f64>)>,
}
impl VtkLegacyData {
pub fn empty() -> Self {
Self {
title: String::new(),
dataset_type: String::new(),
points: Vec::new(),
point_scalars: Vec::new(),
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct VtkFieldData {
pub records: Vec<VtkFieldRecord>,
}
impl VtkFieldData {
pub fn new() -> Self {
Self {
records: Vec::new(),
}
}
pub fn add(&mut self, name: impl Into<String>, values: Vec<f64>) {
self.records.push(VtkFieldRecord::new(name, values));
}
pub fn add_scalar(&mut self, name: impl Into<String>, value: f64) {
self.add(name, vec![value]);
}
pub fn get(&self, name: &str) -> Option<&VtkFieldRecord> {
self.records.iter().find(|r| r.name == name)
}
pub fn to_vtk_field_string(&self) -> String {
if self.records.is_empty() {
return String::new();
}
let mut s = String::new();
s.push_str(&format!("FIELD FieldData {}\n", self.records.len()));
for rec in &self.records {
s.push_str(&format!("{} {} 1 float\n", rec.name, rec.values.len()));
let vals: Vec<String> = rec.values.iter().map(|v| v.to_string()).collect();
s.push_str(&vals.join(" "));
s.push('\n');
}
s
}
}
pub struct VtkWriter;
impl VtkWriter {
pub fn write_points(path: &str, positions: &[Vec3]) -> Result<()> {
let file = File::create(Path::new(path))?;
let mut w = BufWriter::new(file);
writeln!(w, "# vtk DataFile Version 3.0")?;
writeln!(w, "OxiPhysics point cloud")?;
writeln!(w, "ASCII")?;
writeln!(w, "DATASET POLYDATA")?;
writeln!(w, "POINTS {} float", positions.len())?;
for p in positions {
writeln!(w, "{} {} {}", p.x, p.y, p.z)?;
}
w.flush()?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_unstructured_grid(
path: &str,
positions: &[Vec3],
cells: &[[usize; 4]],
scalars: Option<(&str, &[f64])>,
vectors: Option<(&str, &[Vec3])>,
) -> Result<()> {
let file = File::create(Path::new(path))?;
let mut w = BufWriter::new(file);
writeln!(w, "# vtk DataFile Version 3.0")?;
writeln!(w, "OxiPhysics unstructured grid")?;
writeln!(w, "ASCII")?;
writeln!(w, "DATASET UNSTRUCTURED_GRID")?;
writeln!(w, "POINTS {} float", positions.len())?;
for p in positions {
writeln!(w, "{} {} {}", p.x, p.y, p.z)?;
}
let ncells = cells.len();
let cell_size = ncells * 5;
writeln!(w, "CELLS {} {}", ncells, cell_size)?;
for c in cells {
writeln!(w, "4 {} {} {} {}", c[0], c[1], c[2], c[3])?;
}
writeln!(w, "CELL_TYPES {}", ncells)?;
for _ in 0..ncells {
writeln!(w, "10")?;
}
let has_data = scalars.is_some() || vectors.is_some();
if has_data {
writeln!(w, "POINT_DATA {}", positions.len())?;
}
if let Some((name, vals)) = scalars {
writeln!(w, "SCALARS {} float 1", name)?;
writeln!(w, "LOOKUP_TABLE default")?;
for v in vals {
writeln!(w, "{}", v)?;
}
}
if let Some((name, vecs)) = vectors {
writeln!(w, "VECTORS {} float", name)?;
for v in vecs {
writeln!(w, "{} {} {}", v.x, v.y, v.z)?;
}
}
w.flush()?;
Ok(())
}
pub fn write_polydata(path: &str, positions: &[Vec3], triangles: &[[usize; 3]]) -> Result<()> {
let file = File::create(Path::new(path))?;
let mut w = BufWriter::new(file);
writeln!(w, "# vtk DataFile Version 3.0")?;
writeln!(w, "OxiPhysics polydata")?;
writeln!(w, "ASCII")?;
writeln!(w, "DATASET POLYDATA")?;
writeln!(w, "POINTS {} float", positions.len())?;
for p in positions {
writeln!(w, "{} {} {}", p.x, p.y, p.z)?;
}
let ntri = triangles.len();
writeln!(w, "POLYGONS {} {}", ntri, ntri * 4)?;
for t in triangles {
writeln!(w, "3 {} {} {}", t[0], t[1], t[2])?;
}
w.flush()?;
Ok(())
}
}
#[allow(dead_code)]
pub struct VtkTimeSeries {
pub times: Vec<f64>,
pub grids: Vec<VtuGrid>,
pub base_name: String,
}
impl VtkTimeSeries {
pub fn new(base_name: &str) -> Self {
Self {
times: Vec::new(),
grids: Vec::new(),
base_name: base_name.to_owned(),
}
}
pub fn push(&mut self, t: f64, grid: VtuGrid) {
self.times.push(t);
self.grids.push(grid);
}
pub fn n_steps(&self) -> usize {
self.times.len()
}
pub fn to_pvd_string(&self) -> String {
let entries: Vec<(f64, String)> = self
.times
.iter()
.enumerate()
.map(|(i, &t)| (t, format!("{}_{:05}.vtu", self.base_name, i)))
.collect();
VtuGrid::write_pvd_collection(&self.base_name, &entries)
}
pub fn vtu_string(&self, i: usize) -> Option<String> {
self.grids.get(i).map(|g| g.to_vtu_string())
}
pub fn estimated_size_bytes(&self) -> usize {
self.grids.iter().map(|g| g.n_points() * 30).sum()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct PvdEntry {
pub time: f64,
pub filename: String,
pub part: u32,
pub group: String,
}
impl PvdEntry {
pub fn new(time: f64, filename: impl Into<String>) -> Self {
Self {
time,
filename: filename.into(),
part: 0,
group: String::new(),
}
}
}
#[allow(dead_code)]
pub struct VtkMultiBlock {
pub blocks: Vec<VtkBlock>,
pub title: String,
}
impl VtkMultiBlock {
pub fn new(title: impl Into<String>) -> Self {
Self {
blocks: Vec::new(),
title: title.into(),
}
}
pub fn add_block(&mut self, name: impl Into<String>, grid: VtuGrid) {
self.blocks.push(VtkBlock::new(name, grid));
}
pub fn n_blocks(&self) -> usize {
self.blocks.len()
}
pub fn total_points(&self) -> usize {
self.blocks.iter().map(|b| b.grid.n_points()).sum()
}
pub fn total_cells(&self) -> usize {
self.blocks.iter().map(|b| b.grid.n_cells()).sum()
}
pub fn to_vtm_string(&self) -> String {
let mut s = String::new();
s.push_str("<?xml version=\"1.0\"?>\n");
s.push_str(
"<VTKFile type=\"vtkMultiBlockDataSet\" version=\"1.0\" byte_order=\"LittleEndian\">\n",
);
s.push_str(" <vtkMultiBlockDataSet>\n");
for (i, block) in self.blocks.iter().enumerate() {
s.push_str(&format!(
" <DataSet index=\"{}\" name=\"{}\" file=\"{}.vtu\"/>\n",
i, block.name, block.name
));
}
s.push_str(" </vtkMultiBlockDataSet>\n");
s.push_str("</VTKFile>\n");
s
}
pub fn write_to_dir(&self, dir: &str) -> crate::Result<Vec<String>> {
use std::io::Write;
let mut written = Vec::new();
for block in &self.blocks {
let path = format!("{}/{}.vtu", dir, block.name);
let xml = block.grid.to_vtu_string();
let mut f = std::fs::File::create(&path)?;
f.write_all(xml.as_bytes())?;
written.push(path);
}
let vtm_path = format!("{}/{}.vtm", dir, self.title);
let vtm = self.to_vtm_string();
let mut f = std::fs::File::create(&vtm_path)?;
f.write_all(vtm.as_bytes())?;
written.push(vtm_path);
Ok(written)
}
}
#[allow(dead_code)]
pub struct VtkPolyDataGrid {
pub points: Vec<[f64; 3]>,
pub lines: Vec<[usize; 2]>,
pub triangles: Vec<[usize; 3]>,
pub point_data: Vec<VtkDataArray>,
pub cell_data: Vec<VtkDataArray>,
}
impl VtkPolyDataGrid {
pub fn new() -> Self {
Self {
points: Vec::new(),
lines: Vec::new(),
triangles: Vec::new(),
point_data: Vec::new(),
cell_data: Vec::new(),
}
}
pub fn add_point(&mut self, p: [f64; 3]) -> usize {
let idx = self.points.len();
self.points.push(p);
idx
}
pub fn add_triangle(&mut self, a: usize, b: usize, c: usize) {
self.triangles.push([a, b, c]);
}
pub fn add_line(&mut self, a: usize, b: usize) {
self.lines.push([a, b]);
}
pub fn n_points(&self) -> usize {
self.points.len()
}
pub fn n_triangles(&self) -> usize {
self.triangles.len()
}
pub fn compute_triangle_normals(&self) -> Vec<[f64; 3]> {
self.triangles
.iter()
.map(|&[a, b, c]| {
let pa = self.points[a];
let pb = self.points[b];
let pc = self.points[c];
let ab = [pb[0] - pa[0], pb[1] - pa[1], pb[2] - pa[2]];
let ac = [pc[0] - pa[0], pc[1] - pa[1], pc[2] - pa[2]];
let n = [
ab[1] * ac[2] - ab[2] * ac[1],
ab[2] * ac[0] - ab[0] * ac[2],
ab[0] * ac[1] - ab[1] * ac[0],
];
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if len < 1e-12 {
[0.0, 0.0, 1.0]
} else {
[n[0] / len, n[1] / len, n[2] / len]
}
})
.collect()
}
pub fn to_vtu_grid(&self) -> VtuGrid {
let mut g = VtuGrid::new();
for &p in &self.points {
g.add_point(p);
}
for &[a, b, c] in &self.triangles {
g.add_cell(vec![a, b, c], VtkCellType::Triangle);
}
for &[a, b] in &self.lines {
g.add_cell(vec![a, b], VtkCellType::Line);
}
g
}
}
#[derive(Debug, Clone)]
pub enum VtkDataArray {
Scalar {
name: String,
values: Vec<f64>,
},
Vector3 {
name: String,
values: Vec<[f64; 3]>,
},
Integer {
name: String,
values: Vec<i64>,
},
}
impl VtkDataArray {
pub fn name(&self) -> &str {
match self {
Self::Scalar { name, .. } => name,
Self::Vector3 { name, .. } => name,
Self::Integer { name, .. } => name,
}
}
pub fn n_components(&self) -> usize {
match self {
Self::Scalar { .. } => 1,
Self::Vector3 { .. } => 3,
Self::Integer { .. } => 1,
}
}
pub fn len(&self) -> usize {
match self {
Self::Scalar { values, .. } => values.len(),
Self::Vector3 { values, .. } => values.len(),
Self::Integer { values, .. } => values.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[allow(dead_code)]
pub struct VtkBlock {
pub name: String,
pub grid: VtuGrid,
}
impl VtkBlock {
pub fn new(name: impl Into<String>, grid: VtuGrid) -> Self {
Self {
name: name.into(),
grid,
}
}
}
#[allow(dead_code)]
pub struct VtkRectilinearGrid {
pub x_coords: Vec<f64>,
pub y_coords: Vec<f64>,
pub z_coords: Vec<f64>,
pub point_data: Vec<VtkDataArray>,
}
impl VtkRectilinearGrid {
pub fn new(x_coords: Vec<f64>, y_coords: Vec<f64>, z_coords: Vec<f64>) -> Self {
Self {
x_coords,
y_coords,
z_coords,
point_data: Vec::new(),
}
}
pub fn n_points(&self) -> usize {
self.x_coords.len() * self.y_coords.len() * self.z_coords.len()
}
pub fn dims(&self) -> [usize; 3] {
[
self.x_coords.len(),
self.y_coords.len(),
self.z_coords.len(),
]
}
pub fn add_point_scalar(&mut self, name: &str, values: Vec<f64>) {
self.point_data.push(VtkDataArray::Scalar {
name: name.to_owned(),
values,
});
}
pub fn to_vtr_string(&self) -> String {
let [ni, nj, nk] = self.dims();
let mut s = String::new();
s.push_str("<?xml version=\"1.0\"?>\n");
s.push_str(
"<VTKFile type=\"RectilinearGrid\" version=\"0.1\" byte_order=\"LittleEndian\">\n",
);
s.push_str(&format!(
" <RectilinearGrid WholeExtent=\"0 {} 0 {} 0 {}\">\n",
ni.saturating_sub(1),
nj.saturating_sub(1),
nk.saturating_sub(1)
));
s.push_str(&format!(
" <Piece Extent=\"0 {} 0 {} 0 {}\">\n",
ni.saturating_sub(1),
nj.saturating_sub(1),
nk.saturating_sub(1)
));
s.push_str(" <Coordinates>\n");
for (label, coords) in [
("x", &self.x_coords),
("y", &self.y_coords),
("z", &self.z_coords),
] {
s.push_str(&format!(
" <DataArray type=\"Float64\" Name=\"{}\" format=\"ascii\">\n ",
label
));
for (i, v) in coords.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&v.to_string());
}
s.push_str("\n </DataArray>\n");
}
s.push_str(" </Coordinates>\n");
if !self.point_data.is_empty() {
s.push_str(" <PointData>\n");
for arr in &self.point_data {
if let VtkDataArray::Scalar { name, values } = arr {
s.push_str(
&format!(
" <DataArray type=\"Float64\" Name=\"{}\" format=\"ascii\">\n ",
name
),
);
for (i, v) in values.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&v.to_string());
}
s.push_str("\n </DataArray>\n");
}
}
s.push_str(" </PointData>\n");
}
s.push_str(" </Piece>\n </RectilinearGrid>\n</VTKFile>\n");
s
}
}
#[allow(dead_code)]
pub struct VtkStructuredGrid {
pub dims: [usize; 3],
pub points: Vec<[f64; 3]>,
pub point_data: Vec<VtkDataArray>,
}
impl VtkStructuredGrid {
pub fn new(ni: usize, nj: usize, nk: usize) -> Self {
Self {
dims: [ni, nj, nk],
points: Vec::with_capacity(ni * nj * nk),
point_data: Vec::new(),
}
}
pub fn n_points(&self) -> usize {
self.dims[0] * self.dims[1] * self.dims[2]
}
pub fn add_point_scalar(&mut self, name: &str, values: Vec<f64>) {
self.point_data.push(VtkDataArray::Scalar {
name: name.to_owned(),
values,
});
}
pub fn to_vts_string(&self) -> String {
let mut s = String::new();
s.push_str("<?xml version=\"1.0\"?>\n");
s.push_str(
"<VTKFile type=\"StructuredGrid\" version=\"0.1\" byte_order=\"LittleEndian\">\n",
);
s.push_str(&format!(
" <StructuredGrid WholeExtent=\"0 {} 0 {} 0 {}\">\n",
self.dims[0].saturating_sub(1),
self.dims[1].saturating_sub(1),
self.dims[2].saturating_sub(1)
));
s.push_str(&format!(
" <Piece Extent=\"0 {} 0 {} 0 {}\">\n",
self.dims[0].saturating_sub(1),
self.dims[1].saturating_sub(1),
self.dims[2].saturating_sub(1)
));
s.push_str(" <Points>\n");
s.push_str(
" <DataArray type=\"Float64\" NumberOfComponents=\"3\" format=\"ascii\">\n",
);
for p in &self.points {
s.push_str(&format!(" {} {} {}\n", p[0], p[1], p[2]));
}
s.push_str(" </DataArray>\n </Points>\n");
if !self.point_data.is_empty() {
s.push_str(" <PointData>\n");
for arr in &self.point_data {
if let VtkDataArray::Scalar { name, values } = arr {
s.push_str(
&format!(
" <DataArray type=\"Float64\" Name=\"{}\" NumberOfComponents=\"1\" format=\"ascii\">\n ",
name
),
);
for (i, v) in values.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&v.to_string());
}
s.push_str("\n </DataArray>\n");
}
}
s.push_str(" </PointData>\n");
}
s.push_str(" </Piece>\n </StructuredGrid>\n</VTKFile>\n");
s
}
#[allow(clippy::too_many_arguments)]
pub fn uniform(
x0: f64,
x1: f64,
ni: usize,
y0: f64,
y1: f64,
nj: usize,
z0: f64,
z1: f64,
nk: usize,
) -> Self {
let mut g = Self::new(ni, nj, nk);
let dx = if ni > 1 {
(x1 - x0) / (ni - 1) as f64
} else {
0.0
};
let dy = if nj > 1 {
(y1 - y0) / (nj - 1) as f64
} else {
0.0
};
let dz = if nk > 1 {
(z1 - z0) / (nk - 1) as f64
} else {
0.0
};
for k in 0..nk {
for j in 0..nj {
for i in 0..ni {
g.points
.push([x0 + i as f64 * dx, y0 + j as f64 * dy, z0 + k as f64 * dz]);
}
}
}
g
}
}