1#![allow(missing_docs, clippy::manual_clamp)]
2
3use super::Material;
4use super::SignedDistance;
5use macaw::*;
6#[cfg(target_arch = "spirv")]
7use num_traits::Float;
8
9#[inline]
10fn square_vec3(v: Vec3) -> f32 {
11 v.dot(v)
12}
13
14#[inline]
15fn hypot(v: Vec2) -> f32 {
16 #[cfg(target_arch = "spirv")]
17 let k = v.length();
18 #[cfg(not(target_arch = "spirv"))]
19 let k = v.x.hypot(v.y);
20 k
21}
22
23#[inline]
24pub fn sd_plane<T: SignedDistance>(pos: Vec3, plane: Vec4) -> T {
25 T::new_with_distance(Material::default(), pos.dot(plane.truncate()) + plane.w)
26}
27
28#[inline]
29pub fn sd_sphere<T: SignedDistance>(pos: Vec3, center: Vec3, radius: f32) -> T {
30 T::new_with_distance(Material::default(), (pos - center).length() - radius)
31}
32
33#[inline]
34pub fn sd_rounded_box<T: SignedDistance>(pos: Vec3, half_size: Vec3, rounding_radius: f32) -> T {
35 let q = pos.abs() - half_size + Vec3::splat(rounding_radius);
36 let dist = q.max(Vec3::splat(0.0)).length() + q.x.max(q.y.max(q.z)).min(0.0) - rounding_radius;
37 T::new_with_distance(Material::default(), dist)
38}
39
40#[inline]
41pub fn sd_torus<T: SignedDistance>(pos: Vec3, big_r: f32, small_r: f32) -> T {
42 let q = Vec2::new(hypot(pos.xz()) - big_r, pos.y);
43 let dist = q.length() - small_r;
44 T::new_with_distance(Material::default(), dist)
45}
46
47#[inline]
48pub fn sd_torus_sector<T: SignedDistance>(
49 mut pos: Vec3,
50 big_r: f32,
51 small_r: f32,
52 sin_cos_half_angle: (f32, f32),
53) -> T {
54 pos.x = pos.x.abs();
55 let k = if sin_cos_half_angle.1 * pos.x > sin_cos_half_angle.0 * pos.z {
56 pos.x * sin_cos_half_angle.0 + pos.z * sin_cos_half_angle.1
57 } else {
58 hypot(pos.xz())
59 };
60 let dist = (pos.dot(pos) + big_r.powi(2) - 2.0 * big_r * k)
61 .max(0.0)
62 .sqrt()
63 - small_r;
64 T::new_with_distance(Material::default(), dist)
65}
66
67#[inline]
68pub fn sd_biconvex_lens<T: SignedDistance>(
69 pos: Vec3,
70 lower_sagitta: f32,
71 upper_sagitta: f32,
72 chord: f32,
73) -> T {
74 let chord_radius = chord / 2.0;
75 let lower_radius = (chord_radius.powi(2) + lower_sagitta.powi(2)) / (2.0 * lower_sagitta);
76 let upper_radius = (chord_radius.powi(2) + upper_sagitta.powi(2)) / (2.0 * upper_sagitta);
77 let lower_center = Vec3::new(0.0, lower_radius - lower_sagitta, 0.0);
78 let upper_center = Vec3::new(0.0, -(upper_radius - upper_sagitta), 0.0);
79 sd_op_intersect(
80 sd_sphere(pos, lower_center, lower_radius),
81 sd_sphere(pos, upper_center, upper_radius),
82 )
83}
84
85#[inline]
86pub fn sd_capsule<T: SignedDistance>(pos: Vec3, points: &[Vec3; 2], radius: f32) -> T {
87 let pa = pos - points[0];
88 let ba = points[1] - points[0];
89 let h = (pa.dot(ba) / ba.dot(ba)).clamp(0.0, 1.0);
90 let distance = (pa - ba * h).length() - radius;
91 T::new_with_distance(Material::default(), distance)
92}
93
94#[inline]
95pub fn sd_rounded_cylinder_f(
96 pos: Vec3,
97 cylinder_radius: f32,
98 half_height: f32,
99 rounding_radius: f32,
100) -> f32 {
101 let d = Vec2::new(
102 hypot(pos.xz()) - cylinder_radius + rounding_radius,
103 pos.y.abs() - half_height + rounding_radius,
104 );
105 d.x.max(d.y).min(0.0) + d.max(Vec2::ZERO).length() - rounding_radius
106}
107
108#[inline]
109pub fn sd_rounded_cylinder<T: SignedDistance>(
110 pos: Vec3,
111 cylinder_radius: f32,
112 half_height: f32,
113 rounding_radius: f32,
114) -> T {
115 T::new_with_distance(
116 Material::default(),
117 sd_rounded_cylinder_f(pos, cylinder_radius, half_height, rounding_radius),
118 )
119}
120
121#[allow(clippy::many_single_char_names)]
122#[inline]
123pub fn sd_tapered_capsule_f(pos: Vec3, p: &[Vec3; 2], r: [f32; 2]) -> f32 {
124 let ba = p[1] - p[0];
128 let l2 = ba.dot(ba);
129 let rr = r[0] - r[1];
130 let a2 = l2 - rr * rr;
131 let il2 = 1.0 / l2;
132
133 let pa = pos - p[0];
135 let y = pa.dot(ba);
136 let z = y - l2;
137 let x2 = square_vec3(pa * l2 - ba * y);
138 let y2 = y * y * l2;
139 let z2 = z * z * l2;
140
141 let k = rr.signum() * rr * rr * x2;
143 if z.signum() * a2 * z2 > k {
144 (x2 + z2).sqrt() * il2 - r[1]
145 } else if y.signum() * a2 * y2 < k {
146 (x2 + y2).sqrt() * il2 - r[0]
147 } else {
148 (y * rr + (x2 * a2 * il2).sqrt()) * il2 - r[0]
149 }
150}
151
152#[inline]
153pub fn sd_tapered_capsule<T: SignedDistance>(pos: Vec3, p: &[Vec3; 2], r: [f32; 2]) -> T {
154 T::new_with_distance(Material::default(), sd_tapered_capsule_f(pos, p, r))
155}
156
157#[allow(clippy::many_single_char_names)]
159#[inline]
160pub fn sd_cone_f(p: Vec3, r: f32, h: f32) -> f32 {
161 let q = vec2(r, h);
162 let w = vec2(hypot(p.xz()), h - p.y);
163 let a = w - q * (w.dot(q) / q.dot(q)).clamp(0.0, 1.0);
164 let b = w - vec2(r * (w.x / r).clamp(0.0, 1.0), h);
165 let d = a.dot(a).min(b.dot(b));
166 let s = (w.x * h - w.y * r).max(w.y - h);
167 d.sqrt() * s.signum()
168}
169
170#[inline]
171pub fn sd_cone<T: SignedDistance>(pos: Vec3, r: f32, h: f32) -> T {
172 T::new_with_distance(Material::default(), sd_cone_f(pos, r, h))
173}
174
175#[inline]
176pub fn sd_material<T: SignedDistance>(sd: T, material: Material) -> T {
177 T::new_with_distance(material, sd.distance())
178}
179
180#[inline]
181pub fn sd_op_union<T: SignedDistance>(d1: T, d2: T) -> T {
182 if d1.distance() < d2.distance() {
183 d1
184 } else {
185 d2
186 }
187}
188
189#[inline]
190pub fn sd_op_subtract<T: SignedDistance>(d1: T, d2: T) -> T {
191 let neg_distance1 = -d1.distance();
192 let distance2 = d2.distance();
193 if neg_distance1 > distance2 {
194 d1.copy_with_distance(neg_distance1)
195 } else {
196 d2
197 }
198}
199
200#[inline]
201pub fn sd_op_intersect<T: SignedDistance>(d1: T, d2: T) -> T {
202 if d1.distance() > d2.distance() {
203 d1
204 } else {
205 d2
206 }
207}
208
209#[inline]
210pub fn sd_op_union_smooth<T: SignedDistance>(d1: T, d2: T, size: f32) -> T {
211 let h = 0.5 + 0.5 * (d2.distance() - d1.distance()) / size;
212 let h = h.clamp(0.0, 1.0);
213
214 let new_d = d2.lerp(&d1, h);
215
216 let distance = new_d.distance() - size * h * (1.0 - h);
217
218 new_d.copy_with_distance(distance)
219}
220
221#[inline]
222pub fn sd_op_subtract_smooth<T: SignedDistance>(d1: T, d2: T, size: f32) -> T {
223 let h = 0.5 - 0.5 * (d2.distance() + d1.distance()) / size;
224 let h = h.clamp(0.0, 1.0);
225
226 let d1 = d1.copy_with_distance(-d1.distance());
227
228 let new_d = d2.lerp(&d1, h);
229
230 let distance = (size * h) * (1.0 - h) + new_d.distance();
231
232 new_d.copy_with_distance(distance)
233}
234
235#[inline]
236pub fn sd_op_intersect_smooth<T: SignedDistance>(d1: T, d2: T, size: f32) -> T {
237 let h = 0.5 - 0.5 * (d2.distance() - d1.distance()) / size;
238 let h = h.clamp(0.0, 1.0);
239
240 let new_d = d2.lerp(&d1, h);
241
242 let distance = (size * h) * (1.0 - h) + new_d.distance();
243
244 new_d.copy_with_distance(distance)
245}