use energyml_core::value::{FieldValue, Reflect};
use crate::error::{Error, Result};
use crate::parametric::Vertical;
use crate::source::ValueSource;
use crate::values::{self, scalar_f64, scalar_i64};
pub type Point = [f64; 3];
pub fn read(
array: &dyn Reflect,
source: &dyn ValueSource,
at: &str,
vertical: Vertical,
) -> Result<Vec<Point>> {
let name = array.type_info().name;
if name.starts_with("Point2d") && (name.ends_with("ExternalArray") || name.ends_with("Hdf5Array")) {
let coordinates = values::read_doubles(array, source, at)?;
if coordinates.len() % 2 != 0 {
return Err(Error::malformed(
at,
format!(
"{} coordinate(s) is not a whole number of 2D points",
coordinates.len()
),
));
}
return Ok(coordinates
.as_chunks::<2>()
.0
.iter()
.map(|c| [c[0], c[1], 0.0])
.collect());
}
if name.ends_with("ExternalArray") || name.ends_with("Hdf5Array") {
let coordinates = values::read_doubles(array, source, at)?;
if coordinates.len() % 3 != 0 {
return Err(Error::malformed(
at,
format!(
"{} coordinate(s) is not a whole number of points",
coordinates.len()
),
));
}
return Ok(coordinates
.as_chunks::<3>()
.0
.iter()
.map(|c| [c[0], c[1], c[2]])
.collect());
}
if name.starts_with("Point3d") && name.ends_with("LatticeArray") && !name.contains("FromRepresentation") {
return lattice(array, at);
}
if name.ends_with("ZValueArray") {
return z_values(array, source, at, vertical);
}
if name.ends_with("ParametricArray") {
return crate::parametric::read(array, source, at, vertical);
}
if name == "Point3dFromRepresentationLatticeArray" {
return crate::derived::read(array, source, at);
}
Err(Error::unsupported(name, at))
}
pub fn count(array: &dyn Reflect, source: &dyn ValueSource, at: &str) -> Result<usize> {
let name = array.type_info().name;
if name.ends_with("ExternalArray") || name.ends_with("Hdf5Array") {
let per_point = if name.starts_with("Point2d") { 2 } else { 3 };
return Ok(values::count(array, source, at)? / per_point);
}
if name == "Point3dFromRepresentationLatticeArray" {
return crate::derived::count(array, at);
}
Ok(read(array, source, at, Vertical::Down)?.len())
}
fn lattice(array: &dyn Reflect, at: &str) -> Result<Vec<Point>> {
let origin = point_of(array, "Origin")
.ok_or_else(|| Error::malformed(at, "a lattice with no `Origin` describes nothing"))?;
let dimensions = match array.field("Offset").or_else(|| array.field("Dimension")) {
Some(FieldValue::List(list)) => list,
Some(FieldValue::Object(one)) => return lattice_along(origin, &[one], at),
_ => return Ok(vec![origin]),
};
let dimensions: Vec<&dyn Reflect> = dimensions.iter().filter_map(|item| item.as_object()).collect();
lattice_along(origin, &dimensions, at)
}
fn lattice_along(origin: Point, dimensions: &[&dyn Reflect], at: &str) -> Result<Vec<Point>> {
let mut points = vec![origin];
for offset in dimensions {
let offset = *offset;
let mut direction = point_of(offset, "Offset")
.or_else(|| point_of(offset, "Direction"))
.ok_or_else(|| Error::malformed(at, "a lattice dimension with no direction vector"))?;
let spacings = spacings_of(offset, at)?;
let mut distances = vec![0.0];
let mut total = 0.0;
for spacing in &spacings {
total += spacing;
distances.push(total);
}
let length =
(direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2]).sqrt();
if (length - 1.0).abs() > 1e-3 && total > 0.0 && (total / length - 1.0).abs() < 1e-6 {
direction = [
direction[0] / length,
direction[1] / length,
direction[2] / length,
];
}
let outer = std::mem::take(&mut points);
points.reserve(outer.len() * distances.len());
for point in &outer {
for distance in &distances {
points.push([
point[0] + direction[0] * distance,
point[1] + direction[1] * distance,
point[2] + direction[2] * distance,
]);
}
}
}
Ok(points)
}
fn spacings_of(offset: &dyn Reflect, at: &str) -> Result<Vec<f64>> {
let Some(FieldValue::Object(spacing)) = offset.field("Spacing") else {
return Ok(Vec::new());
};
let name = spacing.type_info().name;
if name.ends_with("ConstantArray") {
let value =
scalar_f64(spacing, "Value").ok_or_else(|| Error::malformed(at, "a spacing with no `Value`"))?;
let count =
scalar_i64(spacing, "Count").ok_or_else(|| Error::malformed(at, "a spacing with no `Count`"))?;
return Ok(vec![value; count.max(0) as usize]);
}
if name.ends_with("XmlArray") || name.ends_with("LatticeArray") {
return values::read_doubles(spacing, &energyml_domain::source::NoObjects, at);
}
Err(Error::unsupported(name, format!("{at}.Spacing")))
}
fn z_values(
array: &dyn Reflect,
source: &dyn ValueSource,
at: &str,
vertical: Vertical,
) -> Result<Vec<Point>> {
let Some(FieldValue::Object(supporting)) = array.field("SupportingGeometry") else {
return Err(Error::malformed(
at,
"a z-value array with no supporting geometry",
));
};
let mut points = read(supporting, source, &format!("{at}.SupportingGeometry"), vertical)?;
let Some(FieldValue::Object(z)) = array.field("ZValues") else {
return Err(Error::malformed(at, "a z-value array with no `ZValues`"));
};
let z_shape = shape_of(z, source);
let z = values::read_doubles(z, source, &format!("{at}.ZValues"))?;
let mut dims = lattice_dims(supporting);
if let (Some(d), Some(shape)) = (&mut dims, &z_shape)
&& d.len() == 2
&& shape.len() == 2
&& d.iter().product::<usize>() == points.len()
{
let fits = |axis: usize, extent: usize| axis == extent || axis == extent + 1;
let straight = fits(d[0], shape[0]) && fits(d[1], shape[1]);
let crossed = fits(d[0], shape[1]) && fits(d[1], shape[0]);
if crossed && !straight {
points = transpose(points, d);
d.swap(0, 1);
}
}
if z.len() != points.len() {
match &dims {
Some(dims)
if dims.iter().product::<usize>() == points.len()
&& dims.iter().all(|d| *d > 1)
&& dims.iter().map(|d| d - 1).product::<usize>() == z.len() =>
{
points = drop_last_per_axis(points, dims);
}
_ => {
return Err(Error::malformed(
at,
format!(
"{} z value(s) for {} point(s) of supporting geometry",
z.len(),
points.len()
),
));
}
}
}
for (point, z) in points.iter_mut().zip(z) {
point[2] = z;
}
Ok(points)
}
fn shape_of(array: &dyn Reflect, source: &dyn ValueSource) -> Option<Vec<usize>> {
let locations = source.locate(array);
let [location] = locations.as_slice() else {
return None;
};
let metadata = source.metadata(location).ok()?;
Some(metadata.dimensions.iter().map(|d| (*d).max(0) as usize).collect())
}
pub(crate) fn transpose(points: Vec<Point>, dims: &[usize]) -> Vec<Point> {
let (outer, inner) = (dims[0], dims[1]);
let mut out = Vec::with_capacity(points.len());
for j in 0..inner {
for i in 0..outer {
out.push(points[i * inner + j]);
}
}
out
}
pub(crate) fn lattice_dims(array: &dyn Reflect) -> Option<Vec<usize>> {
let name = array.type_info().name;
if name == "Point3dFromRepresentationLatticeArray" {
return crate::derived::selection_dims(array).ok();
}
if !(name.starts_with("Point3d") && name.ends_with("LatticeArray")) || name.contains("FromRepresentation")
{
return None;
}
let dimensions = match array.field("Offset").or_else(|| array.field("Dimension"))? {
FieldValue::List(list) => list
.iter()
.filter_map(|item| item.as_object())
.collect::<Vec<_>>(),
FieldValue::Object(one) => vec![one],
_ => return None,
};
let mut dims = Vec::with_capacity(dimensions.len());
for offset in dimensions {
dims.push(spacings_of(offset, "lattice").ok()?.len() + 1);
}
Some(dims)
}
pub(crate) fn drop_last_per_axis(points: Vec<Point>, dims: &[usize]) -> Vec<Point> {
let mut keep = Vec::with_capacity(dims.iter().map(|d| d - 1).product());
let mut index = vec![0usize; dims.len()];
for point in points {
if index.iter().zip(dims).all(|(i, d)| i + 1 < *d) {
keep.push(point);
}
for axis in (0..index.len()).rev() {
index[axis] += 1;
if index[axis] < dims[axis] {
break;
}
index[axis] = 0;
}
}
keep
}
fn point_of(object: &dyn Reflect, field: &str) -> Option<Point> {
let FieldValue::Object(point) = object.field(field)? else {
return None;
};
Some([
scalar_f64(point, "Coordinate1")?,
scalar_f64(point, "Coordinate2")?,
scalar_f64(point, "Coordinate3")?,
])
}