1use crate::math::{add, arc_segments, basis, dot, length, normalize, scale, sub};
2use crate::mesh::{Builder, Mesh, weld};
3use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI, TAU};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub enum Shape {
7 RoundBox {
8 half: [f32; 3],
9 radius: f32,
10 },
11 RoundCone {
12 a: [f32; 3],
13 b: [f32; 3],
14 r1: f32,
15 r2: f32,
16 },
17 Capsule {
18 a: [f32; 3],
19 b: [f32; 3],
20 radius: f32,
21 },
22 Ellipsoid {
23 radius: f32,
24 squash: f32,
25 },
26 Slab {
27 half_length: f32,
28 radius: f32,
29 height: f32,
30 fillet: f32,
31 },
32 Torus {
33 major: f32,
34 minor: f32,
35 },
36}
37
38impl Shape {
39 pub fn distance(&self, p: [f32; 3]) -> f32 {
40 match *self {
41 Shape::RoundBox { half, radius } => round_box(p, half, radius),
42 Shape::RoundCone { a, b, r1, r2 } => round_cone(p, a, b, r1, r2),
43 Shape::Capsule { a, b, radius } => round_cone(p, a, b, radius, radius),
44 Shape::Ellipsoid { radius, squash } => ellipsoid(p, [radius, radius, radius * squash]),
45 Shape::Slab {
46 half_length,
47 radius,
48 height,
49 fillet,
50 } => slab(p, half_length, radius, height, fillet),
51 Shape::Torus { major, minor } => {
52 let q = [(p[0] * p[0] + p[1] * p[1]).sqrt() - major, p[2]];
53 (q[0] * q[0] + q[1] * q[1]).sqrt() - minor
54 }
55 }
56 }
57
58 pub fn mesh(&self, error: f32) -> Mesh {
59 let step = (error * 0.5).max(1e-6);
60 match *self {
61 Shape::RoundBox { half, radius } => round_box_mesh(half, radius, step, error),
62 Shape::RoundCone { a, b, r1, r2 } => round_cone_mesh(a, b, r1, r2, step, error),
63 Shape::Capsule { a, b, radius } => round_cone_mesh(a, b, radius, radius, step, error),
64 Shape::Ellipsoid { radius, squash } => {
65 ellipsoid_mesh([radius, radius, radius * squash], step, error)
66 }
67 Shape::Slab {
68 half_length,
69 radius,
70 height,
71 fillet,
72 } => slab_mesh(half_length, radius, height, fillet, step, error),
73 Shape::Torus { major, minor } => torus_mesh(major, minor, step, error),
74 }
75 }
76}
77
78fn round_box(p: [f32; 3], half: [f32; 3], radius: f32) -> f32 {
79 let q = [
80 p[0].abs() - half[0] + radius,
81 p[1].abs() - half[1] + radius,
82 p[2].abs() - half[2] + radius,
83 ];
84 let outside = length([q[0].max(0.0), q[1].max(0.0), q[2].max(0.0)]);
85 outside + q[0].max(q[1]).max(q[2]).min(0.0) - radius
86}
87
88pub fn round_cone(p: [f32; 3], a: [f32; 3], b: [f32; 3], r1: f32, r2: f32) -> f32 {
89 let ba = sub(b, a);
90 let l2 = dot(ba, ba).max(1e-4);
91 let rr = r1 - r2;
92 let a2 = l2 - rr * rr;
93 let il2 = 1.0 / l2;
94 let pa = sub(p, a);
95 let y = dot(pa, ba);
96 let z = y - l2;
97 let xv = sub(scale(pa, l2), scale(ba, y));
98 let x2 = dot(xv, xv);
99 let y2 = y * y * l2;
100 let z2 = z * z * l2;
101 let k = crate::math::sign(rr) * rr * rr * x2;
102 if crate::math::sign(z) * a2 * z2 > k {
103 return (x2 + z2).sqrt() * il2 - r2;
104 }
105 if crate::math::sign(y) * a2 * y2 < k {
106 return (x2 + y2).sqrt() * il2 - r1;
107 }
108 ((x2 * a2 * il2).sqrt() + y * rr) * il2 - r1
109}
110
111fn ellipsoid(p: [f32; 3], radii: [f32; 3]) -> f32 {
112 let k0 = length([p[0] / radii[0], p[1] / radii[1], p[2] / radii[2]]);
113 let k1 = length([
114 p[0] / (radii[0] * radii[0]),
115 p[1] / (radii[1] * radii[1]),
116 p[2] / (radii[2] * radii[2]),
117 ]);
118 k0 * (k0 - 1.0) / k1.max(1e-5)
119}
120
121fn slab(p: [f32; 3], half_length: f32, radius: f32, height: f32, fillet: f32) -> f32 {
122 let flat = length([(p[0].abs() - (half_length - radius)).max(0.0), p[1], 0.0]) - radius;
123 let e = fillet.min(height * 0.5).min(radius);
124 let w = [flat + e, (p[2] - height * 0.5).abs() - height * 0.5 + e];
125 w[0].max(w[1]).min(0.0) + (w[0].max(0.0).powi(2) + w[1].max(0.0).powi(2)).sqrt() - e
126}
127
128fn segments(radius: f32, step: f32, span: f32, least: usize) -> usize {
129 arc_segments(radius, step, span).max(least)
130}
131
132fn round_box_mesh(half: [f32; 3], radius: f32, step: f32, error: f32) -> Mesh {
133 let radius = radius.clamp(0.0, half[0].min(half[1]).min(half[2]));
134 let inner = [half[0] - radius, half[1] - radius, half[2] - radius];
135 let k = if radius > 0.0 {
136 segments(radius, step * 0.6, FRAC_PI_4, 1)
137 } else {
138 0
139 };
140 let axis_samples = |axis: usize| -> Vec<f32> {
141 let mut out = Vec::new();
142 for i in (0..=k).rev() {
143 let t = (FRAC_PI_4 * i as f32 / k.max(1) as f32).tan();
144 out.push(-inner[axis] - radius * t);
145 }
146 for i in 0..=k {
147 let t = (FRAC_PI_4 * i as f32 / k.max(1) as f32).tan();
148 out.push(inner[axis] + radius * t);
149 }
150 out.dedup_by(|a, b| (*a - *b).abs() <= 1e-7);
151 out
152 };
153 let mut builder = Builder::new();
154 for face in 0..6 {
155 let normal_axis = face / 2;
156 let sign = if face % 2 == 0 { 1.0 } else { -1.0 };
157 let u_axis = (normal_axis + 1) % 3;
158 let v_axis = (normal_axis + 2) % 3;
159 let us = axis_samples(u_axis);
160 let vs = axis_samples(v_axis);
161 let base = |i: usize, j: usize, builder: &mut Builder| -> u32 {
162 let mut p = [0.0f32; 3];
163 p[normal_axis] = sign * half[normal_axis];
164 p[u_axis] = us[i];
165 p[v_axis] = vs[j];
166 let q = [
167 p[0].clamp(-inner[0], inner[0]),
168 p[1].clamp(-inner[1], inner[1]),
169 p[2].clamp(-inner[2], inner[2]),
170 ];
171 let mut n = [0.0f32; 3];
172 n[normal_axis] = sign;
173 let d = sub(p, q);
174 let normal = if length(d) > 1e-9 { normalize(d) } else { n };
175 let position = add(q, scale(normal, radius));
176 let uv = [
177 (us[i] / half[u_axis].max(1e-6)) * 0.5 + 0.5,
178 (vs[j] / half[v_axis].max(1e-6)) * 0.5 + 0.5,
179 ];
180 builder.vertex(position, normal, uv)
181 };
182 let mut grid = vec![vec![0u32; vs.len()]; us.len()];
183 for (i, row) in grid.iter_mut().enumerate() {
184 for (j, slot) in row.iter_mut().enumerate() {
185 *slot = base(i, j, &mut builder);
186 }
187 }
188 for i in 0..us.len() - 1 {
189 for j in 0..vs.len() - 1 {
190 let (a, b, c, d) = (
191 grid[i][j],
192 grid[i + 1][j],
193 grid[i + 1][j + 1],
194 grid[i][j + 1],
195 );
196 builder.tri(a, b, c);
197 builder.tri(a, c, d);
198 }
199 }
200 }
201 weld(builder.build(), (error * 0.01).max(1e-6))
202}
203
204fn revolve(
205 a: [f32; 3],
206 axis: [f32; 3],
207 profile: &[([f32; 2], [f32; 2])],
208 around: usize,
209 error: f32,
210) -> Mesh {
211 let (x, y) = basis(axis);
212 let mut builder = Builder::new();
213 let mut rings = Vec::with_capacity(profile.len());
214 for (k, ((s, rho), (ns, nrho))) in profile
215 .iter()
216 .map(|(p, n)| ((p[0], p[1]), (n[0], n[1])))
217 .enumerate()
218 {
219 let mut ring = Vec::with_capacity(around + 1);
220 for i in 0..=around {
221 let phi = TAU * i as f32 / around as f32;
222 let radial = add(scale(x, phi.cos()), scale(y, phi.sin()));
223 let position = add(add(a, scale(axis, s)), scale(radial, rho));
224 let normal = add(scale(axis, ns), scale(radial, nrho));
225 let uv = [
226 i as f32 / around as f32,
227 k as f32 / (profile.len() - 1) as f32,
228 ];
229 ring.push(builder.vertex(position, normal, uv));
230 }
231 rings.push(ring);
232 }
233 for k in 0..rings.len() - 1 {
234 for i in 0..around {
235 let (p, q, r, t) = (
236 rings[k][i],
237 rings[k][i + 1],
238 rings[k + 1][i + 1],
239 rings[k + 1][i],
240 );
241 builder.tri(p, q, r);
242 builder.tri(p, r, t);
243 }
244 }
245 weld(builder.build(), (error * 0.01).max(1e-6))
246}
247
248fn round_cone_mesh(a: [f32; 3], b: [f32; 3], r1: f32, r2: f32, step: f32, error: f32) -> Mesh {
249 let ab = sub(b, a);
250 let h = length(ab);
251 if h <= 1e-6 || (r1 - r2).abs() >= h {
252 let (center, radius) = if r1 >= r2 { (a, r1) } else { (b, r2) };
253 let mut mesh = ellipsoid_mesh([radius; 3], step, error);
254 for p in &mut mesh.positions {
255 *p = add(*p, center);
256 }
257 return Mesh::new(
258 mesh.positions,
259 mesh.normals,
260 mesh.tangents,
261 mesh.uvs,
262 mesh.indices,
263 );
264 }
265 let axis = scale(ab, 1.0 / h);
266 let beta = ((r1 - r2) / h).asin();
267 let tangent = FRAC_PI_2 - beta;
268 let mut profile = Vec::new();
269 let south = segments(r1, step, PI - tangent, 2);
270 for i in 0..=south {
271 let g = PI - (PI - tangent) * i as f32 / south as f32;
272 profile.push(([r1 * g.cos(), r1 * g.sin()], [g.cos(), g.sin()]));
273 }
274 let north = segments(r2, step, tangent, 2);
275 for i in 0..=north {
276 let g = tangent - tangent * i as f32 / north as f32;
277 profile.push(([h + r2 * g.cos(), r2 * g.sin()], [g.cos(), g.sin()]));
278 }
279 let around = segments(r1.max(r2), step, TAU, 8);
280 revolve(a, axis, &profile, around, error)
281}
282
283fn ellipsoid_mesh(radii: [f32; 3], step: f32, error: f32) -> Mesh {
284 let big = radii[0].max(radii[1]).max(radii[2]);
285 let around = segments(big, step, TAU, 8);
286 let rings = segments(big, step, PI, 4);
287 let mut builder = Builder::new();
288 let mut grid = Vec::with_capacity(rings + 1);
289 for k in 0..=rings {
290 let theta = PI * k as f32 / rings as f32;
291 let mut row = Vec::with_capacity(around + 1);
292 for i in 0..=around {
293 let phi = TAU * i as f32 / around as f32;
294 let unit = [
295 theta.sin() * phi.cos(),
296 theta.sin() * phi.sin(),
297 theta.cos(),
298 ];
299 let position = [unit[0] * radii[0], unit[1] * radii[1], unit[2] * radii[2]];
300 let normal = [
301 position[0] / (radii[0] * radii[0]),
302 position[1] / (radii[1] * radii[1]),
303 position[2] / (radii[2] * radii[2]),
304 ];
305 let uv = [i as f32 / around as f32, k as f32 / rings as f32];
306 row.push(builder.vertex(position, normal, uv));
307 }
308 grid.push(row);
309 }
310 for k in 0..rings {
311 for i in 0..around {
312 let (p, q, r, t) = (
313 grid[k][i],
314 grid[k][i + 1],
315 grid[k + 1][i + 1],
316 grid[k + 1][i],
317 );
318 builder.tri(p, q, r);
319 builder.tri(p, r, t);
320 }
321 }
322 weld(builder.build(), (error * 0.01).max(1e-6))
323}
324
325fn slab_mesh(
326 half_length: f32,
327 radius: f32,
328 height: f32,
329 fillet: f32,
330 step: f32,
331 error: f32,
332) -> Mesh {
333 let fillet = fillet.min(height * 0.5).min(radius).max(0.0);
334 let core = radius - fillet;
335 let straight = (half_length - radius).max(0.0);
336 let end = segments(radius, step, PI, 4);
337 let mut outline: Vec<([f32; 2], [f32; 2])> = Vec::new();
338 for (cx, from) in [(straight, -FRAC_PI_2), (-straight, FRAC_PI_2)] {
339 for i in 0..=end {
340 let angle = from + PI * i as f32 / end as f32;
341 let n = [angle.cos(), angle.sin()];
342 outline.push(([cx + core * n[0], core * n[1]], n));
343 }
344 }
345 let bend = if fillet > 0.0 {
346 segments(fillet, step, FRAC_PI_2, 1)
347 } else {
348 0
349 };
350 let mut profile: Vec<(f32, f32, f32, f32)> = Vec::new();
351 for i in 0..=bend {
352 let g = FRAC_PI_2 * i as f32 / bend.max(1) as f32;
353 profile.push((
354 fillet * g.sin(),
355 height - fillet + fillet * g.cos(),
356 g.sin(),
357 g.cos(),
358 ));
359 }
360 for i in 0..=bend {
361 let g = FRAC_PI_2 * i as f32 / bend.max(1) as f32;
362 profile.push((
363 fillet * g.cos(),
364 fillet - fillet * g.sin(),
365 g.cos(),
366 -g.sin(),
367 ));
368 }
369 let mut builder = Builder::new();
370 let count = outline.len();
371 let mut grid = Vec::with_capacity(profile.len());
372 for (k, &(o, z, nn, nz)) in profile.iter().enumerate() {
373 let mut row = Vec::with_capacity(count);
374 for (j, (p, n)) in outline.iter().enumerate() {
375 let position = [p[0] + n[0] * o, p[1] + n[1] * o, z];
376 let normal = [n[0] * nn, n[1] * nn, nz];
377 let uv = [
378 j as f32 / count as f32,
379 k as f32 / (profile.len() - 1) as f32,
380 ];
381 row.push(builder.vertex(position, normal, uv));
382 }
383 grid.push(row);
384 }
385 for k in 0..grid.len() - 1 {
386 for j in 0..count {
387 let jn = (j + 1) % count;
388 let (p, q, r, t) = (grid[k][j], grid[k][jn], grid[k + 1][jn], grid[k + 1][j]);
389 builder.tri(p, q, r);
390 builder.tri(p, r, t);
391 }
392 }
393 for (z, nz, row) in [(height, 1.0f32, 0usize), (0.0, -1.0, grid.len() - 1)] {
394 let center = builder.vertex([0.0, 0.0, z], [0.0, 0.0, nz], [0.5, 0.5]);
395 for j in 0..count {
396 let jn = (j + 1) % count;
397 builder.tri(center, grid[row][j], grid[row][jn]);
398 }
399 }
400 weld(builder.build(), (error * 0.01).max(1e-6))
401}
402
403fn torus_mesh(major: f32, minor: f32, step: f32, error: f32) -> Mesh {
404 let around = segments(major + minor, step, TAU, 8);
405 let tube = segments(minor, step, TAU, 6);
406 let mut builder = Builder::new();
407 let mut grid = Vec::with_capacity(around + 1);
408 for i in 0..=around {
409 let phi = TAU * i as f32 / around as f32;
410 let mut row = Vec::with_capacity(tube + 1);
411 for j in 0..=tube {
412 let theta = TAU * j as f32 / tube as f32;
413 let ring = major + minor * theta.cos();
414 let position = [ring * phi.cos(), ring * phi.sin(), minor * theta.sin()];
415 let normal = [
416 theta.cos() * phi.cos(),
417 theta.cos() * phi.sin(),
418 theta.sin(),
419 ];
420 let uv = [i as f32 / around as f32, j as f32 / tube as f32];
421 row.push(builder.vertex(position, normal, uv));
422 }
423 grid.push(row);
424 }
425 for i in 0..around {
426 for j in 0..tube {
427 let (p, q, r, t) = (
428 grid[i][j],
429 grid[i + 1][j],
430 grid[i + 1][j + 1],
431 grid[i][j + 1],
432 );
433 builder.tri(p, q, r);
434 builder.tri(p, r, t);
435 }
436 }
437 weld(builder.build(), (error * 0.01).max(1e-6))
438}