use axiolid_core::{Point3, Vec3};
use axiolid_mesh::{NormalAttribute, PolygonFace, PolygonMesh, TriMesh};
use axiolid_model::{GeometryNode, NodeId};
use ifc_model::EntityId;
use crate::error::GeometryResult;
use crate::lower::session::LoweringSession;
use crate::resource::point::CartesianPointList3D;
use crate::solid::tessellated::{
IndexedPolygonalFace, IndexedPolygonalFaceWithVoids, PolygonalFaceSet, TriangulatedFaceSet,
};
use crate::transform::Transform;
const KIND: &str = "tessellated face set";
fn positions(
session: &mut LoweringSession<'_>,
owner: EntityId,
coordinates: EntityId,
frame: Transform,
) -> GeometryResult<Vec<Point3>> {
let entity = session.entity(owner, coordinates)?;
let raw = CartesianPointList3D::new(coordinates, entity).coordinates()?;
Ok(raw
.into_iter()
.map(|point| {
let scaled = point.map(|value| session.units().length(value));
Point3::from_array(frame.apply(scaled))
})
.collect())
}
fn checked_index(
session: &LoweringSession<'_>,
owner: EntityId,
index: usize,
vertex_count: usize,
field: &str,
) -> GeometryResult<u32> {
if index >= vertex_count {
return Err(session.degenerate(
owner,
KIND,
format!(
"{field} addresses vertex {} but Coordinates has {vertex_count}",
index + 1
),
));
}
Ok(index as u32)
}
pub fn lower_triangulated_face_set_node(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
if let Some(node) = session.memoized(id, KIND, frame) {
return Ok(node);
}
session.enter(id, KIND)?;
let result = build_triangulated(session, id, frame);
session.exit(id);
let node = result?;
session.memoize(id, KIND, frame, node);
Ok(node)
}
fn build_triangulated(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
let entity = session.entity(id, id)?;
let view = TriangulatedFaceSet::new(id, entity);
let coordinates = view.coordinates()?;
let triangles = view.triangles_0based()?;
let normals = view.normals();
let positions = positions(session, id, coordinates, frame)?;
let index_count = session.check_aggregate(
id,
"IFCTRIANGULATEDFACESET",
"triangle indices",
triangles.len() as u128 * 3,
)?;
let mut indices = Vec::with_capacity(index_count);
for triangle in &triangles {
for corner in triangle {
indices.push(checked_index(
session,
id,
*corner,
positions.len(),
"CoordIndex",
)?);
}
}
let mut mesh = TriMesh::new(positions, indices);
if let Some(values) = normals {
mesh.normals = Some(NormalAttribute {
values: values
.into_iter()
.map(|normal| Vec3::from_array(frame.apply_direction(normal)))
.collect(),
indices: None,
});
}
mesh.attributes = texture::channels(session, id, triangles.len())?;
session.node_for(id, GeometryNode::TriMesh(mesh))
}
pub fn lower_polygonal_face_set_node(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
if let Some(node) = session.memoized(id, KIND, frame) {
return Ok(node);
}
session.enter(id, KIND)?;
let result = build_polygonal(session, id, frame);
session.exit(id);
let node = result?;
session.memoize(id, KIND, frame, node);
Ok(node)
}
fn build_polygonal(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
let entity = session.entity(id, id)?;
let view = PolygonalFaceSet::new(id, entity);
let coordinates = view.coordinates()?;
let face_refs = view.faces()?;
let pn = view.pn_index_0based()?;
let positions = positions(session, id, coordinates, frame)?;
let mut faces = Vec::with_capacity(face_refs.len());
for face_ref in &face_refs {
let face_entity = session.entity(id, *face_ref)?;
let face = IndexedPolygonalFace::new(*face_ref, face_entity);
let outer = resolve_loop(session, id, &face.outer_loop_0based()?, &pn, &positions)?;
let holes = if face.has_voids() {
IndexedPolygonalFaceWithVoids::new(*face_ref, face_entity)
.inner_loops_0based()?
.iter()
.map(|inner| resolve_loop(session, id, inner, &pn, &positions))
.collect::<GeometryResult<Vec<_>>>()?
} else {
Vec::new()
};
faces.push(PolygonFace { outer, holes });
}
session.node_for(
id,
GeometryNode::PolygonMesh(PolygonMesh { positions, faces }),
)
}
fn resolve_loop(
session: &LoweringSession<'_>,
owner: EntityId,
authored: &[usize],
pn: &Option<Vec<usize>>,
positions: &[Point3],
) -> GeometryResult<Vec<u32>> {
authored
.iter()
.map(|index| {
let direct = match pn {
Some(map) => *map.get(*index).ok_or_else(|| {
session.degenerate(
owner,
KIND,
format!(
"CoordIndex {} is past the end of PnIndex ({} entries)",
index + 1,
map.len()
),
)
})?,
None => *index,
};
checked_index(session, owner, direct, positions.len(), "CoordIndex")
})
.collect()
}
#[cfg(test)]
mod tests;
mod texture;
pub use texture::UV_CHANNEL;