ifc_lite_geometry/router/transforms/
linear.rs1use super::super::GeometryRouter;
8use super::walk::PlacementWalk;
9use crate::alignment::AlignmentCurve;
10use crate::alignment_arc_length::ArcLengthMap;
11use crate::profiles::ProfileProcessor;
12use crate::{Point3, Result, TessellationQuality, Vector3};
13use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
14use nalgebra::Matrix4;
15
16impl GeometryRouter {
17 pub(super) fn resolve_linear_placement_with_depth(
28 &self,
29 placement: &DecodedEntity,
30 decoder: &mut EntityDecoder,
31 depth: usize,
32 ) -> Result<PlacementWalk> {
33 let parent = match placement.get(0) {
37 Some(parent_attr) if !parent_attr.is_null() => {
38 match decoder.resolve_ref(parent_attr)? {
39 Some(p) => self.get_placement_transform_with_depth(&p, decoder, depth + 1)?,
40 None => PlacementWalk::complete(Matrix4::identity()),
41 }
42 }
43 _ => PlacementWalk::complete(Matrix4::identity()),
44 };
45
46 let local = self
51 .linear_placement_local(placement, decoder)
52 .unwrap_or_else(Matrix4::identity);
53
54 Ok(PlacementWalk { transform: parent.transform * local, truncated: parent.truncated })
55 }
56
57 fn linear_placement_local(
60 &self,
61 placement: &DecodedEntity,
62 decoder: &mut EntityDecoder,
63 ) -> Option<Matrix4<f64>> {
64 self.try_resolve_cartesian_position(placement, decoder)
65 .or_else(|| self.try_resolve_axis2_placement_linear(placement, decoder))
66 }
67
68 pub fn resolve_linear_placement_local_strict(
73 &self,
74 placement: &DecodedEntity,
75 decoder: &mut EntityDecoder,
76 ) -> Result<[f64; 16]> {
77 if placement.ifc_type != IfcType::IfcLinearPlacement {
78 return Err(crate::Error::geometry(format!(
79 "#{} is not an IfcLinearPlacement",
80 placement.id
81 )));
82 }
83 let local = self.linear_placement_local(placement, decoder).ok_or_else(|| {
84 crate::Error::geometry(format!(
85 "IfcLinearPlacement #{} cannot be resolved",
86 placement.id
87 ))
88 })?;
89 let mut result = [0.0; 16];
90 result.copy_from_slice(local.as_slice());
91 Ok(result)
92 }
93
94 fn try_resolve_axis2_placement_linear(
98 &self,
99 placement: &DecodedEntity,
100 decoder: &mut EntityDecoder,
101 ) -> Option<Matrix4<f64>> {
102 let rel_attr = placement.get(1)?;
103 if rel_attr.is_null() {
104 return None;
105 }
106 let rel = decoder.resolve_ref(rel_attr).ok().flatten()?;
107 if rel.ifc_type != IfcType::IfcAxis2PlacementLinear {
108 return None;
109 }
110
111 let location_attr = rel.get(0)?;
115 if location_attr.is_null() {
116 return None;
117 }
118 let location = decoder.resolve_ref(location_attr).ok().flatten()?;
119 if location.ifc_type != IfcType::IfcPointByDistanceExpression {
120 return None;
121 }
122
123 let distance_along = location.get_float(0)?;
129 let offset_lateral = location.get_float(1).unwrap_or(0.0);
130 let offset_vertical = location.get_float(2).unwrap_or(0.0);
131 let offset_longitudinal = location.get_float(3).unwrap_or(0.0);
132
133 let basis_attr = location.get(4)?;
134 if basis_attr.is_null() {
135 return None;
136 }
137 let basis_curve = decoder.resolve_ref(basis_attr).ok().flatten()?;
138
139 let (origin, tangent) = if basis_curve.ifc_type == IfcType::IfcGradientCurve {
140 let alignment = AlignmentCurve::parse(&basis_curve, decoder).ok().flatten()?;
143 let station = ArcLengthMap::new(&alignment).horizontal_station(distance_along);
144 let station = station.max(0.0).min(alignment.horizontal_length());
145 let frame = alignment.evaluate(station);
146 (frame.origin, frame.tangent)
147 } else {
148 let samples = ProfileProcessor::new(IfcSchema::new())
150 .get_curve_points(&basis_curve, decoder, TessellationQuality::Medium)
151 .ok()
152 .filter(|pts| pts.len() >= 2)?;
153 sample_polyline_at_distance(&samples, distance_along)?
154 };
155
156 let world_up = Vector3::new(0.0, 0.0, 1.0);
161 let tangent_horiz_norm =
162 (tangent - world_up * tangent.dot(&world_up)).norm();
163 let (x_axis, y_axis, z_axis) = if tangent_horiz_norm > 1e-9 {
164 let x = tangent.normalize();
165 let y = world_up.cross(&x).normalize();
166 let z = x.cross(&y).normalize();
167 (x, y, z)
168 } else {
169 (
170 Vector3::new(1.0, 0.0, 0.0),
171 Vector3::new(0.0, 1.0, 0.0),
172 Vector3::new(0.0, 0.0, 1.0),
173 )
174 };
175
176 let position = origin.coords
177 + x_axis * offset_longitudinal
178 + y_axis * offset_lateral
179 + z_axis * offset_vertical;
180
181 let mut m = Matrix4::<f64>::identity();
182 m.fixed_view_mut::<3, 1>(0, 0).copy_from(&x_axis);
183 m.fixed_view_mut::<3, 1>(0, 1).copy_from(&y_axis);
184 m.fixed_view_mut::<3, 1>(0, 2).copy_from(&z_axis);
185 m[(0, 3)] = position.x;
186 m[(1, 3)] = position.y;
187 m[(2, 3)] = position.z;
188 Some(m)
189 }
190
191 fn try_resolve_cartesian_position(
196 &self,
197 placement: &DecodedEntity,
198 decoder: &mut EntityDecoder,
199 ) -> Option<Matrix4<f64>> {
200 let cart_attr = placement.get(2)?;
201 if cart_attr.is_null() {
202 return None;
203 }
204 let cart = decoder.resolve_ref(cart_attr).ok().flatten()?;
205 if cart.ifc_type != IfcType::IfcAxis2Placement3D {
206 return None;
207 }
208 self.parse_axis2_placement_3d(&cart, decoder).ok()
209 }
210}
211
212fn sample_polyline_at_distance(
225 samples: &[Point3<f64>],
226 distance: f64,
227) -> Option<(Point3<f64>, Vector3<f64>)> {
228 if samples.len() < 2 {
229 return None;
230 }
231
232 if distance <= 0.0 {
233 let tangent = (samples[1] - samples[0])
234 .try_normalize(1e-12)
235 .unwrap_or_else(|| Vector3::new(1.0, 0.0, 0.0));
236 return Some((samples[0], tangent));
237 }
238
239 let mut acc = 0.0;
240 for window in samples.windows(2) {
241 let a = window[0];
242 let b = window[1];
243 let seg = b - a;
244 let len = seg.norm();
245 if len < 1e-12 {
246 continue;
247 }
248 if acc + len >= distance {
249 let t = ((distance - acc) / len).clamp(0.0, 1.0);
250 let position = a + seg * t;
251 let tangent = (seg / len)
252 .try_normalize(1e-12)
253 .unwrap_or_else(|| Vector3::new(1.0, 0.0, 0.0));
254 return Some((position, tangent));
255 }
256 acc += len;
257 }
258
259 let last = samples[samples.len() - 1];
261 let prev = samples[samples.len() - 2];
262 let tangent = (last - prev)
263 .try_normalize(1e-12)
264 .unwrap_or_else(|| Vector3::new(1.0, 0.0, 0.0));
265 Some((last, tangent))
266}
267
268#[cfg(test)]
269#[path = "linear_tests.rs"]
270mod tests;