use crate::{Point3, TessellationQuality};
use ifc_lite_core::{DecodedEntity, EntityDecoder};
use super::conics::{sample_circle_edge_curve, sample_ellipse_edge_curve};
use super::curves::{
extract_vertex_coords, read_trim_parameter, sample_bspline_edge_curve,
sample_bspline_edge_curve_range,
};
use super::polyline::sample_curve_polyline;
pub(super) fn extract_edge_loop_points(
loop_entity: &DecodedEntity,
decoder: &mut EntityDecoder,
quality: TessellationQuality,
) -> 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 {
if let Some(sampled) = sample_edge_geometry(
&geom,
&walk_start,
&_walk_end,
curve_forward,
decoder,
quality,
) {
polygon_points.extend(sampled);
continue;
}
}
if let Some(pt) = walk_start {
polygon_points.push(pt);
}
}
polygon_points
}
fn sample_edge_geometry(
geom: &DecodedEntity,
walk_start: &Option<Point3<f64>>,
walk_end: &Option<Point3<f64>>,
curve_forward: bool,
decoder: &mut EntityDecoder,
quality: TessellationQuality,
) -> Option<Vec<Point3<f64>>> {
let geom_type = geom.ifc_type.as_str().to_uppercase();
match geom_type.as_str() {
"IFCBSPLINECURVEWITHKNOTS" | "IFCRATIONALBSPLINECURVEWITHKNOTS" => {
let s = walk_start.unwrap_or(Point3::new(0.0, 0.0, 0.0));
Some(sample_bspline_edge_curve(geom, &s, curve_forward, decoder, quality))
}
"IFCCIRCLE" => {
let (s, e) = ((*walk_start)?, (*walk_end)?);
Some(sample_circle_edge_curve(geom, &s, &e, curve_forward, decoder, quality))
}
"IFCELLIPSE" => {
let (s, e) = ((*walk_start)?, (*walk_end)?);
Some(sample_ellipse_edge_curve(geom, &s, &e, curve_forward, decoder, quality))
}
"IFCTRIMMEDCURVE" => {
let basis = geom.get(0).and_then(|a| decoder.resolve_ref(a).ok().flatten())?;
let sense = geom
.get(3)
.and_then(|a| a.as_enum())
.map(|e| e == "T" || e == "TRUE")
.unwrap_or(true);
let basis_forward = curve_forward == sense;
let basis_type = basis.ifc_type.as_str().to_uppercase();
match basis_type.as_str() {
"IFCCIRCLE" => {
let (s, e) = ((*walk_start)?, (*walk_end)?);
Some(sample_circle_edge_curve(&basis, &s, &e, basis_forward, decoder, quality))
}
"IFCELLIPSE" => {
let (s, e) = ((*walk_start)?, (*walk_end)?);
Some(sample_ellipse_edge_curve(&basis, &s, &e, basis_forward, decoder, quality))
}
"IFCBSPLINECURVEWITHKNOTS" | "IFCRATIONALBSPLINECURVEWITHKNOTS" => {
let s = (*walk_start)?;
let trim_range = match (
geom.get(1).and_then(read_trim_parameter),
geom.get(2).and_then(read_trim_parameter),
) {
(Some(a), Some(b)) => Some((a, b)),
_ => None,
};
Some(sample_bspline_edge_curve_range(
&basis,
&s,
basis_forward,
trim_range,
decoder,
quality,
))
}
_ => None,
}
}
"IFCCOMPOSITECURVE" => {
sample_composite_curve_edge(geom, walk_start, curve_forward, decoder, quality)
}
"IFCPOLYLINE" => sample_polyline_edge(geom, walk_start, curve_forward, decoder),
_ => None,
}
}
fn sample_composite_curve_edge(
geom: &DecodedEntity,
walk_start: &Option<Point3<f64>>,
curve_forward: bool,
decoder: &mut EntityDecoder,
quality: TessellationQuality,
) -> Option<Vec<Point3<f64>>> {
let segments = geom.get(0).and_then(|a| a.as_list())?;
let mut pts: Vec<Point3<f64>> = Vec::new();
for seg_ref in segments {
let Some(seg_id) = seg_ref.as_entity_ref() else { continue };
let Ok(segment) = decoder.decode_by_id(seg_id) else { continue };
let same_sense = segment
.get(1)
.and_then(|a| a.as_enum())
.map(|e| e == "T" || e == "TRUE")
.unwrap_or(true);
let Some(parent) = segment.get(2).and_then(|a| decoder.resolve_ref(a).ok().flatten())
else {
continue;
};
let mut seg_pts = sample_curve_polyline(&parent, decoder, quality);
if seg_pts.is_empty() {
continue;
}
if !same_sense {
seg_pts.reverse();
}
if let (Some(last), Some(first)) = (pts.last(), seg_pts.first()) {
if (last - first).norm_squared() < 1e-12 {
seg_pts.remove(0);
}
}
pts.extend(seg_pts);
}
orient_and_trim_edge_polyline(pts, walk_start, curve_forward)
}
fn sample_polyline_edge(
geom: &DecodedEntity,
walk_start: &Option<Point3<f64>>,
curve_forward: bool,
decoder: &mut EntityDecoder,
) -> Option<Vec<Point3<f64>>> {
let refs = geom.get(0).and_then(|a| a.as_list())?;
let mut pts: Vec<Point3<f64>> = Vec::new();
for r in refs {
let Some(id) = r.as_entity_ref() else { continue };
let Ok(p) = decoder.decode_by_id(id) else { continue };
let Some(coords) = p.get(0).and_then(|v| v.as_list()) else { continue };
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);
pts.push(Point3::new(x, y, z));
}
orient_and_trim_edge_polyline(pts, walk_start, curve_forward)
}
fn orient_and_trim_edge_polyline(
mut pts: Vec<Point3<f64>>,
walk_start: &Option<Point3<f64>>,
curve_forward: bool,
) -> Option<Vec<Point3<f64>>> {
if pts.len() < 2 {
return None;
}
match walk_start {
Some(ws) => {
let d_first = (pts.first().unwrap() - ws).norm_squared();
let d_last = (pts.last().unwrap() - ws).norm_squared();
if d_last < d_first {
pts.reverse();
}
}
None => {
if !curve_forward {
pts.reverse();
}
}
}
pts.pop();
Some(pts)
}
pub(super) fn extract_edge_loop_points_for_bounds(
face: &DecodedEntity,
decoder: &mut EntityDecoder,
quality: TessellationQuality,
) -> Vec<Point3<f64>> {
let mut all = Vec::new();
let bounds = match face.get(0).and_then(|a| a.as_list()) {
Some(b) => b,
None => return all,
};
for bound in bounds {
if let Some(bound_id) = bound.as_entity_ref() {
if let Ok(bound_entity) = decoder.decode_by_id(bound_id) {
if let Some(loop_attr) = bound_entity.get(0) {
if let Some(loop_entity) = decoder.resolve_ref(loop_attr).ok().flatten() {
if loop_entity
.ifc_type
.as_str()
.eq_ignore_ascii_case("IFCEDGELOOP")
{
all.extend(extract_edge_loop_points(&loop_entity, decoder, quality));
}
}
}
}
}
}
all
}