1use crate::linalg::{Vec2, Vec3, IVec3, Mat3x4};
8use crate::types::{
9 Polygons, SimplePolygon, PolygonsIdx, SimplePolygonIdx, PolyVert,
10 cosd, sind, Quality,
11};
12use crate::polygon::{triangulate_idx, triangulate};
13use crate::impl_mesh::ManifoldImpl;
14
15pub fn extrude(
27 cross_section: &Polygons,
28 height: f64,
29 n_divisions: i32,
30 twist_degrees: f64,
31 scale_top: Vec2,
32) -> ManifoldImpl {
33 if cross_section.is_empty() || height <= 0.0 {
34 return ManifoldImpl::new();
35 }
36
37 let scale_top = Vec2::new(scale_top.x.max(0.0), scale_top.y.max(0.0));
38 let n_div = n_divisions + 1; let mut vert_pos: Vec<Vec3> = Vec::new();
41 let mut tri_verts: Vec<IVec3> = Vec::new();
42 let is_cone = scale_top.x == 0.0 && scale_top.y == 0.0;
43
44 let n_cross: i32 = cross_section.iter().map(|p| p.len() as i32).sum();
46
47 let mut polygons_indexed: PolygonsIdx = Vec::new();
49 let mut idx: i32 = 0;
50 for poly in cross_section.iter() {
51 let mut simple_indexed: SimplePolygonIdx = Vec::new();
52 for &pv in poly.iter() {
53 vert_pos.push(Vec3::new(pv.x, pv.y, 0.0));
54 simple_indexed.push(PolyVert { pos: pv, idx });
55 idx += 1;
56 }
57 polygons_indexed.push(simple_indexed);
58 }
59
60 for i in 1..=(n_div as usize) {
62 let alpha = i as f64 / n_div as f64;
63 let phi = alpha * twist_degrees;
64 let scale = lerp2(Vec2::new(1.0, 1.0), scale_top, alpha);
65 let cos_phi = cosd(phi);
66 let sin_phi = sind(phi);
67
68 let t00 = scale.x * cos_phi; let t01 = scale.y * sin_phi; let t10 = scale.x * -sin_phi; let t11 = scale.y * cos_phi; let mut j: i32 = 0; let mut poly_offset: i32 = 0; for (pi, poly) in cross_section.iter().enumerate() {
79 let poly_len = poly.len() as i32;
80 for vert in 0..poly_len {
81 let offset = poly_offset + n_cross * i as i32;
82 let this_vert = vert + offset;
83 let last_vert = (if vert == 0 { poly_len } else { vert }) - 1 + offset;
84 if i == n_div as usize && is_cone {
85 let apex = n_cross * n_div as i32 + j;
87 tri_verts.push(IVec3::new(
88 apex,
89 last_vert - n_cross,
90 this_vert - n_cross,
91 ));
92 } else {
93 let pos2 = poly[vert as usize];
94 let px = t00 * pos2.x + t10 * pos2.y;
95 let py = t01 * pos2.x + t11 * pos2.y;
96 vert_pos.push(Vec3::new(px, py, height * alpha));
97 tri_verts.push(IVec3::new(this_vert, last_vert, this_vert - n_cross));
98 tri_verts.push(IVec3::new(last_vert, last_vert - n_cross, this_vert - n_cross));
99 }
100 }
101 j += 1;
102 poly_offset += poly_len;
103 let _ = pi; }
105 }
106
107 if is_cone {
109 for _ in 0..cross_section.len() {
110 vert_pos.push(Vec3::new(0.0, 0.0, height));
111 }
112 }
113
114 let top_tris = triangulate_idx(&polygons_indexed, -1.0, true);
118 for tri in &top_tris {
119 tri_verts.push(IVec3::new(tri.x, tri.z, tri.y));
121 if !is_cone {
123 tri_verts.push(IVec3::new(
124 tri.x + n_cross * n_div as i32,
125 tri.y + n_cross * n_div as i32,
126 tri.z + n_cross * n_div as i32,
127 ));
128 }
129 }
130
131 let mut m = ManifoldImpl::new();
132 m.vert_pos = vert_pos;
133 m.create_halfedges(&tri_verts, &[]);
134 m.initialize_original();
135 m.calculate_bbox();
136 m.set_epsilon(-1.0, false);
137 m.sort_geometry();
138 m.set_normals_and_coplanar();
139 m
140}
141
142pub fn revolve(
152 cross_section: &Polygons,
153 circular_segments: i32,
154 revolve_degrees: f64,
155) -> ManifoldImpl {
156 let mut polygons: Polygons = Vec::new();
158 let mut radius: f64 = 0.0;
159 for poly in cross_section.iter() {
160 let mut i = 0usize;
161 while i < poly.len() && poly[i].x < 0.0 {
162 i += 1;
163 }
164 if i == poly.len() {
165 continue;
166 }
167 let mut clipped: SimplePolygon = Vec::new();
168 let start = i;
169 let poly_len = poly.len();
170 loop {
171 if poly[i].x >= 0.0 {
172 clipped.push(poly[i]);
173 radius = radius.max(poly[i].x);
174 }
175 let next = if i + 1 == poly_len { 0 } else { i + 1 };
176 if (poly[next].x < 0.0) != (poly[i].x < 0.0) {
178 let y = poly[next].y
179 - poly[next].x * (poly[i].y - poly[next].y)
180 / (poly[i].x - poly[next].x);
181 clipped.push(Vec2::new(0.0, y));
182 }
183 i = next;
184 if i == start {
185 break;
186 }
187 }
188 if !clipped.is_empty() {
189 polygons.push(clipped);
190 }
191 }
192
193 if polygons.is_empty() {
194 return ManifoldImpl::new();
195 }
196
197 let revolve_degrees = revolve_degrees.min(360.0);
198 let is_full_revolution = revolve_degrees == 360.0;
199
200 let n_divisions = if circular_segments > 2 {
201 circular_segments
202 } else {
203 let segs = Quality::get_circular_segments(radius);
204 (segs as f64 * revolve_degrees / 360.0) as i32
205 };
206 let n_divisions = n_divisions.max(3);
207
208 let mut vert_pos: Vec<Vec3> = Vec::new();
209 let mut tri_verts: Vec<IVec3> = Vec::new();
210
211 let mut start_poses: Vec<i32> = Vec::new();
212 let mut end_poses: Vec<i32> = Vec::new();
213
214 let d_phi = revolve_degrees / n_divisions as f64;
215 let n_slices = if is_full_revolution { n_divisions } else { n_divisions + 1 };
217
218 for poly in polygons.iter() {
219 let n_pos_verts: usize = poly.iter().filter(|p| p.x > 0.0).count();
220 let n_axis_verts: usize = poly.iter().filter(|p| p.x == 0.0).count();
221 let _ = n_axis_verts;
222
223 let mut n_revolve_axis_verts: usize = 0;
224 for pt in poly.iter() {
225 if pt.x == 0.0 {
226 n_revolve_axis_verts += 1;
227 }
228 }
229
230 for poly_vert in 0..poly.len() {
231 let start_pos_index = vert_pos.len() as i32;
232
233 if !is_full_revolution {
234 start_poses.push(start_pos_index);
235 }
236
237 let curr = poly[poly_vert];
238 let prev = poly[if poly_vert == 0 { poly.len() - 1 } else { poly_vert - 1 }];
239
240 let prev_start_pos_index = start_pos_index
242 + (if poly_vert == 0 {
243 (n_revolve_axis_verts + n_slices as usize * n_pos_verts) as i32
244 } else {
245 0
246 })
247 + if prev.x == 0.0 { -1 } else { -(n_slices as i32) };
248
249 for slice in 0..n_slices {
250 let phi = slice as f64 * d_phi;
251 if slice == 0 || curr.x > 0.0 {
253 vert_pos.push(Vec3::new(
254 curr.x * cosd(phi),
255 curr.x * sind(phi),
256 curr.y,
257 ));
258 }
259
260 if is_full_revolution || slice > 0 {
261 let last_slice = if slice == 0 { n_divisions } else { slice } - 1;
262 if curr.x > 0.0 {
263 tri_verts.push(IVec3::new(
264 start_pos_index + slice as i32,
265 start_pos_index + last_slice as i32,
266 if prev.x == 0.0 {
267 prev_start_pos_index
268 } else {
269 prev_start_pos_index + last_slice as i32
270 },
271 ));
272 }
273 if prev.x > 0.0 {
274 tri_verts.push(IVec3::new(
275 prev_start_pos_index + last_slice as i32,
276 prev_start_pos_index + slice as i32,
277 if curr.x == 0.0 {
278 start_pos_index
279 } else {
280 start_pos_index + slice as i32
281 },
282 ));
283 }
284 }
285 }
286
287 if !is_full_revolution {
288 end_poses.push(vert_pos.len() as i32 - 1);
289 }
290 }
291 }
292
293 if !is_full_revolution {
295 let front_tris = triangulate(&polygons, -1.0, false);
296 for t in &front_tris {
297 tri_verts.push(IVec3::new(start_poses[t.x as usize], start_poses[t.y as usize], start_poses[t.z as usize]));
298 }
299 for t in &front_tris {
300 tri_verts.push(IVec3::new(end_poses[t.z as usize], end_poses[t.y as usize], end_poses[t.x as usize]));
301 }
302 }
303
304 let mut m = ManifoldImpl::new();
305 m.vert_pos = vert_pos;
306 m.create_halfedges(&tri_verts, &[]);
307 m.initialize_original();
308 m.calculate_bbox();
309 m.set_epsilon(-1.0, false);
310 m.sort_geometry();
311 m.set_normals_and_coplanar();
312 m
313}
314
315pub fn cylinder(
328 height: f64,
329 radius_low: f64,
330 radius_high: f64,
331 circular_segments: i32,
332 center: bool,
333) -> ManifoldImpl {
334 if height <= 0.0 || radius_low < 0.0 {
335 return ManifoldImpl::new();
336 }
337 if radius_low == 0.0 {
338 if radius_high <= 0.0 {
339 return ManifoldImpl::new();
340 }
341 let cone = cylinder(height, radius_high, 0.0, circular_segments, true);
347 let translate_z = if center { 0.0 } else { height / 2.0 };
348 let m = Mat3x4::from_cols(
349 Vec3::new(1.0, 0.0, 0.0),
350 Vec3::new(0.0, 1.0, 0.0),
351 Vec3::new(0.0, 0.0, -1.0),
352 Vec3::new(0.0, 0.0, translate_z),
353 );
354 let mut cone = cone.transform(&m);
355 cone.initialize_original();
357 crate::face_op::set_normals_and_coplanar(&mut cone);
358 return cone;
359 }
360
361 let scale = if radius_high >= 0.0 { radius_high / radius_low } else { 1.0 };
362 let radius = radius_low.max(if radius_high >= 0.0 { radius_high } else { 0.0 });
363 let n = if circular_segments > 2 {
364 circular_segments
365 } else {
366 Quality::get_circular_segments(radius)
367 };
368
369 let d_phi = 360.0 / n as f64;
370 let mut circle: SimplePolygon = Vec::with_capacity(n as usize);
371 for i in 0..n {
372 circle.push(Vec2::new(
373 radius_low * cosd(d_phi * i as f64),
374 radius_low * sind(d_phi * i as f64),
375 ));
376 }
377
378 let mut m = extrude(
379 &vec![circle],
380 height,
381 0,
382 0.0,
383 Vec2::new(scale, scale),
384 );
385
386 if center {
387 for v in m.vert_pos.iter_mut() {
388 v.z -= height / 2.0;
389 }
390 m.calculate_bbox();
391 }
392 m
393}
394
395fn lerp2(a: Vec2, b: Vec2, t: f64) -> Vec2 {
400 Vec2::new(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t)
401}
402
403#[cfg(test)]
408mod tests {
409 use super::*;
410 use crate::linalg::Vec2;
411
412 fn unit_square() -> Polygons {
413 vec![vec![
414 Vec2::new(0.0, 0.0),
415 Vec2::new(1.0, 0.0),
416 Vec2::new(1.0, 1.0),
417 Vec2::new(0.0, 1.0),
418 ]]
419 }
420
421 #[test]
422 fn test_extrude_box() {
423 let m = extrude(&unit_square(), 1.0, 0, 0.0, Vec2::new(1.0, 1.0));
424 assert!(m.is_2_manifold(), "extruded box is not 2-manifold");
426 assert!((m.bbox.min.z).abs() < 1e-10);
428 assert!((m.bbox.max.z - 1.0).abs() < 1e-10);
429 }
430
431 #[test]
432 fn test_extrude_cone() {
433 let m = extrude(&unit_square(), 1.0, 0, 0.0, Vec2::new(0.0, 0.0));
434 assert!(m.is_2_manifold(), "extruded cone is not 2-manifold");
435 assert!((m.bbox.max.z - 1.0).abs() < 1e-10);
436 }
437
438 #[test]
439 fn test_extrude_twist() {
440 let m = extrude(&unit_square(), 1.0, 4, 90.0, Vec2::new(1.0, 1.0));
441 assert!(m.is_2_manifold(), "twisted extrude is not 2-manifold");
442 }
443
444 #[test]
445 fn test_revolve_full() {
446 let poly: SimplePolygon = vec![
448 Vec2::new(1.0, 0.0),
449 Vec2::new(2.0, 0.0),
450 Vec2::new(2.0, 1.0),
451 Vec2::new(1.0, 1.0),
452 ];
453 let m = revolve(&vec![poly], 8, 360.0);
454 assert!(m.is_2_manifold(), "full revolve is not 2-manifold");
455 }
456
457 #[test]
458 fn test_revolve_partial() {
459 let poly: SimplePolygon = vec![
460 Vec2::new(1.0, 0.0),
461 Vec2::new(2.0, 0.0),
462 Vec2::new(2.0, 1.0),
463 Vec2::new(1.0, 1.0),
464 ];
465 let m = revolve(&vec![poly], 8, 180.0);
466 assert!(m.is_2_manifold(), "partial revolve is not 2-manifold");
467 }
468
469 #[test]
470 fn test_cylinder_basic() {
471 let m = cylinder(1.0, 1.0, 1.0, 8, false);
472 assert!(m.is_2_manifold(), "cylinder is not 2-manifold");
473 assert!((m.bbox.max.z - 1.0).abs() < 1e-10);
474 assert!(m.bbox.min.z.abs() < 1e-10);
475 }
476
477 #[test]
478 fn test_cylinder_centered() {
479 let m = cylinder(2.0, 1.0, 1.0, 8, true);
480 assert!(m.is_2_manifold(), "centered cylinder is not 2-manifold");
481 assert!((m.bbox.min.z + 1.0).abs() < 1e-10);
482 assert!((m.bbox.max.z - 1.0).abs() < 1e-10);
483 }
484
485 #[test]
486 fn test_cylinder_cone() {
487 let m = cylinder(1.0, 1.0, 0.0, 8, false);
489 assert!(m.is_2_manifold(), "cone cylinder is not 2-manifold");
490 }
491
492 #[test]
493 fn test_extrude_empty_invalid() {
494 let m = extrude(&vec![], 1.0, 0, 0.0, Vec2::new(1.0, 1.0));
495 assert_eq!(m.num_tri(), 0);
496
497 let m2 = extrude(&unit_square(), -1.0, 0, 0.0, Vec2::new(1.0, 1.0));
498 assert_eq!(m2.num_tri(), 0);
499 }
500}