use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::error::GeometryError;
use super::{invalid, require_finite};
const LIST_2D: &str = "IFCCARTESIANPOINTLIST2D";
const LIST_3D: &str = "IFCCARTESIANPOINTLIST3D";
mod slot {
pub const COORD_LIST: usize = 0;
pub const TAG_LIST: usize = 1;
pub const COORDINATES: usize = 0;
pub const TRI_NORMALS: usize = 1;
pub const TRI_CLOSED: usize = 2;
pub const TRI_COORD_INDEX: usize = 3;
pub const TRI_PN_INDEX: usize = 4;
pub const POLY_CLOSED: usize = 1;
pub const POLY_FACES: usize = 2;
pub const POLY_PN_INDEX: usize = 3;
pub const FACE_COORD_INDEX: usize = 0;
pub const FACE_INNER: usize = 1;
}
fn index_1based(
type_name: &'static str,
attribute: &'static str,
value: usize,
point_count: usize,
) -> Result<Value, GeometryError> {
if value >= point_count {
return Err(invalid(
type_name,
attribute,
format!("index {value} is outside a point list of {point_count}"),
));
}
Ok(Value::Integer(value as i64 + 1))
}
pub fn cartesian_point_list_3d(
tx: &mut Transaction,
points: &[[f64; 3]],
tags: Option<&[&str]>,
) -> Result<EntityId, GeometryError> {
point_list(
tx,
LIST_3D,
points.iter().map(|p| p.as_slice()),
points.len(),
tags,
)
}
pub fn cartesian_point_list_2d(
tx: &mut Transaction,
points: &[[f64; 2]],
tags: Option<&[&str]>,
) -> Result<EntityId, GeometryError> {
point_list(
tx,
LIST_2D,
points.iter().map(|p| p.as_slice()),
points.len(),
tags,
)
}
fn point_list<'p>(
tx: &mut Transaction,
type_name: &'static str,
rows: impl Iterator<Item = &'p [f64]>,
count: usize,
tags: Option<&[&str]>,
) -> Result<EntityId, GeometryError> {
if count == 0 {
return Err(invalid(
type_name,
"CoordList",
"expected at least one point",
));
}
let mut coords = Vec::with_capacity(count);
for row in rows {
require_finite(type_name, "CoordList", row)?;
coords.push(Value::List(row.iter().copied().map(Value::Real).collect()));
}
let mut attrs = vec![Value::Null; slot::COORD_LIST + 1];
attrs[slot::COORD_LIST] = Value::List(coords);
if let Some(tags) = tags {
if tags.len() != count {
return Err(invalid(
type_name,
"TagList",
format!("{} tags for {count} points", tags.len()),
));
}
attrs.resize(slot::TAG_LIST + 1, Value::Null);
attrs[slot::TAG_LIST] =
Value::List(tags.iter().map(|t| Value::Text((*t).into())).collect());
}
Ok(tx.create(Entity::new(type_name, attrs)))
}
#[derive(Debug, Default, Clone, Copy)]
pub struct TriangulatedExtras<'a> {
pub closed: Option<bool>,
pub normals: Option<&'a [[f64; 3]]>,
pub pn_index: Option<&'a [usize]>,
}
pub fn triangulated_face_set(
tx: &mut Transaction,
coordinates: EntityId,
point_count: usize,
triangles: &[[usize; 3]],
extras: TriangulatedExtras<'_>,
) -> Result<EntityId, GeometryError> {
let attrs = triangulated_attrs(
"IFCTRIANGULATEDFACESET",
5,
coordinates,
point_count,
triangles,
extras,
)?;
Ok(tx.create(Entity::new("IFCTRIANGULATEDFACESET", attrs)))
}
fn triangulated_attrs(
type_name: &'static str,
arity: usize,
coordinates: EntityId,
point_count: usize,
triangles: &[[usize; 3]],
extras: TriangulatedExtras<'_>,
) -> Result<Vec<Value>, GeometryError> {
if triangles.is_empty() {
return Err(invalid(
type_name,
"CoordIndex",
"expected at least one triangle",
));
}
let mut indexed = Vec::with_capacity(triangles.len());
for triangle in triangles {
let mut row = Vec::with_capacity(3);
for value in triangle {
row.push(index_1based(type_name, "CoordIndex", *value, point_count)?);
}
indexed.push(Value::List(row));
}
let mut attrs = vec![Value::Null; arity];
attrs[slot::COORDINATES] = Value::Ref(coordinates);
attrs[slot::TRI_COORD_INDEX] = Value::List(indexed);
if let Some(closed) = extras.closed {
attrs[slot::TRI_CLOSED] = Value::Bool(closed);
}
if let Some(normals) = extras.normals {
let mut rows = Vec::with_capacity(normals.len());
for normal in normals {
require_finite(type_name, "Normals", normal)?;
rows.push(Value::List(
normal.iter().copied().map(Value::Real).collect(),
));
}
attrs[slot::TRI_NORMALS] = Value::List(rows);
}
if let Some(pn) = extras.pn_index {
let mut rows = Vec::with_capacity(pn.len());
for value in pn {
rows.push(index_1based(type_name, "PnIndex", *value, point_count)?);
}
attrs[slot::TRI_PN_INDEX] = Value::List(rows);
}
Ok(attrs)
}
pub fn indexed_polygonal_face(
tx: &mut Transaction,
outer: &[usize],
point_count: usize,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCINDEXEDPOLYGONALFACE";
let indices = face_loop(T, "CoordIndex", outer, point_count)?;
let mut attrs = vec![Value::Null];
attrs[slot::FACE_COORD_INDEX] = indices;
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn indexed_polygonal_face_with_voids(
tx: &mut Transaction,
outer: &[usize],
voids: &[&[usize]],
point_count: usize,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCINDEXEDPOLYGONALFACEWITHVOIDS";
if voids.is_empty() {
return Err(invalid(
T,
"InnerCoordIndices",
"expected at least one void; a face without voids is an IfcIndexedPolygonalFace",
));
}
let outer_indices = face_loop(T, "CoordIndex", outer, point_count)?;
let mut inner = Vec::with_capacity(voids.len());
for loop_indices in voids {
inner.push(face_loop(
T,
"InnerCoordIndices",
loop_indices,
point_count,
)?);
}
let mut attrs = vec![Value::Null; 2];
attrs[slot::FACE_COORD_INDEX] = outer_indices;
attrs[slot::FACE_INNER] = Value::List(inner);
Ok(tx.create(Entity::new(T, attrs)))
}
fn face_loop(
type_name: &'static str,
attribute: &'static str,
indices: &[usize],
point_count: usize,
) -> Result<Value, GeometryError> {
if indices.len() < 3 {
return Err(invalid(
type_name,
attribute,
format!("expected at least 3 vertices, got {}", indices.len()),
));
}
let mut row = Vec::with_capacity(indices.len());
for value in indices {
row.push(index_1based(type_name, attribute, *value, point_count)?);
}
Ok(Value::List(row))
}
pub fn polygonal_face_set(
tx: &mut Transaction,
coordinates: EntityId,
point_count: usize,
faces: &[EntityId],
closed: Option<bool>,
pn_index: Option<&[usize]>,
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCPOLYGONALFACESET";
if faces.is_empty() {
return Err(invalid(T, "Faces", "expected at least one face"));
}
let mut seen = faces.to_vec();
seen.sort_unstable();
seen.dedup();
if seen.len() != faces.len() {
return Err(invalid(
T,
"Faces",
"expected a UNIQUE face list, got a repeated face",
));
}
let mut attrs = vec![Value::Null; 4];
attrs[slot::COORDINATES] = Value::Ref(coordinates);
attrs[slot::POLY_FACES] = Value::List(faces.iter().copied().map(Value::Ref).collect());
if let Some(closed) = closed {
attrs[slot::POLY_CLOSED] = Value::Bool(closed);
}
if let Some(pn) = pn_index {
let mut rows = Vec::with_capacity(pn.len());
for value in pn {
rows.push(index_1based(T, "PnIndex", *value, point_count)?);
}
attrs[slot::POLY_PN_INDEX] = Value::List(rows);
}
Ok(tx.create(Entity::new(T, attrs)))
}
pub fn triangulated_irregular_network(
tx: &mut Transaction,
coordinates: EntityId,
point_count: usize,
triangles: &[[usize; 3]],
extras: TriangulatedExtras<'_>,
flags: &[i64],
) -> Result<EntityId, GeometryError> {
const T: &str = "IFCTRIANGULATEDIRREGULARNETWORK";
const FLAGS: usize = 5;
if flags.len() != triangles.len() {
return Err(invalid(
T,
"Flags",
format!(
"expected one flag per triangle: {} triangles, {} flags",
triangles.len(),
flags.len()
),
));
}
let mut attrs = triangulated_attrs(T, 6, coordinates, point_count, triangles, extras)?;
attrs[slot::TRI_CLOSED] = Value::Bool(false);
attrs[FLAGS] = Value::List(flags.iter().copied().map(Value::Integer).collect());
Ok(tx.create(Entity::new(T, attrs)))
}