1use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub enum Representation {
28 ExtrudedAreaSolid {
33 profile: Vec<[f64; 2]>,
36 direction: [f64; 3],
38 depth: f64,
42 },
43 TriangulatedFaceSet {
45 vertices: Vec<[f64; 3]>,
46 faces: Vec<[u32; 3]>,
47 },
48}
49
50impl Representation {
51 pub fn extrusion(profile: Vec<[f64; 2]>, direction: [f64; 3], depth: f64) -> Self {
53 if depth < 0.0 {
54 Self::ExtrudedAreaSolid {
55 profile,
56 direction: [-direction[0], -direction[1], -direction[2]],
57 depth: -depth,
58 }
59 } else {
60 Self::ExtrudedAreaSolid {
61 profile,
62 direction,
63 depth,
64 }
65 }
66 }
67
68 pub fn is_native_parametric(&self) -> bool {
70 matches!(self, Self::ExtrudedAreaSolid { .. })
71 }
72
73 pub fn ifc_representation_type(&self) -> &'static str {
75 match self {
76 Self::ExtrudedAreaSolid { .. } => "SweptSolid",
77 Self::TriangulatedFaceSet { .. } => "Tessellation",
78 }
79 }
80
81 pub fn ifc_item(&self) -> &'static str {
83 match self {
84 Self::ExtrudedAreaSolid { .. } => "IfcExtrudedAreaSolid",
85 Self::TriangulatedFaceSet { .. } => "IfcTriangulatedFaceSet",
86 }
87 }
88
89 pub fn is_valid(&self) -> bool {
91 match self {
92 Self::ExtrudedAreaSolid {
93 profile,
94 direction,
95 depth,
96 } => {
97 profile.len() >= 3
98 && profile.iter().flatten().all(|v| v.is_finite())
99 && direction.iter().all(|v| v.is_finite())
100 && direction.iter().any(|v| *v != 0.0)
101 && depth.is_finite()
102 && *depth > 0.0
103 }
104 Self::TriangulatedFaceSet { vertices, faces } => {
105 !vertices.is_empty()
106 && !faces.is_empty()
107 && vertices.iter().flatten().all(|v| v.is_finite())
108 && faces
109 .iter()
110 .flatten()
111 .all(|i| (*i as usize) < vertices.len())
112 }
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 fn square() -> Vec<[f64; 2]> {
122 vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]
123 }
124
125 #[test]
126 fn a_negative_depth_is_folded_into_the_direction() {
127 let r = Representation::extrusion(square(), [0.0, 0.0, 1.0], -3.0);
130 let Representation::ExtrudedAreaSolid {
131 direction, depth, ..
132 } = &r
133 else {
134 panic!("expected a swept solid");
135 };
136 assert_eq!(*direction, [0.0, 0.0, -1.0]);
137 assert_eq!(*depth, 3.0);
138 assert!(r.is_valid());
139 }
140
141 #[test]
142 fn a_positive_depth_is_left_alone() {
143 let r = Representation::extrusion(square(), [0.0, 0.0, 1.0], 3.0);
144 assert_eq!(
145 r,
146 Representation::ExtrudedAreaSolid {
147 profile: square(),
148 direction: [0.0, 0.0, 1.0],
149 depth: 3.0,
150 }
151 );
152 }
153
154 #[test]
155 fn parametric_and_tessellated_map_to_different_ifc() {
156 let swept = Representation::extrusion(square(), [0.0, 0.0, 1.0], 1.0);
157 assert!(swept.is_native_parametric());
158 assert_eq!(swept.ifc_representation_type(), "SweptSolid");
159 assert_eq!(swept.ifc_item(), "IfcExtrudedAreaSolid");
160
161 let mesh = Representation::TriangulatedFaceSet {
162 vertices: vec![[0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
163 faces: vec![[0, 1, 2]],
164 };
165 assert!(!mesh.is_native_parametric());
166 assert_eq!(mesh.ifc_representation_type(), "Tessellation");
167 assert!(mesh.is_valid());
168 }
169
170 #[test]
171 fn malformed_geometry_is_caught_before_it_reaches_a_file() {
172 assert!(
173 !Representation::extrusion(vec![[0.0, 0.0], [1.0, 0.0]], [0.0, 0.0, 1.0], 1.0)
174 .is_valid()
175 );
176 assert!(!Representation::extrusion(square(), [0.0, 0.0, 0.0], 1.0).is_valid());
177 assert!(!Representation::extrusion(square(), [0.0, 0.0, 1.0], 0.0).is_valid());
178 assert!(!Representation::extrusion(square(), [0.0, 0.0, 1.0], f64::NAN).is_valid());
179 assert!(!Representation::ExtrudedAreaSolid {
180 profile: vec![[0.0, 0.0], [1.0, 0.0], [f64::INFINITY, 1.0]],
181 direction: [0.0, 0.0, 1.0],
182 depth: 1.0,
183 }
184 .is_valid());
185 }
186
187 #[test]
188 fn an_out_of_range_face_index_is_invalid() {
189 assert!(!Representation::TriangulatedFaceSet {
191 vertices: vec![[0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
192 faces: vec![[0, 1, 7]],
193 }
194 .is_valid());
195 assert!(!Representation::TriangulatedFaceSet {
196 vertices: Vec::new(),
197 faces: vec![[0, 1, 2]],
198 }
199 .is_valid());
200 }
201}