use crate::triangulation::{calculate_polygon_normal, project_to_2d, triangulate_polygon};
use crate::{Error, Point3, Result};
use ifc_lite_core::{DecodedEntity, EntityDecoder};
use nalgebra::Matrix4;
use super::helpers::get_axis2_placement_transform_by_id;
pub(super) fn process_advanced_face(
face: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<(Vec<f32>, Vec<u32>)> {
let surface_attr = face
.get(1)
.ok_or_else(|| Error::geometry("AdvancedFace missing FaceSurface".to_string()))?;
let surface = decoder
.resolve_ref(surface_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve FaceSurface".to_string()))?;
let surface_type = surface.ifc_type.as_str().to_uppercase();
let same_sense = face
.get(2)
.and_then(|a| a.as_enum())
.map(|e| e == "T" || e == "TRUE")
.unwrap_or(true);
let result = if surface_type == "IFCPLANE" {
process_planar_face(face, decoder)
} else if surface_type == "IFCBSPLINESURFACEWITHKNOTS" {
process_bspline_face(&surface, decoder, None)
} else if surface_type == "IFCRATIONALBSPLINESURFACEWITHKNOTS" {
let weights = parse_rational_weights(&surface);
process_bspline_face(&surface, decoder, weights.as_deref())
} else if surface_type == "IFCCYLINDRICALSURFACE" {
process_cylindrical_face(face, &surface, decoder)
} else if surface_type == "IFCSURFACEOFLINEAREXTRUSION"
|| surface_type == "IFCSURFACEOFREVOLUTION"
|| surface_type == "IFCCONICALSURFACE"
|| surface_type == "IFCSPHERICALSURFACE"
|| surface_type == "IFCTOROIDALSURFACE"
{
process_planar_face(face, decoder)
} else {
Ok((Vec::new(), Vec::new()))
};
if !same_sense {
result.map(|(positions, mut indices)| {
for tri in indices.chunks_exact_mut(3) {
tri.swap(0, 2);
}
(positions, indices)
})
} else {
result
}
}
#[inline]
fn bspline_basis(i: usize, p: usize, u: f64, knots: &[f64]) -> f64 {
if p == 0 {
if knots[i] <= u && u < knots[i + 1] {
1.0
} else {
0.0
}
} else {
let left = {
let denom = knots[i + p] - knots[i];
if denom.abs() < 1e-10 {
0.0
} else {
(u - knots[i]) / denom * bspline_basis(i, p - 1, u, knots)
}
};
let right = {
let denom = knots[i + p + 1] - knots[i + 1];
if denom.abs() < 1e-10 {
0.0
} else {
(knots[i + p + 1] - u) / denom * bspline_basis(i + 1, p - 1, u, knots)
}
};
left + right
}
}
fn evaluate_bspline_surface(
u: f64,
v: f64,
u_degree: usize,
v_degree: usize,
control_points: &[Vec<Point3<f64>>],
u_knots: &[f64],
v_knots: &[f64],
weights: Option<&[Vec<f64>]>,
) -> Point3<f64> {
let mut result = Point3::new(0.0, 0.0, 0.0);
let mut weight_sum = 0.0;
for (i, row) in control_points.iter().enumerate() {
let n_i = bspline_basis(i, u_degree, u, u_knots);
for (j, cp) in row.iter().enumerate() {
let n_j = bspline_basis(j, v_degree, v, v_knots);
let basis = n_i * n_j;
if basis.abs() > 1e-10 {
let w = weights
.and_then(|ws| ws.get(i))
.and_then(|row_w| row_w.get(j))
.copied()
.unwrap_or(1.0);
let weighted_basis = basis * w;
result.x += weighted_basis * cp.x;
result.y += weighted_basis * cp.y;
result.z += weighted_basis * cp.z;
weight_sum += weighted_basis;
}
}
}
if weights.is_some() && weight_sum.abs() > 1e-10 {
result.x /= weight_sum;
result.y /= weight_sum;
result.z /= weight_sum;
}
result
}
fn tessellate_bspline_surface(
u_degree: usize,
v_degree: usize,
control_points: &[Vec<Point3<f64>>],
u_knots: &[f64],
v_knots: &[f64],
weights: Option<&[Vec<f64>]>,
u_segments: usize,
v_segments: usize,
) -> Option<(Vec<f32>, Vec<u32>)> {
let mut positions = Vec::new();
let mut indices = Vec::new();
let n_u = control_points.len();
let n_v = control_points.first().map_or(0, |r| r.len());
let min_u_knots = n_u + u_degree + 1;
let min_v_knots = n_v + v_degree + 1;
if u_knots.len() < min_u_knots || v_knots.len() < min_v_knots {
return None;
}
if u_degree >= u_knots.len() || v_degree >= v_knots.len() {
return None;
}
if u_knots.len() - u_degree - 1 >= u_knots.len()
|| v_knots.len() - v_degree - 1 >= v_knots.len()
{
return None;
}
let u_min = u_knots[u_degree];
let u_max = u_knots[u_knots.len() - u_degree - 1];
let v_min = v_knots[v_degree];
let v_max = v_knots[v_knots.len() - v_degree - 1];
for i in 0..=u_segments {
let u = u_min + (u_max - u_min) * (i as f64 / u_segments as f64);
let u = u.min(u_max - 1e-6).max(u_min);
for j in 0..=v_segments {
let v = v_min + (v_max - v_min) * (j as f64 / v_segments as f64);
let v = v.min(v_max - 1e-6).max(v_min);
let point = evaluate_bspline_surface(
u,
v,
u_degree,
v_degree,
control_points,
u_knots,
v_knots,
weights,
);
positions.push(point.x as f32);
positions.push(point.y as f32);
positions.push(point.z as f32);
if i < u_segments && j < v_segments {
let base = (i * (v_segments + 1) + j) as u32;
let next_u = base + (v_segments + 1) as u32;
indices.push(base);
indices.push(base + 1);
indices.push(next_u + 1);
indices.push(base);
indices.push(next_u + 1);
indices.push(next_u);
}
}
}
Some((positions, indices))
}
fn parse_rational_weights(bspline: &DecodedEntity) -> Option<Vec<Vec<f64>>> {
let weights_attr = bspline.get(12)?;
let rows = weights_attr.as_list()?;
let mut result = Vec::with_capacity(rows.len());
for row in rows {
let cols = row.as_list()?;
let row_weights: Vec<f64> = cols.iter().filter_map(|v| v.as_float()).collect();
if row_weights.is_empty() {
return None;
}
result.push(row_weights);
}
Some(result)
}
fn parse_control_points(
bspline: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<Vec<Vec<Point3<f64>>>> {
let cp_list_attr = bspline
.get(2)
.ok_or_else(|| Error::geometry("BSplineSurface missing ControlPointsList".to_string()))?;
let rows = cp_list_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected control point list".to_string()))?;
let mut result = Vec::with_capacity(rows.len());
for row in rows {
let cols = row
.as_list()
.ok_or_else(|| Error::geometry("Expected control point row".to_string()))?;
let mut row_points = Vec::with_capacity(cols.len());
for col in cols {
if let Some(point_id) = col.as_entity_ref() {
let point = decoder.decode_by_id(point_id)?;
let coords = point.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
Error::geometry("CartesianPoint missing coordinates".to_string())
})?;
let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
row_points.push(Point3::new(x, y, z));
}
}
result.push(row_points);
}
Ok(result)
}
fn expand_knots(knot_values: &[f64], multiplicities: &[i64]) -> Vec<f64> {
let mut expanded = Vec::new();
for (knot, &mult) in knot_values.iter().zip(multiplicities.iter()) {
for _ in 0..mult {
expanded.push(*knot);
}
}
expanded
}
fn parse_knot_vectors(bspline: &DecodedEntity) -> Result<(Vec<f64>, Vec<f64>)> {
let u_mult_attr = bspline
.get(7)
.ok_or_else(|| Error::geometry("BSplineSurface missing UMultiplicities".to_string()))?;
let u_mults: Vec<i64> = u_mult_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected U multiplicities list".to_string()))?
.iter()
.filter_map(|v| v.as_int())
.collect();
let v_mult_attr = bspline
.get(8)
.ok_or_else(|| Error::geometry("BSplineSurface missing VMultiplicities".to_string()))?;
let v_mults: Vec<i64> = v_mult_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected V multiplicities list".to_string()))?
.iter()
.filter_map(|v| v.as_int())
.collect();
let u_knots_attr = bspline
.get(9)
.ok_or_else(|| Error::geometry("BSplineSurface missing UKnots".to_string()))?;
let u_knot_values: Vec<f64> = u_knots_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected U knots list".to_string()))?
.iter()
.filter_map(|v| v.as_float())
.collect();
let v_knots_attr = bspline
.get(10)
.ok_or_else(|| Error::geometry("BSplineSurface missing VKnots".to_string()))?;
let v_knot_values: Vec<f64> = v_knots_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected V knots list".to_string()))?
.iter()
.filter_map(|v| v.as_float())
.collect();
let u_knots = expand_knots(&u_knot_values, &u_mults);
let v_knots = expand_knots(&v_knot_values, &v_mults);
Ok((u_knots, v_knots))
}
fn extract_vertex_coords(vertex: &DecodedEntity, decoder: &mut EntityDecoder) -> Option<Point3<f64>> {
let point_attr = vertex.get(0)?;
let point = decoder.resolve_ref(point_attr).ok().flatten()?;
let coords = point.get(0).and_then(|v| v.as_list())?;
let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
Some(Point3::new(x, y, z))
}
fn evaluate_bspline_curve(
t: f64,
degree: usize,
control_points: &[Point3<f64>],
knots: &[f64],
) -> Point3<f64> {
let mut result = Point3::new(0.0, 0.0, 0.0);
for (i, cp) in control_points.iter().enumerate() {
let basis = bspline_basis(i, degree, t, knots);
if basis.abs() > 1e-10 {
result.x += basis * cp.x;
result.y += basis * cp.y;
result.z += basis * cp.z;
}
}
result
}
fn sample_bspline_edge_curve(
curve: &DecodedEntity,
start: &Point3<f64>,
curve_forward: bool,
decoder: &mut EntityDecoder,
) -> Vec<Point3<f64>> {
let degree = curve.get_float(0).unwrap_or(3.0) as usize;
let cp_list = match curve.get(1).and_then(|a| a.as_list()) {
Some(list) => list,
None => return vec![*start],
};
let control_points: Vec<Point3<f64>> = cp_list
.iter()
.filter_map(|ref_val| {
let id = ref_val.as_entity_ref()?;
let pt = decoder.decode_by_id(id).ok()?;
let coords = pt.get(0)?.as_list()?;
let x = coords.first()?.as_float().unwrap_or(0.0);
let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
Some(Point3::new(x, y, z))
})
.collect();
if control_points.len() <= degree {
return vec![*start];
}
let mults: Vec<i64> = curve
.get(6)
.and_then(|a| a.as_list())
.map(|l| l.iter().filter_map(|v| v.as_int()).collect())
.unwrap_or_default();
let knot_values: Vec<f64> = curve
.get(7)
.and_then(|a| a.as_list())
.map(|l| l.iter().filter_map(|v| v.as_float()).collect())
.unwrap_or_default();
if mults.is_empty() || knot_values.is_empty() {
return vec![*start];
}
let knots = expand_knots(&knot_values, &mults);
let t_min = knots[degree];
let t_max = knots[knots.len() - degree - 1];
let n_segments = (control_points.len() * 2).clamp(4, 16);
let mut points = Vec::with_capacity(n_segments + 1);
points.push(*start);
for i in 1..n_segments {
let frac = i as f64 / n_segments as f64;
let t = if curve_forward {
t_min + (t_max - t_min) * frac
} else {
t_max - (t_max - t_min) * frac
};
let t_clamped = t.min(t_max - 1e-6).max(t_min);
let pt = evaluate_bspline_curve(t_clamped, degree, &control_points, &knots);
if let Some(prev) = points.last() {
let dist_sq = (pt.x - prev.x).powi(2) + (pt.y - prev.y).powi(2) + (pt.z - prev.z).powi(2);
if dist_sq < 1e-12 {
continue;
}
}
points.push(pt);
}
points
}
fn extract_edge_loop_points(
loop_entity: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Vec<Point3<f64>> {
let edges = match loop_entity.get(0).and_then(|a| a.as_list()) {
Some(e) => e,
None => return Vec::new(),
};
let mut polygon_points = Vec::new();
for edge_ref in edges {
let edge_id = match edge_ref.as_entity_ref() {
Some(id) => id,
None => continue,
};
let oriented_edge = match decoder.decode_by_id(edge_id) {
Ok(e) => e,
Err(_) => continue,
};
let orientation = oriented_edge
.get(3)
.and_then(|a| a.as_enum())
.map(|e| e == "T" || e == "TRUE")
.unwrap_or(true);
let edge_curve = match oriented_edge
.get(2)
.and_then(|attr| decoder.resolve_ref(attr).ok().flatten())
{
Some(ec) => ec,
None => {
let vertex = oriented_edge
.get(0)
.and_then(|attr| decoder.resolve_ref(attr).ok().flatten());
if let Some(v) = vertex {
if let Some(pt) = extract_vertex_coords(&v, decoder) {
polygon_points.push(pt);
}
}
continue;
}
};
let edge_same_sense = edge_curve.get(3).and_then(|a| a.as_enum())
.map(|e| e == "T" || e == "TRUE").unwrap_or(true);
let curve_forward = orientation == edge_same_sense;
let start_vertex = edge_curve
.get(0)
.and_then(|attr| decoder.resolve_ref(attr).ok().flatten());
let end_vertex = edge_curve
.get(1)
.and_then(|attr| decoder.resolve_ref(attr).ok().flatten());
let edge_start_pt = start_vertex.as_ref().and_then(|v| extract_vertex_coords(v, decoder));
let edge_end_pt = end_vertex.as_ref().and_then(|v| extract_vertex_coords(v, decoder));
let (walk_start, _walk_end) = if orientation {
(edge_start_pt, edge_end_pt)
} else {
(edge_end_pt, edge_start_pt)
};
let edge_geometry = edge_curve
.get(2)
.and_then(|attr| decoder.resolve_ref(attr).ok().flatten());
if let Some(geom) = edge_geometry {
let geom_type = geom.ifc_type.as_str().to_uppercase();
if geom_type == "IFCBSPLINECURVEWITHKNOTS" {
let s = walk_start.unwrap_or(Point3::new(0.0, 0.0, 0.0));
let sampled = sample_bspline_edge_curve(&geom, &s, curve_forward, decoder);
polygon_points.extend(sampled);
continue;
}
}
if let Some(pt) = walk_start {
polygon_points.push(pt);
}
}
polygon_points
}
fn process_planar_face(
face: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<(Vec<f32>, Vec<u32>)> {
let bounds_attr = face
.get(0)
.ok_or_else(|| Error::geometry("AdvancedFace missing Bounds".to_string()))?;
let bounds = bounds_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected bounds list".to_string()))?;
let mut positions = Vec::new();
let mut indices = Vec::new();
for bound in bounds {
if let Some(bound_id) = bound.as_entity_ref() {
let bound_entity = decoder.decode_by_id(bound_id)?;
let loop_attr = bound_entity
.get(0)
.ok_or_else(|| Error::geometry("FaceBound missing Bound".to_string()))?;
let loop_entity = decoder
.resolve_ref(loop_attr)?
.ok_or_else(|| Error::geometry("Failed to resolve loop".to_string()))?;
if !loop_entity.ifc_type.as_str().eq_ignore_ascii_case("IFCEDGELOOP") {
continue;
}
let polygon_points = extract_edge_loop_points(&loop_entity, decoder);
if polygon_points.len() >= 3 {
let base_idx = (positions.len() / 3) as u32;
for point in &polygon_points {
positions.push(point.x as f32);
positions.push(point.y as f32);
positions.push(point.z as f32);
}
let normal = calculate_polygon_normal(&polygon_points);
let (points_2d, _, _, _) = project_to_2d(&polygon_points, &normal);
match triangulate_polygon(&points_2d) {
Ok(tri_indices) => {
for idx in tri_indices {
indices.push(base_idx + idx as u32);
}
}
Err(_) => {
for i in 1..polygon_points.len() - 1 {
indices.push(base_idx);
indices.push(base_idx + i as u32);
indices.push(base_idx + i as u32 + 1);
}
}
}
}
}
}
Ok((positions, indices))
}
fn process_bspline_face(
bspline: &DecodedEntity,
decoder: &mut EntityDecoder,
weights: Option<&[Vec<f64>]>,
) -> Result<(Vec<f32>, Vec<u32>)> {
let u_degree = bspline.get_float(0).unwrap_or(3.0) as usize;
let v_degree = bspline.get_float(1).unwrap_or(1.0) as usize;
let control_points = parse_control_points(bspline, decoder)?;
let (u_knots, v_knots) = parse_knot_vectors(bspline)?;
let u_segments = (control_points.len() * 3).clamp(8, 24);
let v_segments = if !control_points.is_empty() {
(control_points[0].len() * 3).clamp(4, 24)
} else {
4
};
match tessellate_bspline_surface(
u_degree,
v_degree,
&control_points,
&u_knots,
&v_knots,
weights,
u_segments,
v_segments,
) {
Some((positions, indices)) => Ok((positions, indices)),
None => Ok((Vec::new(), Vec::new())),
}
}
fn process_cylindrical_face(
face: &DecodedEntity,
surface: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<(Vec<f32>, Vec<u32>)> {
let radius = surface
.get(1)
.and_then(|v| v.as_float())
.ok_or_else(|| Error::geometry("CylindricalSurface missing Radius".to_string()))?;
let position_attr = surface.get(0);
let axis_transform = if let Some(attr) = position_attr {
if let Some(pos_id) = attr.as_entity_ref() {
get_axis2_placement_transform_by_id(pos_id, decoder)?
} else {
Matrix4::identity()
}
} else {
Matrix4::identity()
};
let bounds_attr = face
.get(0)
.ok_or_else(|| Error::geometry("AdvancedFace missing Bounds".to_string()))?;
let bounds = bounds_attr
.as_list()
.ok_or_else(|| Error::geometry("Expected bounds list".to_string()))?;
let mut boundary_points: Vec<Point3<f64>> = Vec::new();
for bound in bounds {
if let Some(bound_id) = bound.as_entity_ref() {
let bound_entity = decoder.decode_by_id(bound_id)?;
let loop_attr = bound_entity
.get(0)
.ok_or_else(|| Error::geometry("FaceBound missing Bound".to_string()))?;
if let Some(loop_entity) = decoder.resolve_ref(loop_attr)? {
if loop_entity
.ifc_type
.as_str()
.eq_ignore_ascii_case("IFCEDGELOOP")
{
if let Some(edges_attr) = loop_entity.get(0) {
if let Some(edges) = edges_attr.as_list() {
for edge_ref in edges {
if let Some(edge_id) = edge_ref.as_entity_ref() {
if let Ok(oriented_edge) = decoder.decode_by_id(edge_id) {
let start_vertex = oriented_edge
.get(0)
.and_then(|attr| {
decoder.resolve_ref(attr).ok().flatten()
});
let vertex = if start_vertex.is_some() {
start_vertex
} else if let Some(edge_elem_attr) =
oriented_edge.get(2)
{
if let Some(edge_curve) = decoder
.resolve_ref(edge_elem_attr)
.ok()
.flatten()
{
edge_curve.get(0).and_then(|attr| {
decoder.resolve_ref(attr).ok().flatten()
})
} else {
None
}
} else {
None
};
if let Some(vertex) = vertex {
if let Some(point_attr) = vertex.get(0) {
if let Some(point) = decoder
.resolve_ref(point_attr)
.ok()
.flatten()
{
if let Some(coords) =
point.get(0).and_then(|v| v.as_list())
{
let x = coords
.first()
.and_then(|v| v.as_float())
.unwrap_or(0.0);
let y = coords
.get(1)
.and_then(|v| v.as_float())
.unwrap_or(0.0);
let z = coords
.get(2)
.and_then(|v| v.as_float())
.unwrap_or(0.0);
boundary_points
.push(Point3::new(x, y, z));
}
}
}
}
}
}
}
}
}
}
}
}
}
if boundary_points.is_empty() {
return Ok((Vec::new(), Vec::new()));
}
let inv_transform = axis_transform
.try_inverse()
.unwrap_or(Matrix4::identity());
let local_points: Vec<Point3<f64>> = boundary_points
.iter()
.map(|p| inv_transform.transform_point(p))
.collect();
let mut min_angle = f64::MAX;
let mut max_angle = f64::MIN;
let mut min_z = f64::MAX;
let mut max_z = f64::MIN;
for p in &local_points {
let angle = p.y.atan2(p.x);
min_angle = min_angle.min(angle);
max_angle = max_angle.max(angle);
min_z = min_z.min(p.z);
max_z = max_z.max(p.z);
}
if max_angle - min_angle > std::f64::consts::PI * 1.5 {
let positive_angles: Vec<f64> = local_points
.iter()
.map(|p| {
let a = p.y.atan2(p.x);
if a < 0.0 {
a + 2.0 * std::f64::consts::PI
} else {
a
}
})
.collect();
min_angle = positive_angles.iter().cloned().fold(f64::MAX, f64::min);
max_angle = positive_angles.iter().cloned().fold(f64::MIN, f64::max);
}
let angle_span = max_angle - min_angle;
let height = max_z - min_z;
let angle_segments =
((angle_span / (std::f64::consts::PI / 12.0)).ceil() as usize).clamp(3, 16);
let height_segments = ((height / (radius * 2.0)).ceil() as usize).clamp(1, 4);
let mut positions = Vec::new();
let mut indices = Vec::new();
for h in 0..=height_segments {
let z = min_z + (height * h as f64 / height_segments as f64);
for a in 0..=angle_segments {
let angle = min_angle + (angle_span * a as f64 / angle_segments as f64);
let x = radius * angle.cos();
let y = radius * angle.sin();
let local_point = Point3::new(x, y, z);
let world_point = axis_transform.transform_point(&local_point);
positions.push(world_point.x as f32);
positions.push(world_point.y as f32);
positions.push(world_point.z as f32);
}
}
let cols = angle_segments + 1;
for h in 0..height_segments {
for a in 0..angle_segments {
let base = (h * cols + a) as u32;
let next_row = base + cols as u32;
indices.push(base);
indices.push(base + 1);
indices.push(next_row + 1);
indices.push(base);
indices.push(next_row + 1);
indices.push(next_row);
}
}
Ok((positions, indices))
}