ifc_lite_geometry/profiles/
directrix.rs1use super::outline::trim_polyline;
10use super::ProfileProcessor;
11use crate::tessellation::TessellationQuality;
12use crate::{Error, Point3, Result, Vector3};
13use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcType};
14use std::f64::consts::TAU;
15
16const PARAM_EPS: f64 = 1e-9;
18
19impl ProfileProcessor {
20 pub fn get_directrix_points(
32 &self,
33 directrix: &DecodedEntity,
34 decoder: &mut EntityDecoder,
35 start_param: Option<f64>,
36 end_param: Option<f64>,
37 quality: TessellationQuality,
38 ) -> Result<Vec<Point3<f64>>> {
39 self.set_tessellation_quality(quality);
40 if start_param.is_none() && end_param.is_none() {
41 return self.get_curve_points(directrix, decoder, quality);
42 }
43 if directrix.ifc_type.is_subtype_of(IfcType::IfcCompositeCurve) {
44 return self
45 .get_composite_curve_points_trimmed(directrix, decoder, start_param, end_param);
46 }
47 match directrix.ifc_type {
48 IfcType::IfcPolyline => {
49 self.get_polyline_points_trimmed(directrix, decoder, start_param, end_param)
50 }
51 IfcType::IfcLine => self.get_line_points_3d(
54 directrix,
55 decoder,
56 start_param.unwrap_or(0.0),
57 end_param.unwrap_or(1.0),
58 ),
59 IfcType::IfcCircle | IfcType::IfcEllipse => {
60 self.get_conic_points_trimmed(directrix, decoder, start_param, end_param)
61 }
62 _ => self.get_curve_points(directrix, decoder, quality),
63 }
64 }
65
66 fn get_conic_points_trimmed(
72 &self,
73 conic: &DecodedEntity,
74 decoder: &mut EntityDecoder,
75 start_param: Option<f64>,
76 end_param: Option<f64>,
77 ) -> Result<Vec<Point3<f64>>> {
78 let scale = decoder.plane_angle_to_radians();
79 let start = start_param.map_or(0.0, |s| s * scale);
80 let mut end = end_param.map_or(TAU, |e| e * scale);
81 if end < start {
82 end += TAU;
83 }
84 end = end.min(start + TAU);
85 if !(start.is_finite() && end.is_finite()) || end <= start {
86 return Ok(Vec::new());
87 }
88 if conic.ifc_type == IfcType::IfcCircle
91 && start.abs() <= PARAM_EPS
92 && (end - TAU).abs() <= PARAM_EPS
93 {
94 return self.process_circle_3d(conic, decoder);
95 }
96 let radius = conic
97 .get_float(1)
98 .ok_or_else(|| Error::geometry("Conic missing radius".to_string()))?;
99 let radius2 = if conic.ifc_type == IfcType::IfcEllipse {
100 conic.get_float(2).unwrap_or(radius)
101 } else {
102 radius
103 };
104 let frame = self.read_conic_placement_3d(conic, decoder)?;
105 Ok(self.sample_conic_arc_3d(frame, (radius, radius2), start, end, decoder))
106 }
107
108 pub fn get_composite_curve_points_trimmed(
124 &self,
125 curve: &DecodedEntity,
126 decoder: &mut EntityDecoder,
127 start_param: Option<f64>,
128 end_param: Option<f64>,
129 ) -> Result<Vec<Point3<f64>>> {
130 let segments_attr = curve
131 .get(0)
132 .ok_or_else(|| Error::geometry("CompositeCurve missing Segments".to_string()))?;
133 let segments = decoder.resolve_ref_list(segments_attr)?;
134 if segments.is_empty() {
135 return Ok(Vec::new());
136 }
137
138 let mut parts = Vec::with_capacity(segments.len());
139 let mut spans_known = true;
140 for segment in segments {
141 let parent_curve_attr = segment.get(2).ok_or_else(|| {
142 Error::geometry("CompositeCurveSegment missing ParentCurve".to_string())
143 })?;
144 let parent_curve = decoder
145 .resolve_ref(parent_curve_attr)?
146 .ok_or_else(|| Error::geometry("Failed to resolve ParentCurve".to_string()))?;
147 let same_sense = segment
148 .get(1)
149 .and_then(|v| match v {
150 ifc_lite_core::AttributeValue::Enum(e) => Some(e.as_str()),
151 _ => None,
152 })
153 .map(|e| e == "T" || e == "TRUE")
154 .unwrap_or(true);
155 let span = crate::analytic::composite_parent_parameter_span(&parent_curve, decoder)
159 .ok()
160 .flatten();
161 spans_known &= span.is_some();
162 parts.push((parent_curve, same_sense, span.unwrap_or(0.0)));
163 }
164
165 let (start, end) = if spans_known {
166 let total: f64 = parts.iter().map(|(_, _, span)| span).sum();
167 (
168 start_param.unwrap_or(0.0).max(0.0),
169 end_param.unwrap_or(total).min(total),
170 )
171 } else {
172 for part in &mut parts {
173 part.2 = 1.0;
174 }
175 (0.0, parts.len() as f64)
176 };
177 if end <= start {
178 return Ok(Vec::new());
179 }
180
181 let mut result: Vec<Point3<f64>> = Vec::new();
182 let mut seg_start = 0.0;
183 for (parent_curve, same_sense, span) in parts {
184 let seg_end = seg_start + span;
185 let window = (start - seg_start, end - seg_start);
186 seg_start = seg_end;
187 if span <= 0.0 || window.1 <= 0.0 || window.0 >= span {
189 continue;
190 }
191
192 let mut seg_points = self.get_curve_points_with_depth(&parent_curve, decoder, 1)?;
193 if !same_sense {
194 seg_points.reverse();
195 }
196 if seg_points.len() < 2 {
197 continue;
198 }
199
200 let snap = |t: f64| {
205 if t <= PARAM_EPS {
206 0.0
207 } else if t >= 1.0 - PARAM_EPS {
208 1.0
209 } else {
210 t
211 }
212 };
213 let local_start = snap(window.0 / span);
214 let local_end = snap(window.1 / span);
215 if local_end <= local_start {
216 continue;
217 }
218
219 let trimmed = if local_start == 0.0 && local_end == 1.0 {
220 seg_points
221 } else {
222 trim_polyline(&seg_points, local_start, local_end)
223 };
224
225 const JUNCTION_EPS: f64 = 1e-6;
233 let mut iter = trimmed.into_iter();
234 if let Some(first) = iter.next() {
235 let coincident = result.last().is_some_and(|last| {
236 (first.x - last.x).abs() < JUNCTION_EPS
237 && (first.y - last.y).abs() < JUNCTION_EPS
238 && (first.z - last.z).abs() < JUNCTION_EPS
239 });
240 if !coincident {
241 result.push(first);
242 }
243 result.extend(iter);
244 }
245 }
246
247 Ok(result)
248 }
249
250 pub(super) fn sample_conic_arc_3d(
255 &self,
256 (center, x_axis, y_axis): (Point3<f64>, Vector3<f64>, Vector3<f64>),
257 (radius, radius2): (f64, f64),
258 start_angle: f64,
259 end_angle: f64,
260 decoder: &mut EntityDecoder,
261 ) -> Vec<Point3<f64>> {
262 let arc_angle = (end_angle - start_angle).abs();
263 let by_angle = (arc_angle / std::f64::consts::FRAC_PI_2 * 8.0).ceil() as usize;
264 let by_chord = {
265 const CHORD_TOL_M: f64 = 5.0e-4; let r_eff = radius.abs().max(radius2.abs());
267 let radius_m = r_eff * decoder.length_unit_scale();
268 if radius_m > CHORD_TOL_M {
269 let rel = (CHORD_TOL_M / radius_m).clamp(1e-9, 0.5);
270 let max_step = 2.0 * (1.0 - rel).acos();
271 if max_step > 1e-9 {
272 (arc_angle / max_step).ceil() as usize
273 } else {
274 0
275 }
276 } else {
277 0
278 }
279 };
280 let num_segments = self
281 .quality()
282 .profile_arc_segments(by_angle.max(by_chord), 2)
283 .min(128);
284
285 let mut points = Vec::with_capacity(num_segments + 1);
286 for i in 0..=num_segments {
287 let t = i as f64 / num_segments as f64;
288 let angle = start_angle + t * (end_angle - start_angle);
289 let p = center
290 + x_axis * (radius * angle.cos())
291 + y_axis * (radius2 * angle.sin());
292 points.push(p);
293 }
294 points
295 }
296}