Skip to main content

pfx_geom/
shapes.rs

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}