1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum FunctionStatus {
9 NativePrimitive,
11 Scaffolded,
13 Implemented,
17 NotApplicable,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub struct FunctionSupport {
28 pub name: &'static str,
30 pub owner: &'static str,
32 pub status: FunctionStatus,
34}
35
36const NATIVE: FunctionStatus = FunctionStatus::NativePrimitive;
37#[allow(dead_code)]
42const SCAFFOLDED: FunctionStatus = FunctionStatus::Scaffolded;
43const NOT_APPLICABLE: FunctionStatus = FunctionStatus::NotApplicable;
44const IMPLEMENTED: FunctionStatus = FunctionStatus::Implemented;
45
46pub const FUNCTIONS: &[FunctionSupport] = &[
48 FunctionSupport {
49 name: "IfcAssociatedSurface",
50 owner: "surface::basis",
51 status: IMPLEMENTED,
52 },
53 FunctionSupport {
54 name: "IfcBaseAxis",
55 owner: "resource::axes",
56 status: IMPLEMENTED,
57 },
58 FunctionSupport {
59 name: "IfcBuild2Axes",
60 owner: "resource::functions",
61 status: IMPLEMENTED,
62 },
63 FunctionSupport {
64 name: "IfcBuildAxes",
65 owner: "transform",
66 status: IMPLEMENTED,
67 },
68 FunctionSupport {
69 name: "IfcConsecutiveSegments",
70 owner: "rules::express",
71 status: IMPLEMENTED,
72 },
73 FunctionSupport {
74 name: "IfcConstraintsParamBSpline",
75 owner: "rules::express",
76 status: IMPLEMENTED,
77 },
78 FunctionSupport {
79 name: "IfcCrossProduct",
80 owner: "axiolid_core::Vec3::cross",
81 status: NATIVE,
82 },
83 FunctionSupport {
84 name: "IfcCurveDim",
85 owner: "rules::dimension",
86 status: IMPLEMENTED,
87 },
88 FunctionSupport {
89 name: "IfcCurveWeightsPositive",
90 owner: "rules::bspline",
91 status: IMPLEMENTED,
92 },
93 FunctionSupport {
94 name: "IfcDotProduct",
95 owner: "axiolid_core::Vec3::dot",
96 status: NATIVE,
97 },
98 FunctionSupport {
99 name: "IfcFirstProjAxis",
100 owner: "transform",
101 status: IMPLEMENTED,
102 },
103 FunctionSupport {
104 name: "IfcGetBasisSurface",
105 owner: "surface::basis",
106 status: IMPLEMENTED,
107 },
108 FunctionSupport {
109 name: "IfcListToArray",
110 owner: "curve::bspline",
111 status: NOT_APPLICABLE,
112 },
113 FunctionSupport {
114 name: "IfcMakeArrayOfArray",
115 owner: "surface::bspline",
116 status: NOT_APPLICABLE,
117 },
118 FunctionSupport {
119 name: "IfcNormalise",
120 owner: "axiolid_core::Vec3::normalize",
121 status: NATIVE,
122 },
123 FunctionSupport {
124 name: "IfcOrthogonalComplement",
125 owner: "resource::functions",
126 status: IMPLEMENTED,
127 },
128 FunctionSupport {
129 name: "IfcSameAxis2Placement",
130 owner: "resource::functions",
131 status: IMPLEMENTED,
132 },
133 FunctionSupport {
134 name: "IfcSameCartesianPoint",
135 owner: "resource::functions",
136 status: IMPLEMENTED,
137 },
138 FunctionSupport {
139 name: "IfcSameDirection",
140 owner: "resource::functions",
141 status: IMPLEMENTED,
142 },
143 FunctionSupport {
144 name: "IfcSameValue",
145 owner: "resource::functions",
146 status: IMPLEMENTED,
147 },
148 FunctionSupport {
149 name: "IfcScalarTimesVector",
150 owner: "axiolid_core::Vec3::mul",
151 status: NATIVE,
152 },
153 FunctionSupport {
154 name: "IfcSecondProjAxis",
155 owner: "transform",
156 status: IMPLEMENTED,
157 },
158 FunctionSupport {
159 name: "IfcSurfaceWeightsPositive",
160 owner: "rules::bspline",
161 status: IMPLEMENTED,
162 },
163 FunctionSupport {
164 name: "IfcVectorDifference",
165 owner: "axiolid_core::Vec3::sub",
166 status: NATIVE,
167 },
168 FunctionSupport {
169 name: "IfcVectorSum",
170 owner: "axiolid_core::Vec3::add",
171 status: NATIVE,
172 },
173 FunctionSupport {
174 name: "IfcPointListDim",
175 owner: "resource::functions",
176 status: IMPLEMENTED,
177 },
178 FunctionSupport {
179 name: "IfcTaperedSweptAreaProfiles",
180 owner: "rules::surface",
181 status: IMPLEMENTED,
182 },
183 FunctionSupport {
184 name: "IfcCorrectLocalPlacement",
185 owner: "rules::placement",
186 status: IMPLEMENTED,
187 },
188];
189
190const DEFAULT_EPSILON: f64 = 0.000_001;
192
193#[must_use]
202pub fn same_value(a: f64, b: f64, epsilon: Option<f64>) -> bool {
203 let eps = epsilon.unwrap_or(DEFAULT_EPSILON);
204 a + eps > b && a < b + eps
205}
206
207#[must_use]
214pub fn same_components(a: &[f64], b: &[f64], epsilon: Option<f64>) -> bool {
215 let at = |v: &[f64], i: usize| v.get(i).copied().unwrap_or(0.0);
216 (0..3).all(|i| same_value(at(a, i), at(b, i), epsilon))
217}
218
219#[must_use]
224pub fn point_list_dim(type_name: &str) -> Option<usize> {
225 match type_name.to_ascii_uppercase().as_str() {
226 "IFCCARTESIANPOINTLIST2D" => Some(2),
227 "IFCCARTESIANPOINTLIST3D" => Some(3),
228 _ => None,
229 }
230}
231
232#[must_use]
236pub fn orthogonal_complement(v: &[f64]) -> Option<[f64; 2]> {
237 match v {
238 [x, y] => Some([-*y, *x]),
239 _ => None,
240 }
241}
242
243#[must_use]
248pub fn build_2_axes(ref_direction: Option<[f64; 2]>) -> ([f64; 2], [f64; 2]) {
249 let x = ref_direction
250 .and_then(|d| {
251 let len = d[0].hypot(d[1]);
252 (len.is_finite() && len > 0.0).then(|| [d[0] / len, d[1] / len])
253 })
254 .unwrap_or([1.0, 0.0]);
255 (x, [-x[1], x[0]])
256}
257
258#[must_use]
274pub fn same_axis2_placement(
275 a_axes: (&[f64], &[f64]),
276 a_location: &[f64],
277 b_axes: (&[f64], &[f64]),
278 b_location: &[f64],
279 epsilon: Option<f64>,
280) -> bool {
281 same_components(a_axes.0, b_axes.0, epsilon)
282 && same_components(a_axes.1, b_axes.1, epsilon)
283 && same_components(a_location, b_location, epsilon)
284}
285
286pub fn function_support(name: &str) -> Option<&'static FunctionSupport> {
288 FUNCTIONS
289 .iter()
290 .find(|item| item.name.eq_ignore_ascii_case(name))
291}
292
293#[cfg(test)]
294mod function_tests {
295 use super::*;
296
297 #[test]
301 fn same_value_boundary_is_exclusive() {
302 assert!(same_value(1.0, 1.0 + 0.5e-6, None));
303 assert!(!same_value(1.0, 1.0 + 1e-6, None), "exactly eps apart");
304 assert!(!same_value(1.0, 1.0 + 2e-6, None));
305 assert!(!same_value(1.0 + 1e-6, 1.0, None));
307 }
308
309 #[test]
311 fn same_value_default_epsilon_is_not_exact_equality() {
312 assert!(same_value(2.0, 2.0 + 1e-9, None));
313 assert!(!same_value(2.0, 2.0 + 1e-9, Some(1e-12)));
314 }
315
316 #[test]
319 fn missing_third_component_is_zero() {
320 assert!(same_components(&[1.0, 2.0], &[1.0, 2.0, 0.0], None));
321 assert!(!same_components(&[1.0, 2.0], &[1.0, 2.0, 5.0], None));
322 }
323
324 #[test]
325 fn point_list_dim_refuses_unknown_types() {
326 assert_eq!(point_list_dim("IfcCartesianPointList2D"), Some(2));
327 assert_eq!(point_list_dim("IFCCARTESIANPOINTLIST3D"), Some(3));
328 assert_eq!(point_list_dim("IFCCARTESIANPOINT"), None);
329 }
330
331 #[test]
334 fn orthogonal_complement_is_2d_only() {
335 assert_eq!(orthogonal_complement(&[1.0, 0.0]), Some([0.0, 1.0]));
336 assert_eq!(orthogonal_complement(&[0.0, 1.0]), Some([-1.0, 0.0]));
337 assert_eq!(orthogonal_complement(&[1.0, 0.0, 0.0]), None);
338 }
339
340 #[test]
342 fn build_2_axes_defaults_and_normalises() {
343 assert_eq!(build_2_axes(None), ([1.0, 0.0], [0.0, 1.0]));
344 assert_eq!(build_2_axes(Some([0.0, 0.0])), ([1.0, 0.0], [0.0, 1.0]));
345 let (x, y) = build_2_axes(Some([5.0, 0.0]));
346 assert!((x[0] - 1.0).abs() < 1e-12, "normalised: {x:?}");
347 assert!((y[1] - 1.0).abs() < 1e-12, "perpendicular: {y:?}");
348 }
349
350 #[test]
354 fn same_axis2_placement_actually_compares_locations() {
355 let x = [1.0, 0.0, 0.0];
356 let y = [0.0, 1.0, 0.0];
357 assert!(same_axis2_placement(
358 (&x, &y),
359 &[0.0, 0.0, 0.0],
360 (&x, &y),
361 &[0.0, 0.0, 0.0],
362 None
363 ));
364 assert!(!same_axis2_placement(
367 (&x, &y),
368 &[0.0, 0.0, 0.0],
369 (&x, &y),
370 &[5.0, 0.0, 0.0],
371 None
372 ));
373 assert!(!same_axis2_placement(
375 (&x, &y),
376 &[0.0, 0.0, 0.0],
377 (&y, &x),
378 &[0.0, 0.0, 0.0],
379 None
380 ));
381 }
382}