1use std::f64::consts::PI;
7
8use cgmath::{Matrix, Point3, SquareMatrix, Transform, Vector3};
9
10use geo::TriangulateEarcut;
11
12use crate::*;
13
14pub enum Extrusion {
16 Linear {
18 height: Length,
20 scale_x: Scalar,
22 scale_y: Scalar,
24 twist: Angle,
26 },
27 Revolve {
29 angle: Angle,
31 segments: usize,
33 },
34}
35
36pub trait Extrude {
38 fn extrude(&self, extrusion: Extrusion) -> WithBounds3D<TriangleMesh> {
40 match extrusion {
41 Extrusion::Linear {
42 height,
43 scale_x,
44 scale_y,
45 twist,
46 } => self.linear_extrude(height, scale_x, scale_y, twist),
47 Extrusion::Revolve { angle, segments } => self.revolve_extrude(angle, segments),
48 }
49 }
50
51 fn extrude_slice(&self, m_a: &Mat4, m_b: &Mat4) -> TriangleMesh;
53
54 fn cap(&self, _m: &Mat4, _bottom: bool) -> TriangleMesh {
56 TriangleMesh::default()
57 }
58
59 fn linear_extrude(
61 &self,
62 height: Length,
63 scale_x: Scalar,
64 scale_y: Scalar,
65 twist: Angle,
66 ) -> WithBounds3D<TriangleMesh> {
67 let m_a = Mat4::identity();
68 let m_b = Mat4::from_angle_z(twist)
69 * Mat4::from_translation(Vec3::new(0.0, 0.0, *height))
70 * Mat4::from_nonuniform_scale(scale_x, scale_y, 1.0);
71 let mut mesh = self.extrude_slice(&m_a, &m_b);
72 mesh.append(&self.cap(&m_a, true));
73 mesh.append(&self.cap(&m_b, false));
74 let bounds = mesh.calc_bounds_3d();
75 mesh.repair(&bounds);
76 WithBounds3D::new(mesh, bounds)
77 }
78
79 fn revolve_extrude(&self, angle: Angle, segments: usize) -> WithBounds3D<TriangleMesh> {
81 let mut mesh = TriangleMesh::default();
82 if segments < 2 {
83 return WithBounds3D::default();
84 }
85
86 let delta = angle / segments as Scalar;
87
88 let transforms: Vec<_> = (0..=segments)
90 .map(|i| {
91 let a = delta * i as Scalar;
92 let mut mat = Mat4::from_angle_y(a);
93 mat.swap_rows(2, 1); mat
95 })
96 .collect();
97
98 for i in 0..segments {
100 let m_a = &transforms[i];
101 let m_b = &transforms[i + 1];
102 let slice = self.extrude_slice(m_a, m_b);
103 mesh.append(&slice);
104 }
105
106 if angle.0 < PI * 2.0 {
108 let m_start = &transforms[0];
109 let m_end = transforms.last().expect("Transform");
110 mesh.append(&self.cap(m_start, true));
111 mesh.append(&self.cap(m_end, false));
112 }
113
114 let bounds = mesh.calc_bounds_3d();
115 mesh.repair(&bounds);
116 WithBounds3D::new(mesh, bounds)
117 }
118
119 fn spiralize(
127 &self,
128 height: Scalar,
129 inner_radius: Scalar,
130 outer_radius: Scalar,
131 turns: Scalar,
132 segments_per_turn: usize,
133 ) -> WithBounds3D<TriangleMesh> {
134 let mut mesh = TriangleMesh::default();
135
136 if segments_per_turn < 2 || turns <= 0.0 {
137 return WithBounds3D::default();
138 }
139
140 let total_segments = (turns * segments_per_turn as Scalar).round() as usize;
142 if total_segments < 1 {
143 return WithBounds3D::default();
144 }
145
146 let total_angle = turns * 2.0 * std::f64::consts::PI;
148 let delta_angle = total_angle / (total_segments as Scalar);
149
150 let delta_height = height / (total_segments as Scalar);
152
153 let delta_radius = (outer_radius - inner_radius) / (total_segments as Scalar);
155
156 let transforms: Vec<Mat4> = (0..=total_segments)
158 .map(|i| {
159 let angle_i = delta_angle * (i as Scalar);
160 let height_i = delta_height * (i as Scalar);
161 let radius_i = inner_radius + delta_radius * (i as Scalar);
162
163 let mut mat = Mat4::from_angle_y(cgmath::Rad(angle_i));
165 mat.swap_rows(2, 1);
167
168 mat = mat * Mat4::from_scale(radius_i);
170
171 mat = mat * Mat4::from_translation(Vec3::new(0.0, height_i, 0.0));
173
174 mat
175 })
176 .collect();
177
178 for i in 0..total_segments {
180 let m_a = &transforms[i];
181 let m_b = &transforms[i + 1];
182 let slice = self.extrude_slice(m_a, m_b);
183 mesh.append(&slice);
184 }
185
186 {
188 let m_start = &transforms[0];
189 let m_end = transforms.last().expect("Transform");
190 mesh.append(&self.cap(m_start, true));
191 mesh.append(&self.cap(m_end, false));
192 }
193
194 let bounds = mesh.calc_bounds_3d();
195 mesh.repair(&bounds);
196 WithBounds3D::new(mesh, bounds)
197 }
198}
199
200impl Extrude for LineString {
201 fn extrude_slice(&self, m_a: &Mat4, m_b: &Mat4) -> TriangleMesh {
202 let mut mesh = TriangleMesh::default();
203
204 let points = self.points();
205 let len = points.len();
206 if len < 2 {
207 return mesh; }
209 mesh.positions.reserve(len * 2); mesh.triangle_indices.reserve(len * 2); let m_a: cgmath::Matrix4<f32> = m_a.cast().expect("Successful cast");
214 let m_b: cgmath::Matrix4<f32> = m_b.cast().expect("Successful cast");
215
216 let transform_point =
217 |p: &cgmath::Point3<f32>, m: &cgmath::Matrix4<f32>| -> cgmath::Vector3<f32> {
218 m.transform_point(*p).to_homogeneous().truncate()
219 };
220
221 for point in points {
223 let point = cgmath::Point3::new(point.x() as f32, point.y() as f32, 0.0_f32);
224 mesh.positions.push(transform_point(&point, &m_a)); mesh.positions.push(transform_point(&point, &m_b)); }
227
228 let range = if self.is_closed() {
229 0..len
230 } else {
231 0..(len - 1)
232 };
233
234 for i in range {
235 let next = (i + 1) % len;
236
237 let bl = (i * 2) as u32;
238 let br = (next * 2) as u32;
239 let tl = bl + 1;
240 let tr = br + 1;
241 mesh.triangle_indices.push(Triangle(bl, br, tr));
242 mesh.triangle_indices.push(Triangle(bl, tr, tl));
243 }
244
245 mesh
246 }
247}
248
249impl Extrude for Polygon {
250 fn extrude_slice(&self, m_a: &Mat4, m_b: &Mat4) -> TriangleMesh {
251 let mut mesh = TriangleMesh::default();
252 mesh.append(&self.exterior().extrude_slice(m_a, m_b));
253 for interior in self.interiors() {
254 mesh.append(&interior.extrude_slice(m_a, m_b));
255 }
256 mesh
257 }
258
259 fn cap(&self, m: &Mat4, flip: bool) -> TriangleMesh {
260 let raw_triangulation = self.earcut_triangles_raw();
261 let m: cgmath::Matrix4<f32> = m.cast().expect("Successful cast");
262
263 TriangleMesh {
264 positions: raw_triangulation
265 .vertices
266 .as_slice()
267 .chunks_exact(2)
268 .map(|chunk| {
269 let p = Point3::new(chunk[0] as f32, chunk[1] as f32, 0.0_f32);
270 let p = m.transform_point(p);
271 Vector3::<f32>::new(p.x, p.y, p.z)
272 })
273 .collect(),
274 normals: None,
275 triangle_indices: raw_triangulation
276 .triangle_indices
277 .as_slice()
278 .chunks_exact(3)
279 .map(|chunk| match flip {
280 true => Triangle(chunk[2] as u32, chunk[1] as u32, chunk[0] as u32),
281 false => Triangle(chunk[0] as u32, chunk[1] as u32, chunk[2] as u32),
282 })
283 .collect(),
284 }
285 }
286}
287
288impl Extrude for MultiPolygon {
289 fn extrude_slice(&self, m_a: &Mat4, m_b: &Mat4) -> TriangleMesh {
290 let mut mesh = TriangleMesh::default();
291 self.iter().for_each(|polygon| {
292 mesh.append(&polygon.extrude_slice(m_a, m_b));
293 });
294 mesh
295 }
296
297 fn cap(&self, m: &Mat4, flip: bool) -> TriangleMesh {
298 let mut mesh = TriangleMesh::default();
299 self.iter().for_each(|polygon| {
300 mesh.append(&polygon.cap(m, flip));
301 });
302 mesh
303 }
304}
305
306impl Extrude for Geometries2D {
307 fn extrude_slice(&self, m_a: &Mat4, m_b: &Mat4) -> TriangleMesh {
308 self.to_multi_polygon().extrude_slice(m_a, m_b)
309 }
310
311 fn cap(&self, m: &Mat4, flip: bool) -> TriangleMesh {
312 self.to_multi_polygon().cap(m, flip)
313 }
314}