use energyml_core::value::{FieldValue, Reflect};
use crate::error::{Error, Result};
use crate::points::{self, Point};
use crate::readers::{Readers, RepresentationReader};
use crate::source::ValueSource;
use crate::values;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Topology {
Points,
Triangles(Vec<[u32; 3]>),
Quads(Vec<[u32; 4]>),
Polygons(Vec<Vec<u32>>),
Polylines(Vec<u32>),
Hexahedra {
cells: Vec<[u32; 8]>,
dimensions: [usize; 3],
index: Vec<u32>,
},
Grid {
fastest: usize,
slowest: usize,
},
Polyhedra(Polyhedra),
Segments(Vec<[u32; 2]>),
}
impl Topology {
pub fn cell_count(&self) -> usize {
match self {
Topology::Triangles(triangles) => triangles.len(),
Topology::Quads(quads) => quads.len(),
Topology::Polygons(rings) => rings.len(),
Topology::Polylines(runs) => runs.len(),
Topology::Hexahedra { cells, .. } => cells.len(),
Topology::Grid { fastest, slowest } => fastest.saturating_sub(1) * slowest.saturating_sub(1),
Topology::Polyhedra(cells) => cells.cell_count(),
Topology::Segments(pairs) => pairs.len(),
Topology::Points => 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Polyhedra {
face_nodes: Vec<u32>,
face_starts: Vec<u32>,
cell_faces: Vec<u32>,
cell_starts: Vec<u32>,
outward: Vec<bool>,
}
impl Polyhedra {
pub fn new(
face_nodes: Vec<u32>,
face_starts: Vec<u32>,
cell_faces: Vec<u32>,
cell_starts: Vec<u32>,
outward: Vec<bool>,
) -> Self {
Self {
face_nodes,
face_starts,
cell_faces,
cell_starts,
outward,
}
}
pub fn cell_count(&self) -> usize {
self.cell_starts.len().saturating_sub(1)
}
pub fn face_count(&self) -> usize {
self.face_starts.len().saturating_sub(1)
}
pub fn face(&self, face: usize) -> &[u32] {
let (from, to) = (
self.face_starts[face] as usize,
self.face_starts[face + 1] as usize,
);
&self.face_nodes[from..to]
}
pub fn cell(&self, cell: usize) -> impl Iterator<Item = (usize, bool)> + '_ {
let (from, to) = (
self.cell_starts[cell] as usize,
self.cell_starts[cell + 1] as usize,
);
(from..to).map(move |slot| (self.cell_faces[slot] as usize, self.outward[slot]))
}
pub fn faces_outward(&self, cell: usize) -> impl Iterator<Item = Vec<u32>> + '_ {
self.cell(cell).map(move |(face, outward)| {
let nodes = self.face(face);
if outward {
nodes.to_vec()
} else {
nodes.iter().rev().copied().collect()
}
})
}
pub fn triangles(&self) -> Vec<[u32; 3]> {
let mut out = Vec::new();
for cell in 0..self.cell_count() {
for nodes in self.faces_outward(cell) {
for corner in 1..nodes.len().saturating_sub(1) {
out.push([nodes[0], nodes[corner], nodes[corner + 1]]);
}
}
}
out
}
pub fn boundary(&self) -> Vec<Vec<u32>> {
let mut claims = vec![0u32; self.face_count()];
for cell in 0..self.cell_count() {
for (face, _) in self.cell(cell) {
if let Some(count) = claims.get_mut(face) {
*count += 1;
}
}
}
let mut out = Vec::new();
for cell in 0..self.cell_count() {
for (face, outward) in self.cell(cell) {
if claims.get(face).copied() != Some(1) {
continue;
}
let nodes = self.face(face);
out.push(if outward {
nodes.to_vec()
} else {
nodes.iter().rev().copied().collect()
});
}
}
out
}
pub fn slot_count(&self) -> usize {
self.cell_faces.len()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Patch {
pub index: usize,
pub node_offset: usize,
pub points: Vec<Point>,
pub topology: Topology,
pub closed: Vec<bool>,
}
impl Topology {
pub fn cell_at(&self, at: usize) -> Option<u32> {
match self {
Topology::Hexahedra { cells, index, .. } => {
if at >= cells.len() {
return None;
}
Some(index.get(at).copied().unwrap_or(at as u32))
}
_ => (at < self.cell_count()).then_some(at as u32),
}
}
pub fn position_of(&self, cell: u32) -> Option<usize> {
match self {
Topology::Hexahedra { cells, index, .. } => {
if index.is_empty() {
return ((cell as usize) < cells.len()).then_some(cell as usize);
}
index.binary_search(&cell).ok()
}
_ => ((cell as usize) < self.cell_count()).then_some(cell as usize),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Geometry {
pub kind: &'static str,
pub patches: Vec<Patch>,
}
impl Geometry {
pub fn point_count(&self) -> usize {
self.patches.iter().map(|patch| patch.points.len()).sum()
}
pub fn is_empty(&self) -> bool {
self.point_count() == 0
}
pub fn nodes_at(&self, ranks: &[usize]) -> std::result::Result<Vec<[f64; 3]>, String> {
let all: Vec<&[f64; 3]> = self
.patches
.iter()
.flat_map(|patch| patch.points.iter())
.collect();
ranks
.iter()
.map(|&rank| {
all.get(rank).map(|p| **p).ok_or_else(|| {
format!(
"node {rank} is named, and the shape holds {} (ranks from 0)",
all.len()
)
})
})
.collect()
}
pub fn cell_count(&self) -> usize {
self.patches.iter().map(|patch| patch.topology.cell_count()).sum()
}
pub fn merged(&self) -> (Vec<Point>, Vec<[u32; 3]>) {
let mut points = Vec::with_capacity(self.point_count());
let mut triangles = Vec::new();
for patch in &self.patches {
let base = points.len() as u32;
points.extend_from_slice(&patch.points);
if let Topology::Triangles(patch_triangles) = &patch.topology {
triangles.extend(
patch_triangles
.iter()
.map(|t| [t[0] + base, t[1] + base, t[2] + base]),
);
}
}
(points, triangles)
}
pub fn drawable(&self) -> Drawable {
let mut points: Vec<Point> = Vec::with_capacity(self.point_count());
let mut triangles: Vec<[u32; 3]> = Vec::new();
let mut triangle_cells: Vec<u32> = Vec::new();
let mut lines: Vec<[u32; 2]> = Vec::new();
let mut node_cells: Vec<u32> = Vec::new();
let mut cell_base = 0u32;
let mut unmappable = false;
for patch in &self.patches {
let base = points.len() as u32;
let placed = orphans_as_holes(&patch.points, &patch.topology);
points.extend_from_slice(&placed);
node_cells.resize(points.len(), Drawable::NO_CELL);
let finite = |index: u32| -> bool {
placed
.get(index as usize)
.is_some_and(|p| p.iter().all(|c| c.is_finite()))
};
let mut triangle = |a: u32, b: u32, c: u32, cell: u32| {
if finite(a) && finite(b) && finite(c) {
triangles.push([a + base, b + base, c + base]);
triangle_cells.push(cell);
}
};
let skin = match &patch.topology {
volume @ (Topology::Hexahedra { .. } | Topology::Polyhedra(_)) => {
crate::shape::reshape(volume, &patch.points, crate::shape::Shape::Surface).ok()
}
_ => None,
};
if skin.is_some() {
unmappable = true;
}
match skin.as_ref().unwrap_or(&patch.topology) {
Topology::Triangles(found) => {
for (at, t) in found.iter().enumerate() {
triangle(t[0], t[1], t[2], cell_base + at as u32);
}
}
Topology::Quads(found) => {
for (at, q) in found.iter().enumerate() {
triangle(q[0], q[1], q[2], cell_base + at as u32);
triangle(q[0], q[2], q[3], cell_base + at as u32);
}
}
Topology::Polygons(rings) => {
for (cell, ring) in rings.iter().enumerate() {
for at in 1..ring.len().saturating_sub(1) {
triangle(ring[0], ring[at], ring[at + 1], cell_base + cell as u32);
}
}
}
Topology::Grid { fastest, slowest } => {
for row in 0..slowest.saturating_sub(1) {
for column in 0..fastest.saturating_sub(1) {
let a = (row * fastest + column) as u32;
let b = a + 1;
let c = a + *fastest as u32;
let d = c + 1;
let cell = cell_base + (row * (fastest - 1) + column) as u32;
triangle(a, b, d, cell);
triangle(a, d, c, cell);
}
}
}
Topology::Polylines(runs) => {
let mut at: u32 = 0;
for (cell, run) in runs.iter().enumerate() {
for step in 0..*run {
if let Some(entry) = node_cells.get_mut((base + at + step) as usize) {
*entry = cell_base + cell as u32;
}
}
for step in 0..run.saturating_sub(1) {
let from = at + step;
if finite(from) && finite(from + 1) {
lines.push([from + base, from + 1 + base]);
}
}
if patch.closed.get(cell).copied().unwrap_or(false)
&& *run >= 3
&& finite(at)
&& finite(at + run - 1)
{
lines.push([at + run - 1 + base, at + base]);
}
at += run;
}
}
Topology::Segments(pairs) => {
for pair in pairs {
if finite(pair[0]) && finite(pair[1]) {
lines.push([pair[0] + base, pair[1] + base]);
}
}
}
Topology::Hexahedra { .. } | Topology::Polyhedra(_) => {}
Topology::Points => {}
}
cell_base += patch.topology.cell_count() as u32;
}
if unmappable {
triangle_cells.clear();
}
compact(points, triangles, triangle_cells, lines, node_cells)
}
}
pub(crate) fn orphans_as_holes<'a>(
points: &'a [Point],
topology: &Topology,
) -> std::borrow::Cow<'a, [Point]> {
let named: Vec<u32> = match topology {
Topology::Hexahedra { cells, .. } => cells.iter().flatten().copied().collect(),
Topology::Polyhedra(cells) => (0..cells.cell_count())
.flat_map(|cell| cells.cell(cell).map(|(face, _)| face))
.flat_map(|face| cells.face(face).iter().copied())
.collect(),
_ => return std::borrow::Cow::Borrowed(points),
};
let mut used = vec![false; points.len()];
for index in named {
if let Some(slot) = used.get_mut(index as usize) {
*slot = true;
}
}
if used.iter().all(|u| *u) {
return std::borrow::Cow::Borrowed(points);
}
let holed: Vec<Point> = points
.iter()
.zip(&used)
.map(|(point, used)| if *used { *point } else { [f64::NAN; 3] })
.collect();
std::borrow::Cow::Owned(holed)
}
fn compact(
points: Vec<Point>,
triangles: Vec<[u32; 3]>,
triangle_cells: Vec<u32>,
lines: Vec<[u32; 2]>,
node_cells: Vec<u32>,
) -> Drawable {
let is_location = |point: &Point| point.iter().all(|c| c.is_finite());
if points.iter().all(is_location) {
return Drawable {
points,
triangles,
lines,
dropped: 0,
triangle_cells,
node_cells,
};
}
const GONE: u32 = u32::MAX;
let mut moved = Vec::with_capacity(points.len());
let mut kept = Vec::with_capacity(points.len());
for point in &points {
if is_location(point) {
moved.push(kept.len() as u32);
kept.push(*point);
} else {
moved.push(GONE);
}
}
let at = |index: u32| moved.get(index as usize).copied().unwrap_or(GONE);
let mapped = !triangle_cells.is_empty();
let mut cells_kept = Vec::new();
let triangles_kept: Vec<[u32; 3]> = triangles
.into_iter()
.enumerate()
.filter_map(|(index, t)| {
let renumbered = [at(t[0]), at(t[1]), at(t[2])];
if renumbered.contains(&GONE) {
return None;
}
if mapped && let Some(cell) = triangle_cells.get(index) {
cells_kept.push(*cell);
}
Some(renumbered)
})
.collect();
let node_cells_kept: Vec<u32> = node_cells
.into_iter()
.zip(&points)
.filter(|(_, point)| is_location(point))
.map(|(cell, _)| cell)
.collect();
Drawable {
dropped: points.len() - kept.len(),
points: kept,
triangles: triangles_kept,
triangle_cells: cells_kept,
node_cells: node_cells_kept,
lines: lines
.into_iter()
.map(|l| [at(l[0]), at(l[1])])
.filter(|l| !l.contains(&GONE))
.collect(),
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Drawable {
pub points: Vec<Point>,
pub triangles: Vec<[u32; 3]>,
pub lines: Vec<[u32; 2]>,
pub dropped: usize,
pub triangle_cells: Vec<u32>,
pub node_cells: Vec<u32>,
}
impl Drawable {
pub const NO_CELL: u32 = u32::MAX;
}
pub fn of(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Geometry> {
of_with(crate::readers::default_readers(), representation, source)
}
pub fn of_with(
readers: &Readers,
representation: &dyn Reflect,
source: &dyn ValueSource,
) -> Result<Geometry> {
let info = representation.type_info();
let Some(reader) = readers.find(info.type_key) else {
return Err(Error::unsupported(leak(info.type_key), info.name));
};
let patches = reader.read(representation, source)?;
if patches.is_empty() {
return Err(Error::malformed(
info.name,
"the representation declares no patch at all",
));
}
Ok(Geometry {
kind: info.name,
patches,
})
}
pub fn is_supported(representation: &dyn Reflect) -> bool {
supports(representation.type_info().type_key)
}
pub fn supports(type_key: &str) -> bool {
crate::readers::default_readers().handles(type_key)
}
pub fn declared_nodes(representation: &dyn Reflect) -> Option<u64> {
crate::readers::default_readers().declared_nodes(representation)
}
pub(crate) fn builtin_readers() -> Vec<Box<dyn RepresentationReader>> {
struct Builtin {
name: &'static str,
key: &'static str,
read: fn(&dyn Reflect, &dyn ValueSource) -> Result<Vec<Patch>>,
nodes: Option<fn(&dyn Reflect) -> Option<u64>>,
}
impl RepresentationReader for Builtin {
fn name(&self) -> &'static str {
self.name
}
fn handles(&self, type_key: &str) -> bool {
type_key == self.key
}
fn read(&self, representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Patch>> {
(self.read)(representation, source)
}
fn declared_nodes(&self, representation: &dyn Reflect) -> Option<u64> {
(self.nodes?)(representation)
}
}
macro_rules! reader {
($key:literal, $f:path) => {
reader!($key, $f, None)
};
($key:literal, $f:path, $nodes:expr) => {
Box::new(Builtin {
name: $key,
key: $key,
read: $f,
nodes: $nodes,
}) as Box<dyn RepresentationReader>
};
}
vec![
reader!(
"TriangulatedSetRepresentation",
triangulated,
Some(triangulated_nodes)
),
reader!("PointSetRepresentation", point_set, Some(patch_count_nodes)),
reader!("PolylineRepresentation", polyline, Some(patch_count_nodes)),
reader!("PolylineSetRepresentation", polyline_set),
reader!("Grid2dRepresentation", grid_2d, Some(grid_2d_nodes)),
reader!(
"WellboreFrameRepresentation",
crate::well::frame,
Some(stated_node_count)
),
reader!("IjkGridRepresentation", crate::ijk::read),
reader!(
"WellboreTrajectoryRepresentation",
trajectory,
Some(trajectory_nodes)
),
reader!("SubRepresentation", crate::parent::sub_representation),
reader!(
"UnstructuredGridRepresentation",
crate::unstructured::read,
Some(stated_node_count)
),
reader!("PlaneSetRepresentation", crate::plane::read),
reader!("GridConnectionSetRepresentation", crate::connection::read),
reader!(
"Trajectory",
crate::stations::read,
Some(crate::stations::declared)
),
]
}
fn stated_node_count(representation: &dyn Reflect) -> Option<u64> {
let count = crate::values::scalar_i64(representation, "NodeCount").or_else(|| {
let geometry = field_object(representation, "Geometry")?;
crate::values::scalar_i64(geometry, "NodeCount")
})?;
non_negative(count)
}
fn trajectory_nodes(representation: &dyn Reflect) -> Option<u64> {
let geometry = field_object(representation, "Geometry")?;
non_negative(crate::values::scalar_i64(geometry, "KnotCount")?)
}
fn triangulated_nodes(representation: &dyn Reflect) -> Option<u64> {
sum_over(representation, &["TrianglePatch"], "NodeCount")
}
fn patch_count_nodes(representation: &dyn Reflect) -> Option<u64> {
sum_over(representation, NODE_PATCH, "Count")
}
fn grid_2d_nodes(representation: &dyn Reflect) -> Option<u64> {
let patch = field_object(representation, "Grid2dPatch").unwrap_or(representation);
let fastest = non_negative(crate::values::scalar_i64(patch, "FastestAxisCount")?)?;
let slowest = non_negative(crate::values::scalar_i64(patch, "SlowestAxisCount")?)?;
fastest.checked_mul(slowest)
}
fn sum_over(representation: &dyn Reflect, patches: &[&str], field: &str) -> Option<u64> {
let mut total = 0u64;
let mut stated = false;
for name in patches {
for patch in list(representation, name) {
let Some(count) = crate::values::scalar_i64(patch, field) else {
continue;
};
total = total.checked_add(non_negative(count)?)?;
stated = true;
}
}
stated.then_some(total)
}
fn non_negative(value: i64) -> Option<u64> {
u64::try_from(value).ok()
}
struct PatchParts<'a> {
owner: &'a dyn Reflect,
geometry: Option<&'a dyn Reflect>,
}
fn patches_of<'a>(representation: &'a dyn Reflect, fields: &[&str]) -> Vec<PatchParts<'a>> {
let mut out = Vec::new();
for field in fields {
for item in list(representation, field) {
let geometry = if field_object(item, "Points").is_some() {
Some(item)
} else {
field_object(item, "Geometry")
};
out.push(PatchParts {
owner: item,
geometry,
});
}
if !out.is_empty() {
return out;
}
}
out
}
fn triangulated(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Patch>> {
let mut out = Vec::new();
let mut node_offset = 0usize;
for (index, patch) in patches_of(representation, &["TrianglePatch"])
.into_iter()
.enumerate()
{
let at = format!("TrianglePatch[{index}]");
let points = patch_points(&patch, source, &at)?;
let patch = patch.owner;
let triangles = match field_object(patch, "Triangles") {
Some(array) => {
let flat = values::read_integers(array, source, &format!("{at}.Triangles"))?;
if flat.len() % 3 != 0 {
return Err(Error::malformed(
&at,
format!("{} index/indices is not a whole number of triangles", flat.len()),
));
}
let base = node_offset_used(&flat, node_offset, points.len(), &at)?;
flat.as_chunks::<3>()
.0
.iter()
.map(|c| [(c[0] - base) as u32, (c[1] - base) as u32, (c[2] - base) as u32])
.collect()
}
None => Vec::new(),
};
node_offset += points.len();
out.push(Patch {
index,
node_offset: node_offset - points.len(),
points,
topology: Topology::Triangles(triangles),
closed: Vec::new(),
});
}
Ok(out)
}
fn node_offset_used(indices: &[i64], offset: usize, points: usize, at: &str) -> Result<i64> {
let Some(&min) = indices.iter().min() else {
return Ok(0);
};
let Some(&max) = indices.iter().max() else {
return Ok(0);
};
let (offset, points) = (offset as i64, points as i64);
if min >= 0 && max < points {
return Ok(0);
}
if min >= offset && max < offset + points {
return Ok(offset);
}
Err(Error::malformed(
at,
format!(
"triangle indices run {min}..={max}, which is neither this patch's own \
0..{points} nor its place {offset}..{} in the representation's nodes",
offset + points
),
))
}
const NODE_PATCH: &[&str] = &["NodePatch", "NodePatchGeometry"];
fn point_set(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Patch>> {
let mut out = Vec::new();
let mut node_offset = 0usize;
for (index, patch) in patches_of(representation, NODE_PATCH).into_iter().enumerate() {
let at = format!("NodePatch[{index}]");
let points = patch_points(&patch, source, &at)?;
let start = node_offset;
node_offset += points.len();
out.push(Patch {
index,
node_offset: start,
points,
topology: Topology::Points,
closed: Vec::new(),
});
}
Ok(out)
}
fn polyline(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Patch>> {
let mut out = Vec::new();
let mut node_offset = 0usize;
for (index, patch) in patches_of(representation, NODE_PATCH).into_iter().enumerate() {
let at = format!("NodePatch[{index}]");
let points = patch_points(&patch, source, &at)?;
let length = points.len() as u32;
let start = node_offset;
node_offset += points.len();
out.push(Patch {
index,
node_offset: start,
points,
topology: Topology::Polylines(vec![length]),
closed: Vec::new(),
});
}
Ok(out)
}
fn polyline_set(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Patch>> {
let mut out = Vec::new();
let mut node_offset = 0usize;
for (index, patch) in patches_of(representation, &["LinePatch"]).into_iter().enumerate() {
let at = format!("LinePatch[{index}]");
let points = patch_points(&patch, source, &at)?;
let runs = match field_object(patch.owner, "NodeCountPerPolyline") {
Some(array) => values::read_integers(array, source, &at)?
.into_iter()
.map(|n| n.max(0) as u32)
.collect(),
None => vec![points.len() as u32],
};
let closed = match field_object(patch.owner, "ClosedPolylines") {
Some(array) => {
let mut flags = values::read_flags(array, source, &format!("{at}.ClosedPolylines"))?;
flags.truncate(runs.len());
flags
}
None => Vec::new(),
};
let start = node_offset;
node_offset += points.len();
out.push(Patch {
index,
node_offset: start,
points,
topology: Topology::Polylines(runs),
closed,
});
}
Ok(out)
}
fn trajectory(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Patch>> {
let at = "WellboreTrajectoryRepresentation";
let Some(geometry) = field_object(representation, "Geometry") else {
return crate::well::datum_trajectory(representation, source, at);
};
let points = crate::parametric::Line::single(geometry, source, at)?.stations();
if points.is_empty() {
return Err(Error::malformed(at, "a trajectory with no control point"));
}
let length = points.len() as u32;
Ok(vec![Patch {
index: 0,
node_offset: 0,
points,
topology: Topology::Polylines(vec![length]),
closed: Vec::new(),
}])
}
fn grid_2d(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Patch>> {
let patch = field_object(representation, "Grid2dPatch").unwrap_or(representation);
let points = geometry_points(patch, source, "Grid2dPatch")?;
let fastest = values::scalar_i64(patch, "FastestAxisCount").unwrap_or(0).max(0) as usize;
let slowest = values::scalar_i64(patch, "SlowestAxisCount").unwrap_or(0).max(0) as usize;
let mut points = points;
if fastest * slowest != points.len() {
if (fastest + 1) * (slowest + 1) == points.len() {
points = crate::points::drop_last_per_axis(points, &[slowest + 1, fastest + 1]);
} else {
return Err(Error::malformed(
"Grid2dPatch",
format!(
"{fastest} × {slowest} axes describe {} point(s), the geometry has {}",
fastest * slowest,
points.len()
),
));
}
}
Ok(vec![Patch {
index: 0,
node_offset: 0,
points,
topology: Topology::Grid { fastest, slowest },
closed: Vec::new(),
}])
}
fn patch_points(patch: &PatchParts<'_>, source: &dyn ValueSource, at: &str) -> Result<Vec<Point>> {
let Some(geometry) = patch.geometry else {
return Err(Error::malformed(at, "a patch that declares no geometry"));
};
let Some(array) = field_object(geometry, "Points") else {
return Err(Error::malformed(at, "a geometry with no `Points`"));
};
points::read(
array,
source,
&format!("{at}.Geometry.Points"),
crate::parametric::Vertical::read(geometry, source),
)
}
fn geometry_points(owner: &dyn Reflect, source: &dyn ValueSource, at: &str) -> Result<Vec<Point>> {
let geometry = match field_object(owner, "Geometry") {
Some(geometry) => geometry,
None if field_object(owner, "Points").is_some() => owner,
None => return Err(Error::malformed(at, "a patch that declares no geometry")),
};
let Some(array) = field_object(geometry, "Points") else {
return Err(Error::malformed(at, "a geometry with no `Points`"));
};
points::read(
array,
source,
&format!("{at}.Geometry.Points"),
crate::parametric::Vertical::read(geometry, source),
)
}
pub(crate) fn field_list<'a>(object: &'a dyn Reflect, field: &str) -> Vec<&'a dyn Reflect> {
list(object, field)
}
fn list<'a>(object: &'a dyn Reflect, field: &str) -> Vec<&'a dyn Reflect> {
match object.field(field) {
Some(FieldValue::List(items)) => items.iter().filter_map(|item| item.as_object()).collect(),
Some(FieldValue::Object(one)) => vec![one],
_ => Vec::new(),
}
}
pub(crate) fn field_object<'a>(object: &'a dyn Reflect, field: &str) -> Option<&'a dyn Reflect> {
match object.field(field)? {
FieldValue::Object(child) => Some(child),
_ => None,
}
}
pub(crate) fn leak(key: &str) -> &'static str {
Box::leak(key.to_string().into_boxed_str())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_first_patch_reads_the_same_either_way() {
assert_eq!(node_offset_used(&[0, 1, 3, 1, 4, 3], 0, 5, "t").unwrap(), 0);
}
#[test]
fn a_later_patch_written_by_fesapi_is_offset() {
assert_eq!(
node_offset_used(&[5, 6, 8, 6, 9, 8, 6, 7, 9], 5, 5, "t").unwrap(),
5
);
}
#[test]
fn a_later_patch_written_by_resqpy_is_not() {
assert_eq!(node_offset_used(&[0, 1, 3, 1, 4, 3], 5, 5, "t").unwrap(), 0);
}
#[test]
fn an_index_belonging_to_neither_is_refused() {
let refused = node_offset_used(&[0, 1, 99], 5, 5, "TrianglePatch[1]");
assert!(refused.is_err(), "{refused:?}");
}
#[test]
fn merging_renumbers_into_one_buffer() {
let geometry = Geometry {
kind: "TriangulatedSetRepresentation",
patches: vec![
Patch {
index: 0,
node_offset: 0,
points: vec![[0.0; 3], [1.0; 3], [2.0; 3]],
topology: Topology::Triangles(vec![[0, 1, 2]]),
closed: Vec::new(),
},
Patch {
index: 1,
node_offset: 3,
points: vec![[3.0; 3], [4.0; 3], [5.0; 3]],
topology: Topology::Triangles(vec![[0, 1, 2]]),
closed: Vec::new(),
},
],
};
let (points, triangles) = geometry.merged();
assert_eq!(points.len(), 6);
assert_eq!(triangles, vec![[0, 1, 2], [3, 4, 5]]);
}
}