use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcType};
use std::collections::HashMap;
use super::color::resolve_color_via_styles;
use super::primitives::{SymbolicData, SymbolicFillArea};
use super::transform::{circle_center, Transform2D};
#[allow(clippy::too_many_arguments)]
pub(super) fn extract_annotation_fill_area(
item: &DecodedEntity,
decoder: &mut EntityDecoder,
express_id: u32,
ifc_type: &str,
rep_identifier: &str,
unit_scale: f32,
transform: &Transform2D,
rtc_x: f32,
rtc_z: f32,
styled_items: &HashMap<u32, Vec<u32>>,
out: &mut SymbolicData,
) {
let Some(outer_ref) = item.get_ref(0) else { return };
let mut points = extract_curve_ring(outer_ref, decoder, unit_scale, transform, rtc_x, rtc_z);
if points.len() < 6 {
return;
}
let mut holes_offsets: Vec<u32> = Vec::new();
if let Some(inners_attr) = item.get(1) {
if let Ok(inner_list) = decoder.resolve_ref_list(inners_attr) {
for inner in inner_list {
let hole = extract_curve_ring(inner.id, decoder, unit_scale, transform, rtc_x, rtc_z);
if hole.len() >= 6 {
let vertex_index = (points.len() / 2) as u32;
holes_offsets.push(vertex_index);
points.extend(hole);
}
}
}
}
let fill_color = resolve_color_via_styles(item.id, styled_items, decoder)
.unwrap_or([0.0, 0.0, 0.0, 1.0]);
let world_y = sample_curve_world_y(outer_ref, decoder, unit_scale) + transform.tz;
out.fills.push(SymbolicFillArea {
express_id,
ifc_type: ifc_type.to_string(),
points,
holes_offsets,
fill_color,
has_hatching: false,
hatch_spacing: 0.0,
hatch_angle: 0.0,
hatch_angle_secondary: f32::NAN,
hatch_line_width: 0.0,
world_y,
representation: rep_identifier.to_string(),
});
}
fn extract_curve_ring(
curve_id: u32,
decoder: &mut EntityDecoder,
unit_scale: f32,
transform: &Transform2D,
rtc_x: f32,
rtc_z: f32,
) -> Vec<f32> {
let Ok(curve) = decoder.decode_by_id(curve_id) else {
return Vec::new();
};
match curve.ifc_type {
IfcType::IfcPolyline => {
let Some(points_attr) = curve.get(0) else { return Vec::new() };
let Ok(point_entities) = decoder.resolve_ref_list(points_attr) else {
return Vec::new();
};
let mut out = Vec::with_capacity(point_entities.len() * 2);
for pe in point_entities {
if pe.ifc_type != IfcType::IfcCartesianPoint {
continue;
}
let Some(coords) = pe.get(0).and_then(|a| a.as_list()) else { continue };
let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32 * unit_scale;
let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32 * unit_scale;
let (wx, wy) = transform.transform_point(x, y);
out.push(wx - rtc_x);
out.push(-wy + rtc_z);
}
out
}
IfcType::IfcIndexedPolyCurve => {
let Some(points_ref) = curve.get_ref(0) else { return Vec::new() };
let Ok(points_entity) = decoder.decode_by_id(points_ref) else { return Vec::new() };
let Some(coord_list_attr) = points_entity.get(0) else { return Vec::new() };
let Some(coord_list) = coord_list_attr.as_list() else { return Vec::new() };
let mut out = Vec::with_capacity(coord_list.len() * 2);
for tuple in coord_list {
let Some(coords) = tuple.as_list() else { continue };
let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0) as f32 * unit_scale;
let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0) as f32 * unit_scale;
let (wx, wy) = transform.transform_point(x, y);
out.push(wx - rtc_x);
out.push(-wy + rtc_z);
}
out
}
IfcType::IfcEllipse => {
let semi_a = curve.get(1).and_then(|a| a.as_float()).unwrap_or(0.0) as f32 * unit_scale;
let semi_b = curve.get(2).and_then(|a| a.as_float()).unwrap_or(0.0) as f32 * unit_scale;
if semi_a <= 0.0 || semi_b <= 0.0 || !semi_a.is_finite() || !semi_b.is_finite() {
return Vec::new();
}
let (cx_local, cy_local, _) = circle_center(&curve, decoder, unit_scale);
const SEGMENTS: usize = 64;
let mut out = Vec::with_capacity(SEGMENTS * 2);
for i in 0..SEGMENTS {
let theta = (i as f32) * std::f32::consts::TAU / (SEGMENTS as f32);
let lx = cx_local + semi_a * theta.cos();
let ly = cy_local + semi_b * theta.sin();
let (wx, wy) = transform.transform_point(lx, ly);
out.push(wx - rtc_x);
out.push(-wy + rtc_z);
}
out
}
IfcType::IfcCircle => {
let radius = curve.get(1).and_then(|a| a.as_float()).unwrap_or(0.0) as f32 * unit_scale;
if radius <= 0.0 || !radius.is_finite() {
return Vec::new();
}
let (cx_local, cy_local, _) = circle_center(&curve, decoder, unit_scale);
let seg_count = if radius < 0.05 { 32 } else { 64 };
let mut out = Vec::with_capacity(seg_count * 2);
let two_pi = std::f32::consts::TAU;
for i in 0..seg_count {
let theta = (i as f32) * two_pi / (seg_count as f32);
let lx = cx_local + radius * theta.cos();
let ly = cy_local + radius * theta.sin();
let (wx, wy) = transform.transform_point(lx, ly);
out.push(wx - rtc_x);
out.push(-wy + rtc_z);
}
out
}
_ => Vec::new(),
}
}
fn sample_curve_world_y(curve_id: u32, decoder: &mut EntityDecoder, unit_scale: f32) -> f32 {
let Ok(curve) = decoder.decode_by_id(curve_id) else { return 0.0 };
match curve.ifc_type {
IfcType::IfcPolyline => {
let Some(points_attr) = curve.get(0) else { return 0.0 };
let Ok(point_entities) = decoder.resolve_ref_list(points_attr) else { return 0.0 };
for pe in point_entities {
if pe.ifc_type != IfcType::IfcCartesianPoint {
continue;
}
if let Some(coords) = pe.get(0).and_then(|a| a.as_list()) {
let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0) as f32 * unit_scale;
return z;
}
}
0.0
}
IfcType::IfcCircle | IfcType::IfcEllipse => {
let (_, _, z) = circle_center(&curve, decoder, unit_scale);
z
}
IfcType::IfcIndexedPolyCurve => {
let Some(points_ref) = curve.get_ref(0) else { return 0.0 };
let Ok(points_entity) = decoder.decode_by_id(points_ref) else { return 0.0 };
let Some(coord_list_attr) = points_entity.get(0) else { return 0.0 };
let Some(coord_list) = coord_list_attr.as_list() else { return 0.0 };
if let Some(first) = coord_list.first().and_then(|v| v.as_list()) {
return first.get(2).and_then(|v| v.as_float()).unwrap_or(0.0) as f32 * unit_scale;
}
0.0
}
_ => 0.0,
}
}