geop_ops_extrude_revolve/
figure8_profile.rs1use crate::{
11 common::{Profile, polygon},
12 extrude::{ExtrudeNames, extrude},
13};
14use geop_core_math::{
15 geop_error::GeopResult,
16 primitives::CoordinateSystem,
17 scalars::Scalar,
18 vector::{Vector2, Vector3},
19};
20use geop_core_part::{Namer, Part};
21use geop_core_topology::SolidId;
22
23pub fn outer_polygon<S: Scalar>() -> Vec<Vector2<S>> {
26 let c = |x: f64, y: f64| Vector2::from_array([S::from_f64(x), S::from_f64(y)]);
27 vec![
28 c(0.0, 0.0),
29 c(3.0 / 8.0, 0.0),
30 c(3.0 / 8.0, 1.0 / 3.0),
31 c(5.0 / 8.0, 1.0 / 3.0),
32 c(5.0 / 8.0, 0.0),
33 c(1.0, 0.0),
34 c(1.0, 1.0),
35 c(5.0 / 8.0, 1.0),
36 c(5.0 / 8.0, 2.0 / 3.0),
37 c(3.0 / 8.0, 2.0 / 3.0),
38 c(3.0 / 8.0, 1.0),
39 c(0.0, 1.0),
40 ]
41}
42
43pub fn hole_polygons<S: Scalar>() -> [Vec<Vector2<S>>; 2] {
46 let c = |x: f64, y: f64| Vector2::from_array([S::from_f64(x), S::from_f64(y)]);
47 [
48 vec![
49 c(0.125, 1.0 / 3.0),
50 c(0.125, 2.0 / 3.0),
51 c(0.25, 2.0 / 3.0),
52 c(0.25, 1.0 / 3.0),
53 ],
54 vec![
55 c(0.75, 1.0 / 3.0),
56 c(0.75, 2.0 / 3.0),
57 c(0.875, 2.0 / 3.0),
58 c(0.875, 1.0 / 3.0),
59 ],
60 ]
61}
62
63pub fn figure8_profile<S: Scalar>(part: &mut Part<S>, name: &str) -> GeopResult<SolidId> {
71 let origin = Vector3::from_array([S::ZERO, S::ZERO, S::ZERO]);
76 let u = Vector3::from_array([S::ONE, S::ZERO, S::ZERO]);
77 let v = Vector3::from_array([S::ZERO, S::ONE, S::ZERO]);
78 let w = Vector3::from_array([S::ZERO, S::ZERO, S::from_f64(-1.0)]);
79 let coordinate_system = CoordinateSystem::try_new(origin, u, v, w)?;
80
81 let outer = Profile::closed(polygon(&outer_polygon::<S>())?);
82 let holes = hole_polygons::<S>()
83 .iter()
84 .enumerate()
85 .map(|(k, h)| Ok(Profile::closed(polygon(h)?).with_prefix(&format!("h{k}"))))
86 .collect::<GeopResult<Vec<_>>>()?;
87 let namer = Namer::new("figure8", name)?;
88 extrude(
89 part,
90 &ExtrudeNames::single(&namer),
91 &coordinate_system,
92 &outer,
93 &holes,
94 )
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use geop_core_math::for_all_scalars;
101 use geop_core_topology::validation::{ValidationParameters, validate, validate_manifold};
102
103 fn check_figure8_face_count<S: Scalar>() {
106 let mut part = Part::<S>::new();
107 figure8_profile(&mut part, "t").unwrap();
108 let model = part.topology();
109 assert_eq!(model.faces.len(), 22);
110 }
111 #[test]
112 fn figure8_face_count() {
113 for_all_scalars!(check_figure8_face_count);
114 }
115
116 fn check_figure8_cap_normals_point_outward<S: Scalar>() {
117 let mut part = Part::<S>::new();
118 figure8_profile(&mut part, "t").unwrap();
119 let model = part.topology();
120 for face in model.faces.values() {
121 let (u0, u1) = face.surface.domain_u();
122 let (v0, v1) = face.surface.domain_v();
123 let mid_u = u0.add(u1).div(S::from_f64(2.0)).unwrap();
124 let mid_v = v0.add(v1).div(S::from_f64(2.0)).unwrap();
125 let p = face.surface.evaluate(mid_u, mid_v).unwrap();
126 let n = face.surface.normal(mid_u, mid_v).unwrap();
127 let p_at_00 = face.surface.evaluate(u0, v0).unwrap();
130 if !p[2].could_be_equal(p_at_00[2]) {
131 continue;
132 }
133 let expect_positive_z = p[2].to_f64().abs() < 0.5;
136 assert_eq!(
137 n[2].to_f64() > 0.0,
138 expect_positive_z,
139 "cap at z={} has normal z-component {}",
140 p[2].to_f64(),
141 n[2].to_f64()
142 );
143 }
144 }
145 #[test]
146 fn figure8_cap_normals_point_outward() {
147 for_all_scalars!(check_figure8_cap_normals_point_outward);
148 }
149
150 fn check_figure8_profile_is_valid<S: Scalar>() {
151 let mut part = Part::<S>::new();
152 figure8_profile(&mut part, "t").unwrap();
153 let model = part.topology();
154
155 let params = ValidationParameters::default();
156 if let Err(e) = validate(¶ms, &model) {
157 panic!("{e:?}");
158 }
159 if let Err(e) = validate_manifold(¶ms, &model) {
160 panic!("{e:?}");
161 }
162 }
163 #[test]
164 fn figure8_profile_is_valid() {
165 for_all_scalars!(check_figure8_profile_is_valid);
166 }
167
168 fn check_rasterize_topology_figure8<S: Scalar>() {
169 let mut part = Part::<S>::new();
170 figure8_profile(&mut part, "t").unwrap();
171 let model = part.topology();
172
173 let scene = geop_ops_rasterize::rasterize_topology(&model, 8).unwrap();
174 assert!(!scene.points.is_empty());
175 assert!(!scene.lines.is_empty());
176 assert!(!scene.triangles_transparent.is_empty());
177 assert!(!scene.labels.is_empty());
178
179 std::fs::create_dir_all("outputs").unwrap();
180 scene.save_to_file("outputs/figure8_topology.html").unwrap();
181 }
182 #[test]
183 fn rasterize_topology_figure8() {
184 for_all_scalars!(check_rasterize_topology_figure8);
185 }
186}