use energyml_core::value::Reflect;
use energyml_domain::values;
use crate::error::{Error, Result};
use crate::geometry::{Patch, Polyhedra, Topology, field_object};
use crate::source::ValueSource;
use crate::values::scalar_i64;
pub fn read(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Patch>> {
let at = "UnstructuredGridRepresentation";
let cells = match scalar_i64(representation, "CellCount") {
Some(count) if count > 0 => count as usize,
Some(count) => return Err(Error::malformed(at, format!("`CellCount` is {count}"))),
None => return Err(Error::malformed(at, "no `CellCount`")),
};
let Some(geometry) = field_object(representation, "Geometry") else {
return Err(Error::unsupported(
"an unstructured grid with a cell count and no geometry",
format!("{at}, {cells} cell(s)"),
));
};
let Some(array) = field_object(geometry, "Points") else {
return Err(Error::malformed(at, "a geometry with no `Points`"));
};
let points = crate::points::read(
array,
source,
&format!("{at}.Geometry.Points"),
crate::parametric::Vertical::read(geometry, source),
)?;
let nodes = declared(geometry, "NodeCount", at)?;
let faces = declared(geometry, "FaceCount", at)?;
if points.len() != nodes {
return Err(Error::malformed(
at,
format!("`NodeCount` is {nodes} and `Points` holds {}", points.len()),
));
}
let (face_nodes, face_starts) = jagged(geometry, "NodesPerFace", source, at)?;
if face_starts.len().saturating_sub(1) != faces {
return Err(Error::malformed(
at,
format!(
"`FaceCount` is {faces} and `NodesPerFace` describes {}",
face_starts.len().saturating_sub(1)
),
));
}
if let Some(bad) = face_nodes.iter().find(|node| **node as usize >= nodes) {
return Err(Error::malformed(
at,
format!("`NodesPerFace` names node {bad} of {nodes}"),
));
}
let (cell_faces, cell_starts) = jagged(geometry, "FacesPerCell", source, at)?;
if cell_starts.len().saturating_sub(1) != cells {
return Err(Error::malformed(
at,
format!(
"`CellCount` is {cells} and `FacesPerCell` describes {}",
cell_starts.len().saturating_sub(1)
),
));
}
if let Some(bad) = cell_faces.iter().find(|face| **face as usize >= faces) {
return Err(Error::malformed(
at,
format!("`FacesPerCell` names face {bad} of {faces}"),
));
}
let outward = handedness(geometry, source, cell_faces.len(), at)?;
let polyhedra = Polyhedra::new(face_nodes, face_starts, cell_faces, cell_starts, outward);
let degenerate = (0..polyhedra.cell_count())
.filter(|cell| polyhedra.cell(*cell).count() < 4)
.count();
if degenerate == polyhedra.cell_count() && degenerate > 0 {
return Err(Error::malformed(
at,
format!("all {degenerate} cell(s) have fewer than four faces, so enclose nothing"),
));
}
Ok(vec![Patch {
index: 0,
node_offset: 0,
points,
topology: Topology::Polyhedra(polyhedra),
closed: Vec::new(),
}])
}
fn declared(geometry: &dyn Reflect, field: &str, at: &str) -> Result<usize> {
match scalar_i64(geometry, field) {
Some(value) if value > 0 => Ok(value as usize),
Some(value) => Err(Error::malformed(at, format!("`{field}` is {value}"))),
None => Err(Error::malformed(at, format!("a geometry with no `{field}`"))),
}
}
fn jagged(
geometry: &dyn Reflect,
field: &str,
source: &dyn ValueSource,
at: &str,
) -> Result<(Vec<u32>, Vec<u32>)> {
let Some(array) = field_object(geometry, field) else {
return Err(Error::malformed(at, format!("a geometry with no `{field}`")));
};
let rows = values::jagged(array, source, &format!("{at}.{field}"))?;
let mut flat = Vec::new();
let mut starts = Vec::with_capacity(rows.len() + 1);
starts.push(0u32);
for row in &rows {
let Some(values) = row.integer() else {
return Err(Error::malformed(
at,
format!("`{field}` holds something that is not indices"),
));
};
for value in values {
if *value < 0 {
return Err(Error::malformed(at, format!("`{field}` holds the index {value}")));
}
flat.push(*value as u32);
}
starts.push(flat.len() as u32);
}
Ok((flat, starts))
}
fn handedness(geometry: &dyn Reflect, source: &dyn ValueSource, slots: usize, at: &str) -> Result<Vec<bool>> {
let Some(array) = field_object(geometry, "CellFaceIsRightHanded") else {
return Err(Error::malformed(at, "a geometry with no `CellFaceIsRightHanded`"));
};
let flags = values::read(array, source, &format!("{at}.CellFaceIsRightHanded"))?;
let flags = flags
.boolean()
.ok_or_else(|| Error::malformed(at, "`CellFaceIsRightHanded` is not a boolean array"))?
.to_vec();
if flags.len() != slots {
return Err(Error::malformed(
at,
format!(
"`CellFaceIsRightHanded` holds {} flag(s) for {slots} cell-face pair(s): the \
schema says its length is \"the sum of the cell face count per cell\"",
flags.len()
),
));
}
Ok(flags)
}