ifc_lite_geometry/processors/swept/
revolved.rs1use crate::{
6 extrusion::apply_transform, profiles::ProfileProcessor, scale_segments, Error, Mesh, Point3,
7 Result, TessellationQuality, Vector3,
8};
9use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
10use nalgebra::Matrix4;
11
12use super::super::helpers::parse_axis2_placement_3d;
13use super::super::tessellated::PolygonalFaceSetProcessor;
14use crate::router::GeometryProcessor;
15
16pub struct RevolvedAreaSolidProcessor {
19 profile_processor: ProfileProcessor,
20}
21
22impl RevolvedAreaSolidProcessor {
23 pub fn new(schema: IfcSchema) -> Self {
24 Self {
25 profile_processor: ProfileProcessor::new(schema),
26 }
27 }
28}
29
30impl GeometryProcessor for RevolvedAreaSolidProcessor {
31 fn process(
32 &self,
33 entity: &DecodedEntity,
34 decoder: &mut EntityDecoder,
35 _schema: &IfcSchema,
36 quality: TessellationQuality,
37 ) -> Result<Mesh> {
38 let profile_attr = entity
49 .get(0)
50 .ok_or_else(|| Error::geometry("RevolvedAreaSolid missing SweptArea".to_string()))?;
51 let profile = decoder
52 .resolve_ref(profile_attr)?
53 .ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
54
55 let position_transform = if let Some(pos_attr) = entity.get(1) {
58 if !pos_attr.is_null() {
59 if let Some(pos_entity) = decoder.resolve_ref(pos_attr)? {
60 parse_axis2_placement_3d(&pos_entity, decoder)?
61 } else {
62 Matrix4::identity()
63 }
64 } else {
65 Matrix4::identity()
66 }
67 } else {
68 Matrix4::identity()
69 };
70
71 let axis_attr = entity
72 .get(2)
73 .ok_or_else(|| Error::geometry("RevolvedAreaSolid missing Axis".to_string()))?;
74 let axis_placement = decoder
75 .resolve_ref(axis_attr)?
76 .ok_or_else(|| Error::geometry("Failed to resolve Axis".to_string()))?;
77
78 let angle = entity
79 .get_float(3)
80 .ok_or_else(|| Error::geometry("RevolvedAreaSolid missing Angle".to_string()))?
81 * decoder.plane_angle_to_radians();
82
83 let profile_2d = self.profile_processor.process(&profile, decoder, quality)?;
84 if profile_2d.outer.is_empty() {
85 return Ok(Mesh::new());
86 }
87
88 let axis_location = {
90 let loc_attr = axis_placement
91 .get(0)
92 .ok_or_else(|| Error::geometry("Axis1Placement missing Location".to_string()))?;
93 let loc = decoder
94 .resolve_ref(loc_attr)?
95 .ok_or_else(|| Error::geometry("Failed to resolve axis location".to_string()))?;
96 let coords = loc
97 .get(0)
98 .and_then(|v| v.as_list())
99 .ok_or_else(|| Error::geometry("Axis location missing coordinates".to_string()))?;
100 Point3::new(
101 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
102 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
103 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
104 )
105 };
106
107 let axis_direction = {
108 if let Some(dir_attr) = axis_placement.get(1) {
109 if !dir_attr.is_null() {
110 let dir = decoder.resolve_ref(dir_attr)?.ok_or_else(|| {
111 Error::geometry("Failed to resolve axis direction".to_string())
112 })?;
113 let coords = dir.get(0).and_then(|v| v.as_list()).ok_or_else(|| {
114 Error::geometry("Axis direction missing coordinates".to_string())
115 })?;
116 let raw = Vector3::new(
117 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
118 coords.get(1).and_then(|v| v.as_float()).unwrap_or(1.0),
119 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
120 );
121 if raw.norm() < 1e-12 {
122 Vector3::new(0.0, 1.0, 0.0)
123 } else {
124 raw.normalize()
125 }
126 } else {
127 Vector3::new(0.0, 1.0, 0.0)
128 }
129 } else {
130 Vector3::new(0.0, 1.0, 0.0)
131 }
132 };
133
134 let full_circle = angle.abs() >= std::f64::consts::PI * 1.99;
135 let segments = if full_circle {
139 scale_segments(24, 8, 96, quality)
140 } else {
141 let base = (angle.abs() / std::f64::consts::PI * 12.0).ceil() as usize;
142 scale_segments(base, 8, 4096, quality)
143 };
144
145 let profile_points = &profile_2d.outer;
146 let num_profile_points = profile_points.len();
147
148 let ring_count = if full_circle { segments } else { segments + 1 };
149 let mut positions = Vec::with_capacity(ring_count * num_profile_points * 3);
150 let mut indices = Vec::new();
151
152 for i in 0..ring_count {
154 let t = if full_circle {
155 std::f64::consts::TAU * i as f64 / segments as f64
156 } else {
157 angle * i as f64 / segments as f64
158 };
159
160 let cos_t = t.cos();
161 let sin_t = t.sin();
162 let k = axis_direction;
163
164 for p2d in profile_points {
165 let p_local = Point3::new(p2d.x, p2d.y, 0.0);
167
168 let v = p_local - axis_location;
171 let v_par_len = v.dot(&k);
172 let v_par = k * v_par_len;
173 let v_perp = v - v_par;
174 let v_perp_rot = v_perp * cos_t + k.cross(&v_perp) * sin_t;
175
176 let pos_local = axis_location + v_par + v_perp_rot;
177
178 positions.push(pos_local.x as f32);
179 positions.push(pos_local.y as f32);
180 positions.push(pos_local.z as f32);
181 }
182 }
183
184 let segment_quads = segments;
187 for i in 0..segment_quads {
188 let ring_a = i;
189 let ring_b = (i + 1) % ring_count;
190 for j in 0..num_profile_points {
191 let j_next = (j + 1) % num_profile_points;
192 let a = (ring_a * num_profile_points + j) as u32;
193 let b = (ring_b * num_profile_points + j) as u32;
194 let c = (ring_b * num_profile_points + j_next) as u32;
195 let d = (ring_a * num_profile_points + j_next) as u32;
196 indices.push(a);
197 indices.push(b);
198 indices.push(c);
199 indices.push(a);
200 indices.push(c);
201 indices.push(d);
202 }
203 }
204
205 if !full_circle && num_profile_points >= 3 {
223 let profile_flat: Vec<f64> = profile_points
224 .iter()
225 .flat_map(|p| [p.x, p.y])
226 .collect();
227 let cap_indices = crate::triangulation::safe_earcut(&profile_flat, &[], 2)
228 .map_err(|e| Error::geometry(format!(
229 "Revolved profile cap triangulation failed: {e}"
230 )))?;
231
232 for (ring_idx, flip) in [(0usize, true), (segments, false)] {
233 let base = (ring_idx * num_profile_points) as u32;
234 for tri in cap_indices.chunks_exact(3) {
235 let a = base + tri[0] as u32;
236 let b = base + tri[1] as u32;
237 let c = base + tri[2] as u32;
238 if flip {
239 indices.push(a);
240 indices.push(c);
241 indices.push(b);
242 } else {
243 indices.push(a);
244 indices.push(b);
245 indices.push(c);
246 }
247 }
248 }
249 }
250
251 let mut mesh = Mesh {
252 positions,
253 normals: Vec::new(),
254 indices,
255 rtc_applied: false,
256 origin: [0.0; 3], instance_meta: None, local_bounds: None, local_to_world: None };
257
258 apply_transform(&mut mesh, &position_transform);
260
261 let flat =
269 PolygonalFaceSetProcessor::build_flat_shaded_mesh(&mesh.positions, &mesh.indices);
270 mesh.positions = flat.positions;
271 mesh.normals = flat.normals;
272 mesh.indices = flat.indices;
273
274 Ok(mesh)
275 }
276
277 fn supported_types(&self) -> Vec<IfcType> {
278 vec![IfcType::IfcRevolvedAreaSolid]
279 }
280}
281
282impl Default for RevolvedAreaSolidProcessor {
283 fn default() -> Self {
284 Self::new(IfcSchema::new())
285 }
286}