1use super::outline::{approximate_arc_3pt_3d, same_point_3d, trim_polyline};
6use super::ProfileProcessor;
7use crate::tessellation::scale_segments;
8use crate::{Error, Point3, Result, Vector3};
9use ifc_lite_core::{AttributeValue, DecodedEntity, EntityDecoder, IfcType};
10use std::f64::consts::PI;
11
12impl ProfileProcessor {
13 fn read_line_3d(
20 &self,
21 line: &DecodedEntity,
22 decoder: &mut EntityDecoder,
23 ) -> Result<(Point3<f64>, Vector3<f64>)> {
24 let pnt_attr = line
25 .get(0)
26 .ok_or_else(|| Error::geometry("Line missing Pnt".to_string()))?;
27 let pnt = decoder
28 .resolve_ref(pnt_attr)?
29 .ok_or_else(|| Error::geometry("Failed to resolve Line Pnt".to_string()))?;
30 let coords = pnt
31 .get(0)
32 .and_then(|v| v.as_list())
33 .ok_or_else(|| Error::geometry("Line Pnt missing coordinates".to_string()))?;
34 let origin = Point3::new(
35 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
36 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
37 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
38 );
39
40 let dir_attr = line
42 .get(1)
43 .ok_or_else(|| Error::geometry("Line missing Dir".to_string()))?;
44 let vector = decoder
45 .resolve_ref(dir_attr)?
46 .ok_or_else(|| Error::geometry("Failed to resolve Line Dir".to_string()))?;
47 let magnitude = vector.get(1).and_then(|v| v.as_float()).unwrap_or(1.0);
48 let orientation = vector
49 .get(0)
50 .and_then(|a| decoder.resolve_ref(a).ok().flatten())
51 .and_then(|d| {
52 let coords = d.get(0).and_then(|v| v.as_list())?;
53 Some(Vector3::new(
54 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
55 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
56 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
57 ))
58 })
59 .and_then(|v| v.try_normalize(1e-12))
60 .unwrap_or_else(|| Vector3::new(1.0, 0.0, 0.0));
61 Ok((origin, orientation * magnitude))
62 }
63
64 pub fn get_line_points_3d(
68 &self,
69 line: &DecodedEntity,
70 decoder: &mut EntityDecoder,
71 t_start: f64,
72 t_end: f64,
73 ) -> Result<Vec<Point3<f64>>> {
74 let (origin, v) = self.read_line_3d(line, decoder)?;
75 Ok(vec![origin + v * t_start, origin + v * t_end])
76 }
77
78 fn line_trim_point_3d(
83 &self,
84 attr: Option<&AttributeValue>,
85 origin: &Point3<f64>,
86 v: &Vector3<f64>,
87 prefer_cartesian: bool,
88 decoder: &mut EntityDecoder,
89 ) -> Option<Point3<f64>> {
90 let list = attr?.as_list()?;
91 let mut param: Option<f64> = None;
92 let mut point: Option<Point3<f64>> = None;
93 for item in list {
94 if let Some(inner) = item.as_list() {
96 if let Some(name) = inner.first().and_then(|v| v.as_string()) {
97 if name == "IFCPARAMETERVALUE" {
98 param = inner.get(1).and_then(|v| v.as_float());
99 continue;
100 }
101 }
102 }
103 if item.as_entity_ref().is_some() {
104 if let Ok(Some(pt)) = decoder.resolve_ref(item) {
105 if pt.ifc_type == IfcType::IfcCartesianPoint {
106 if let Some(coords) = pt.get(0).and_then(|v| v.as_list()) {
107 point = Some(Point3::new(
108 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
109 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
110 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
111 ));
112 }
113 }
114 }
115 continue;
116 }
117 if let Some(f) = item.as_float() {
118 param = Some(f);
119 }
120 }
121 match (prefer_cartesian, point, param) {
122 (true, Some(p), _) => Some(p),
123 (_, _, Some(t)) => Some(origin + v * t),
124 (_, Some(p), None) => Some(p),
125 _ => None,
126 }
127 }
128
129 pub(super) fn process_trimmed_line_3d(
133 &self,
134 trimmed: &DecodedEntity,
135 line: &DecodedEntity,
136 decoder: &mut EntityDecoder,
137 ) -> Result<Vec<Point3<f64>>> {
138 let (origin, v) = self.read_line_3d(line, decoder)?;
139 let prefer_cartesian = trimmed
140 .get(4)
141 .and_then(|m| m.as_enum())
142 .map(|m| m == "CARTESIAN")
143 .unwrap_or(false);
144 let p_start = self.line_trim_point_3d(trimmed.get(1), &origin, &v, prefer_cartesian, decoder);
145 let p_end = self.line_trim_point_3d(trimmed.get(2), &origin, &v, prefer_cartesian, decoder);
146 let sense = trimmed
147 .get(3)
148 .and_then(|x| match x {
149 AttributeValue::Enum(s) => Some(s == "T"),
150 _ => None,
151 })
152 .unwrap_or(true);
153 let start = p_start.unwrap_or(origin);
154 let end = p_end.unwrap_or(origin + v);
155 Ok(if sense {
156 vec![start, end]
157 } else {
158 vec![end, start]
159 })
160 }
161
162 fn read_conic_placement_3d(
169 &self,
170 curve: &DecodedEntity,
171 decoder: &mut EntityDecoder,
172 ) -> Result<(Point3<f64>, Vector3<f64>, Vector3<f64>)> {
173 let position_attr = curve
174 .get(0)
175 .ok_or_else(|| Error::geometry("Conic missing Position".to_string()))?;
176 let position = decoder
177 .resolve_ref(position_attr)?
178 .ok_or_else(|| Error::geometry("Failed to resolve conic position".to_string()))?;
179
180 if position.ifc_type == IfcType::IfcAxis2Placement3D {
181 let loc_attr = position
183 .get(0)
184 .ok_or_else(|| Error::geometry("Axis2Placement3D missing Location".to_string()))?;
185 let loc = decoder
186 .resolve_ref(loc_attr)?
187 .ok_or_else(|| Error::geometry("Failed to resolve location".to_string()))?;
188 let coords = loc
189 .get(0)
190 .and_then(|v| v.as_list())
191 .ok_or_else(|| Error::geometry("Location missing coordinates".to_string()))?;
192 let center = Point3::new(
193 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
194 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
195 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
196 );
197
198 let z_axis = if let Some(axis_attr) = position.get(1) {
200 if !axis_attr.is_null() {
201 let axis = decoder.resolve_ref(axis_attr)?;
202 if let Some(axis) = axis {
203 let coords = axis.get(0).and_then(|v| v.as_list());
204 if let Some(coords) = coords {
205 Vector3::new(
206 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
207 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
208 coords.get(2).and_then(|v| v.as_float()).unwrap_or(1.0),
209 )
210 .normalize()
211 } else {
212 Vector3::new(0.0, 0.0, 1.0)
213 }
214 } else {
215 Vector3::new(0.0, 0.0, 1.0)
216 }
217 } else {
218 Vector3::new(0.0, 0.0, 1.0)
219 }
220 } else {
221 Vector3::new(0.0, 0.0, 1.0)
222 };
223
224 let x_axis = if let Some(ref_attr) = position.get(2) {
226 if !ref_attr.is_null() {
227 let ref_dir = decoder.resolve_ref(ref_attr)?;
228 if let Some(ref_dir) = ref_dir {
229 let coords = ref_dir.get(0).and_then(|v| v.as_list());
230 if let Some(coords) = coords {
231 Vector3::new(
232 coords.first().and_then(|v| v.as_float()).unwrap_or(1.0),
233 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
234 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
235 )
236 .normalize()
237 } else {
238 Vector3::new(1.0, 0.0, 0.0)
239 }
240 } else {
241 Vector3::new(1.0, 0.0, 0.0)
242 }
243 } else {
244 Vector3::new(1.0, 0.0, 0.0)
245 }
246 } else {
247 Vector3::new(1.0, 0.0, 0.0)
248 };
249
250 let y_axis = z_axis.cross(&x_axis).normalize();
252
253 Ok((center, x_axis, y_axis))
254 } else {
255 let loc_attr = position.get(0);
259 let (cx, cy) = if let Some(attr) = loc_attr {
260 let loc = decoder.resolve_ref(attr)?;
261 if let Some(loc) = loc {
262 let coords = loc.get(0).and_then(|v| v.as_list());
263 if let Some(coords) = coords {
264 (
265 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
266 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
267 )
268 } else {
269 (0.0, 0.0)
270 }
271 } else {
272 (0.0, 0.0)
273 }
274 } else {
275 (0.0, 0.0)
276 };
277 let x_axis = position
278 .get(1)
279 .filter(|a| !a.is_null())
280 .and_then(|a| decoder.resolve_ref(a).ok().flatten())
281 .and_then(|d| {
282 let coords = d.get(0).and_then(|v| v.as_list())?;
283 Some(Vector3::new(
284 coords.first().and_then(|v| v.as_float()).unwrap_or(1.0),
285 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
286 0.0,
287 ))
288 })
289 .and_then(|v| v.try_normalize(1e-12))
290 .unwrap_or_else(|| Vector3::new(1.0, 0.0, 0.0));
291 let y_axis = Vector3::new(-x_axis.y, x_axis.x, 0.0);
293 Ok((Point3::new(cx, cy, 0.0), x_axis, y_axis))
294 }
295 }
296
297 pub(super) fn process_circle_3d(
299 &self,
300 curve: &DecodedEntity,
301 decoder: &mut EntityDecoder,
302 ) -> Result<Vec<Point3<f64>>> {
303 let radius = curve
305 .get_float(1)
306 .ok_or_else(|| Error::geometry("Circle missing Radius".to_string()))?;
307
308 let (center, x_axis, y_axis) = self.read_conic_placement_3d(curve, decoder)?;
309
310 let segments = scale_segments(24, 8, 96, self.quality());
312 let mut points = Vec::with_capacity(segments + 1);
313
314 for i in 0..=segments {
315 let angle = 2.0 * std::f64::consts::PI * i as f64 / segments as f64;
316 let p = center + x_axis * (radius * angle.cos()) + y_axis * (radius * angle.sin());
317 points.push(p);
318 }
319
320 Ok(points)
321 }
322
323 pub(super) fn process_trimmed_conic_3d(
333 &self,
334 trimmed: &DecodedEntity,
335 basis: &DecodedEntity,
336 decoder: &mut EntityDecoder,
337 ) -> Result<Vec<Point3<f64>>> {
338 let radius = basis.get_float(1).unwrap_or(1.0);
339 let radius2 = if basis.ifc_type == IfcType::IfcEllipse {
340 basis.get_float(2).unwrap_or(radius)
341 } else {
342 radius
343 };
344
345 let (center, x_axis, y_axis) = self.read_conic_placement_3d(basis, decoder)?;
346
347 let prefer_cartesian = trimmed
350 .get(4)
351 .and_then(|v| v.as_enum())
352 .map(|m| m == "CARTESIAN")
353 .unwrap_or(false);
354
355 let sense = trimmed
356 .get(3)
357 .and_then(|v| match v {
358 AttributeValue::Enum(s) => Some(s == "T"),
359 _ => None,
360 })
361 .unwrap_or(true);
362
363 let angle_scale = decoder.plane_angle_to_radians();
364 let start_angle = self
365 .trim_to_angle_3d(
366 trimmed.get(1),
367 prefer_cartesian,
368 ¢er,
369 &x_axis,
370 &y_axis,
371 radius,
372 radius2,
373 angle_scale,
374 decoder,
375 )
376 .unwrap_or(0.0);
377 let mut end_angle = self
378 .trim_to_angle_3d(
379 trimmed.get(2),
380 prefer_cartesian,
381 ¢er,
382 &x_axis,
383 &y_axis,
384 radius,
385 radius2,
386 angle_scale,
387 decoder,
388 )
389 .unwrap_or(2.0 * PI);
390
391 if sense && end_angle < start_angle {
394 end_angle += 2.0 * PI;
395 } else if !sense && end_angle > start_angle {
396 end_angle -= 2.0 * PI;
397 }
398
399 let arc_angle = (end_angle - start_angle).abs();
402 let by_angle = (arc_angle / std::f64::consts::FRAC_PI_2 * 8.0).ceil() as usize;
403 let by_chord = {
404 const CHORD_TOL_M: f64 = 5.0e-4; let r_eff = radius.abs().max(radius2.abs());
406 let radius_m = r_eff * decoder.length_unit_scale();
407 if radius_m > CHORD_TOL_M {
408 let rel = (CHORD_TOL_M / radius_m).clamp(1e-9, 0.5);
409 let max_step = 2.0 * (1.0 - rel).acos();
410 if max_step > 1e-9 {
411 (arc_angle / max_step).ceil() as usize
412 } else {
413 0
414 }
415 } else {
416 0
417 }
418 };
419 let num_segments = self
420 .quality()
421 .profile_arc_segments(by_angle.max(by_chord), 2)
422 .min(128);
423
424 let angle_range = if sense {
425 end_angle - start_angle
426 } else {
427 start_angle - end_angle
428 };
429
430 let mut points = Vec::with_capacity(num_segments + 1);
431 for i in 0..=num_segments {
432 let t = i as f64 / num_segments as f64;
433 let angle = if sense {
434 start_angle + t * angle_range
435 } else {
436 start_angle - t * angle_range.abs()
437 };
438 let p = center
439 + x_axis * (radius * angle.cos())
440 + y_axis * (radius2 * angle.sin());
441 points.push(p);
442 }
443
444 Ok(points)
445 }
446
447 #[allow(clippy::too_many_arguments)]
454 fn trim_to_angle_3d(
455 &self,
456 attr: Option<&AttributeValue>,
457 prefer_cartesian: bool,
458 center: &Point3<f64>,
459 x_axis: &Vector3<f64>,
460 y_axis: &Vector3<f64>,
461 radius: f64,
462 radius2: f64,
463 angle_scale: f64,
464 decoder: &mut EntityDecoder,
465 ) -> Option<f64> {
466 let list = attr?.as_list()?;
467 let mut param: Option<f64> = None;
468 let mut point: Option<Point3<f64>> = None;
469
470 for item in list {
471 if let Some(inner_list) = item.as_list() {
473 if let Some(type_name) = inner_list.first().and_then(|v| v.as_string()) {
474 if type_name == "IFCPARAMETERVALUE" {
475 param = inner_list.get(1).and_then(|v| v.as_float());
476 continue;
477 }
478 }
479 }
480 if item.as_entity_ref().is_some() {
482 if let Ok(Some(pt)) = decoder.resolve_ref(item) {
483 if pt.ifc_type == IfcType::IfcCartesianPoint {
484 if let Some(coords) = pt.get(0).and_then(|v| v.as_list()) {
485 point = Some(Point3::new(
486 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
487 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
488 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
489 ));
490 }
491 }
492 }
493 continue;
494 }
495 if let Some(f) = item.as_float() {
497 param = Some(f);
498 }
499 }
500
501 let use_point = (prefer_cartesian && point.is_some()) || param.is_none();
502 if use_point {
503 if let Some(p) = point {
504 let d = p - center;
505 let lx = d.dot(x_axis);
506 let ly = d.dot(y_axis);
507 return Some((ly / radius2).atan2(lx / radius));
508 }
509 }
510 param.map(|v| v * angle_scale)
511 }
512
513 pub(super) fn process_polyline_3d(
515 &self,
516 curve: &DecodedEntity,
517 decoder: &mut EntityDecoder,
518 ) -> Result<Vec<Point3<f64>>> {
519 let points_attr = curve
521 .get(0)
522 .ok_or_else(|| Error::geometry("Polyline missing Points".to_string()))?;
523
524 let points = decoder.resolve_ref_list(points_attr)?;
525 let mut result = Vec::with_capacity(points.len());
526
527 for point in points {
528 let coords_attr = point
530 .get(0)
531 .ok_or_else(|| Error::geometry("CartesianPoint missing Coordinates".to_string()))?;
532
533 let coords = coords_attr
534 .as_list()
535 .ok_or_else(|| Error::geometry("Coordinates is not a list".to_string()))?;
536
537 let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
538 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
539 let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
540
541 result.push(Point3::new(x, y, z));
542 }
543
544 Ok(result)
545 }
546
547 pub fn get_polyline_points_trimmed(
554 &self,
555 curve: &DecodedEntity,
556 decoder: &mut EntityDecoder,
557 start_param: Option<f64>,
558 end_param: Option<f64>,
559 ) -> Result<Vec<Point3<f64>>> {
560 let points = self.process_polyline_3d(curve, decoder)?;
561 if points.len() < 2 {
562 return Ok(points);
563 }
564 let max_param = (points.len() - 1) as f64;
565 let s = start_param.unwrap_or(0.0).clamp(0.0, max_param);
566 let e = end_param.unwrap_or(max_param).clamp(0.0, max_param);
567 if e <= s {
568 return Ok(Vec::new());
569 }
570 Ok(trim_polyline(&points, s / max_param, e / max_param))
572 }
573
574 pub(super) fn process_indexed_polycurve_3d(
585 &self,
586 curve: &DecodedEntity,
587 decoder: &mut EntityDecoder,
588 ) -> Result<Vec<Point3<f64>>> {
589 let points_attr = curve
590 .get(0)
591 .ok_or_else(|| Error::geometry("IndexedPolyCurve missing Points".to_string()))?;
592
593 let points_list = decoder
594 .resolve_ref(points_attr)?
595 .ok_or_else(|| Error::geometry("Failed to resolve Points list".to_string()))?;
596
597 let coord_list = points_list
598 .get(0)
599 .and_then(|a| a.as_list())
600 .ok_or_else(|| Error::geometry("CartesianPointList missing CoordList".to_string()))?;
601
602 let all_points: Vec<Point3<f64>> = coord_list
605 .iter()
606 .filter_map(|coord| {
607 coord.as_list().map(|coords| {
608 let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
609 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
610 let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
611 Point3::new(x, y, z)
612 })
613 })
614 .collect();
615
616 let segments_attr = curve.get(1);
617 if segments_attr.is_none() || segments_attr.map(|a| a.is_null()).unwrap_or(true) {
618 return Ok(all_points);
619 }
620
621 let segments = segments_attr
622 .unwrap()
623 .as_list()
624 .ok_or_else(|| Error::geometry("Expected segments list".to_string()))?;
625
626 let mut result: Vec<Point3<f64>> = Vec::new();
627 for segment in segments {
628 let (is_arc, indices) = if let Some(segment_list) = segment.as_list() {
631 if segment_list.len() >= 2 {
632 let type_name = segment_list
633 .first()
634 .and_then(|v| v.as_string())
635 .unwrap_or("");
636 let is_arc_type = type_name.to_uppercase().contains("ARC");
637 if let Some(AttributeValue::List(indices_list)) = segment_list.get(1) {
638 (is_arc_type, Some(indices_list.as_slice()))
639 } else {
640 (false, Some(segment_list))
641 }
642 } else {
643 (false, Some(segment_list))
644 }
645 } else {
646 (false, None)
647 };
648
649 let Some(indices) = indices else { continue };
650 let idx_values: Vec<usize> = indices
651 .iter()
652 .filter_map(|v| v.as_float())
653 .filter_map(|f| {
656 if !f.is_finite() || f < 1.0 || f.fract() != 0.0 {
657 return None;
658 }
659 (f as usize).checked_sub(1)
660 })
661 .collect();
662
663 if is_arc && idx_values.len() == 3 {
664 let p1 = all_points.get(idx_values[0]).copied();
665 let p2 = all_points.get(idx_values[1]).copied();
666 let p3 = all_points.get(idx_values[2]).copied();
667 if let (Some(start), Some(mid), Some(end)) = (p1, p2, p3) {
668 let chord = end - start;
671 let chord_len = chord.norm();
672 let mid_offset = mid - Point3::new(
673 0.5 * (start.x + end.x),
674 0.5 * (start.y + end.y),
675 0.5 * (start.z + end.z),
676 );
677 let mid_dev = mid_offset.norm();
678 let arc_estimate = if chord_len > 1e-10 {
681 4.0 * (2.0 * mid_dev / chord_len).atan()
682 } else {
683 0.5
684 };
685 let arc_base =
686 (arc_estimate / std::f64::consts::FRAC_PI_2 * 8.0).ceil() as usize;
687 let num_segments = scale_segments(arc_base, 4, 16, self.quality());
688 let arc_points = approximate_arc_3pt_3d(start, mid, end, num_segments);
689 for pt in arc_points {
690 if !same_point_3d(result.last(), &pt) {
691 result.push(pt);
692 }
693 }
694 }
695 } else {
696 for &idx in &idx_values {
698 if let Some(&pt) = all_points.get(idx) {
699 if !same_point_3d(result.last(), &pt) {
700 result.push(pt);
701 }
702 }
703 }
704 }
705 }
706
707 Ok(result)
708 }
709}