geop_ops_extrude_revolve/
cylinder.rs1use crate::{
6 common::{Profile, polygon, polyline},
7 extrude::{ExtrudeNames, extrude},
8 revolve::revolve_at_oriented,
9};
10use geop_core_math::{
11 geop_error::GeopResult,
12 primitives::CoordinateSystem,
13 scalars::Scalar,
14 vector::{Vector2, Vector3},
15};
16use geop_core_part::{Namer, Part};
17use geop_core_topology::SolidId;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Axis {
22 X,
23 Y,
24 Z,
25}
26
27pub fn revolved_cylinder<S: Scalar>(
35 part: &mut Part<S>,
36 name: &str,
37 base_center: Vector3<S>,
38 radius: S,
39 height: S,
40) -> GeopResult<SolidId> {
41 revolved_cylinder_along_axis(part, name, base_center, radius, height, Axis::Z)
42}
43
44pub fn revolved_cylinder_along_axis<S: Scalar>(
63 part: &mut Part<S>,
64 name: &str,
65 base_center: Vector3<S>,
66 radius: S,
67 height: S,
68 axis: Axis,
69) -> GeopResult<SolidId> {
70 let (zero, one) = (S::ZERO, S::ONE);
71 let x = Vector3::from_array([one, zero, zero]);
72 let y = Vector3::from_array([zero, one, zero]);
73 let z = Vector3::from_array([zero, zero, one]);
74 let (u, v, w) = match axis {
75 Axis::X => (y, z, x),
76 Axis::Y => (z, x, y),
77 Axis::Z => (x, y, z),
78 };
79 let coordinate_system = CoordinateSystem::try_new(base_center, u, v, w)?;
80
81 let profile = polyline(&[
86 Vector2::from_array([S::ZERO, height]),
87 Vector2::from_array([radius, height]),
88 Vector2::from_array([radius, S::ZERO]),
89 Vector2::from_array([S::ZERO, S::ZERO]),
90 ])?;
91 let namer = Namer::new("cylinder", name)?;
92 revolve_at_oriented(
93 part,
94 &namer,
95 &namer.root(),
96 &Profile::open(profile),
97 &coordinate_system,
98 )
99}
100
101pub fn extruded_cylinder<S: Scalar>(
112 part: &mut Part<S>,
113 name: &str,
114 base_center: Vector3<S>,
115 radius: S,
116 height: S,
117 segments: usize,
118) -> GeopResult<SolidId> {
119 if segments < 3 {
120 return Err(geop_core_math::geop_error::GeopError::new(
121 "extruded_cylinder: need at least 3 segments",
122 ));
123 }
124 let u = Vector3::from_array([radius, S::ZERO, S::ZERO]);
125 let v = Vector3::from_array([S::ZERO, radius, S::ZERO]);
126 let w = Vector3::from_array([S::ZERO, S::ZERO, height.mul(S::from_f64(-1.0))]);
127 let coordinate_system = CoordinateSystem::try_new(base_center, u, v, w)?;
128
129 let outer: Vec<Vector2<S>> = (0..segments)
130 .map(|k| {
131 let angle = std::f64::consts::TAU * (k as f64) / (segments as f64);
132 Vector2::from_array([S::from_f64(angle.cos()), S::from_f64(angle.sin())])
133 })
134 .collect();
135
136 let namer = Namer::new("cylinder", name)?;
137 extrude(
138 part,
139 &ExtrudeNames::single(&namer),
140 &coordinate_system,
141 &Profile::closed(polygon(&outer)?),
142 &[],
143 )
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use geop_core_math::for_all_scalars;
150 use geop_core_topology::validation::{ValidationParameters, validate, validate_manifold};
151
152 fn check_revolved_cylinder_is_valid<S: Scalar>() {
153 let mut part = Part::<S>::new();
154 revolved_cylinder(
155 &mut part,
156 "t2",
157 Vector3::from_array([S::ZERO, S::ZERO, S::ZERO]),
158 S::ONE,
159 S::TWO,
160 )
161 .unwrap();
162 let model = part.topology();
163 assert_eq!(model.faces.len(), 12);
164
165 let params = ValidationParameters::default();
166 if let Err(e) = validate(¶ms, &model) {
167 panic!("{e:?}");
168 }
169 if let Err(e) = validate_manifold(¶ms, &model) {
170 panic!("{e:?}");
171 }
172 }
173 #[test]
174 fn revolved_cylinder_is_valid() {
175 for_all_scalars!(check_revolved_cylinder_is_valid);
176 }
177
178 fn check_extruded_cylinder_is_valid<S: Scalar>() {
179 let mut part = Part::<S>::new();
180 extruded_cylinder(
181 &mut part,
182 "t4",
183 Vector3::from_array([S::ZERO, S::ZERO, S::ZERO]),
184 S::ONE,
185 S::TWO,
186 12,
187 )
188 .unwrap();
189 let model = part.topology();
190
191 let params = ValidationParameters::default();
192 if let Err(e) = validate(¶ms, &model) {
193 panic!("{e:?}");
194 }
195 if let Err(e) = validate_manifold(¶ms, &model) {
196 panic!("{e:?}");
197 }
198 assert_eq!(model.faces.len(), 12 + 2);
199 }
200 #[test]
201 fn extruded_cylinder_is_valid() {
202 for_all_scalars!(check_extruded_cylinder_is_valid);
203 }
204
205 fn check_rasterize_topology_revolved_cylinder<S: Scalar>() {
206 let mut part = Part::<S>::new();
207 revolved_cylinder(
208 &mut part,
209 "t3",
210 Vector3::from_array([S::ZERO, S::ZERO, S::ZERO]),
211 S::ONE,
212 S::TWO,
213 )
214 .unwrap();
215 let model = part.topology();
216
217 let scene = geop_ops_rasterize::rasterize_topology(&model, 32).unwrap();
218 assert!(!scene.points.is_empty());
219 assert!(!scene.lines.is_empty());
220 assert!(!scene.triangles_transparent.is_empty());
221 assert!(!scene.labels.is_empty());
222
223 std::fs::create_dir_all("outputs").unwrap();
224 scene
225 .save_to_file("outputs/revolved_cylinder_topology.html")
226 .unwrap();
227 }
228 #[test]
229 fn rasterize_topology_revolved_cylinder() {
230 for_all_scalars!(check_rasterize_topology_revolved_cylinder);
231 }
232
233 fn check_rasterize_topology_extruded_cylinder<S: Scalar>() {
234 let mut part = Part::<S>::new();
235 extruded_cylinder(
236 &mut part,
237 "t5",
238 Vector3::from_array([S::ZERO, S::ZERO, S::ZERO]),
239 S::ONE,
240 S::TWO,
241 12,
242 )
243 .unwrap();
244 let model = part.topology();
245
246 let scene = geop_ops_rasterize::rasterize_topology(&model, 8).unwrap();
247 assert!(!scene.points.is_empty());
248 assert!(!scene.lines.is_empty());
249 assert!(!scene.triangles_transparent.is_empty());
250 assert!(!scene.labels.is_empty());
251
252 std::fs::create_dir_all("outputs").unwrap();
253 scene
254 .save_to_file("outputs/extruded_cylinder_topology.html")
255 .unwrap();
256 }
257 #[test]
258 fn rasterize_topology_extruded_cylinder() {
259 for_all_scalars!(check_rasterize_topology_extruded_cylinder);
260 }
261
262 fn check_revolved_cylinder_along_axis_is_valid<S: Scalar>() {
268 let params = ValidationParameters::default();
269 for (axis, extent_axis) in [(Axis::X, 0), (Axis::Y, 1), (Axis::Z, 2)] {
270 let mut part = Part::<S>::new();
271 revolved_cylinder_along_axis(
272 &mut part,
273 "t1",
274 Vector3::from_array([S::ZERO, S::ZERO, S::ZERO]),
275 S::ONE,
276 S::TWO,
277 axis,
278 )
279 .unwrap_or_else(|e| panic!("{axis:?}: {e}"));
280 let model = part.topology();
281 assert_eq!(model.faces.len(), 12, "{axis:?}");
282
283 validate(¶ms, &model).unwrap_or_else(|e| panic!("{axis:?}: {e:?}"));
284 validate_manifold(¶ms, &model).unwrap_or_else(|e| panic!("{axis:?}: {e:?}"));
285
286 let max_extent = model
290 .vertices
291 .values()
292 .map(|v| v.point[extent_axis].to_f64())
293 .fold(0.0_f64, f64::max);
294 assert!(
295 (max_extent - 2.0).abs() < 1e-6,
296 "{axis:?}: max extent along its own axis = {max_extent}"
297 );
298 for other in 0..3 {
299 if other == extent_axis {
300 continue;
301 }
302 let max_other = model
303 .vertices
304 .values()
305 .map(|v| v.point[other].to_f64().abs())
306 .fold(0.0_f64, f64::max);
307 assert!(
308 max_other <= 1.0 + 1e-6,
309 "{axis:?}: vertex strayed to {max_other} on axis {other}, radius is 1"
310 );
311 }
312 }
313 }
314 #[test]
315 fn revolved_cylinder_along_axis_is_valid() {
316 for_all_scalars!(check_revolved_cylinder_along_axis_is_valid);
317 }
318}