|
1 | 1 | import { tgpu } from 'typegpu'; |
2 | | -import { f32, vec3f } from 'typegpu/data'; |
3 | | -import { abs, distance, dot, length, max, min, saturate } from 'typegpu/std'; |
| 2 | +import { f32, type v2f, type v3f, vec2f, vec3f } from 'typegpu/data'; |
| 3 | +import { |
| 4 | + abs, |
| 5 | + clamp, |
| 6 | + cross, |
| 7 | + distance, |
| 8 | + dot, |
| 9 | + length, |
| 10 | + max, |
| 11 | + min, |
| 12 | + saturate, |
| 13 | + select, |
| 14 | + sign, |
| 15 | + sqrt, |
| 16 | +} from 'typegpu/std'; |
4 | 17 |
|
5 | 18 | /** |
6 | 19 | * Signed distance function for a sphere |
@@ -115,3 +128,110 @@ export const sdCapsule = tgpu.fn( |
115 | 128 | const h = saturate(dot(pa, ba) / dot(ba, ba)); |
116 | 129 | return distance(pa, ba * h) - radius; |
117 | 130 | }); |
| 131 | + |
| 132 | +/** |
| 133 | + * Squared length of a vector. |
| 134 | + * @param a Vector whose squared length to compute |
| 135 | + */ |
| 136 | +const dot2 = (a: v2f | v3f) => { |
| 137 | + 'use gpu'; |
| 138 | + return dot(a, a); |
| 139 | +}; |
| 140 | + |
| 141 | +/** |
| 142 | + * Unsigned distance function for a filled, zero-thickness triangle in 3D space. |
| 143 | + * The vertices must be distinct and non-collinear, and may use either winding order. |
| 144 | + * |
| 145 | + * @param p Point to evaluate, in the same coordinate space as the vertices |
| 146 | + * @param a First vertex of the triangle |
| 147 | + * @param b Second vertex of the triangle |
| 148 | + * @param c Third vertex of the triangle |
| 149 | + * @returns Distance to the nearest point on the triangle, including its edges; |
| 150 | + * zero on the triangle and positive on either side of its plane |
| 151 | + */ |
| 152 | +export const sdTriangle3d = (p: v3f, a: v3f, b: v3f, c: v3f) => { |
| 153 | + 'use gpu'; |
| 154 | + const ba = b - a; |
| 155 | + const pa = p - a; |
| 156 | + const cb = c - b; |
| 157 | + const pb = p - b; |
| 158 | + const ac = a - c; |
| 159 | + const pc = p - c; |
| 160 | + const nor = cross(ba, ac); |
| 161 | + |
| 162 | + const cond = |
| 163 | + sign(dot(cross(ba, nor), pa)) + sign(dot(cross(cb, nor), pb)) + sign(dot(cross(ac, nor), pc)) < |
| 164 | + 2; |
| 165 | + |
| 166 | + return sqrt( |
| 167 | + select( |
| 168 | + // false |
| 169 | + (dot(nor, pa) * dot(nor, pa)) / dot2(nor), |
| 170 | + // true |
| 171 | + min( |
| 172 | + min( |
| 173 | + dot2(ba * saturate(dot(ba, pa) / dot2(ba)) - pa), |
| 174 | + dot2(cb * saturate(dot(cb, pb) / dot2(cb)) - pb), |
| 175 | + ), |
| 176 | + dot2(ac * saturate(dot(ac, pc) / dot2(ac)) - pc), |
| 177 | + ), |
| 178 | + cond, |
| 179 | + ), |
| 180 | + ); |
| 181 | +}; |
| 182 | + |
| 183 | +/** |
| 184 | + * Signed distance function for a solid cylinder centered at the origin, along the Y axis, |
| 185 | + * with flat caps at `y = -h` and `y = h`. |
| 186 | + * |
| 187 | + * @param p Point to evaluate, relative to the cylinder's center |
| 188 | + * @param r Radius of the circular cross-section in the XZ plane (non-negative) |
| 189 | + * @param h Half-height of the cylinder; its full height is `2 * h` (non-negative) |
| 190 | + * @returns Negative inside the cylinder, zero on its surface, positive outside |
| 191 | + */ |
| 192 | +export const sdCappedCylinder = tgpu.fn( |
| 193 | + [vec3f, f32, f32], |
| 194 | + f32, |
| 195 | +)((p, r, h) => { |
| 196 | + 'use gpu'; |
| 197 | + const dd = abs(vec2f(length(p.xz), p.y)) - vec2f(r, h); |
| 198 | + return min(max(dd.x, dd.y), 0.0) + length(max(dd, vec2f())); |
| 199 | +}); |
| 200 | + |
| 201 | +/** |
| 202 | + * Computes `a.x * b.x - a.y * b.y` for the rhombus edge projection. |
| 203 | + * @param a First vector |
| 204 | + * @param b Second vector |
| 205 | + */ |
| 206 | +function ndot(a: v2f, b: v2f) { |
| 207 | + 'use gpu'; |
| 208 | + return a.x * b.x - a.y * b.y; |
| 209 | +} |
| 210 | + |
| 211 | +/** |
| 212 | + * Signed distance function for a rhombus in the XZ plane, extruded along Y and centered |
| 213 | + * at the origin. Rounding expands the 2D cross-section; the caps at `y = +/-h` stay flat. |
| 214 | + * |
| 215 | + * @param p Point to evaluate, relative to the shape's center |
| 216 | + * @param la Half-length of the unrounded rhombus's diagonal along X (positive) |
| 217 | + * @param lb Half-length of the unrounded rhombus's diagonal along Z (positive) |
| 218 | + * @param h Half-height of the extrusion along Y (non-negative) |
| 219 | + * @param ra Outward rounding radius in the XZ plane (non-negative). Zero gives a sharp |
| 220 | + * rhombus with vertices at `(x, z) = (+/-la, 0)` and `(0, +/-lb)`; rounding increases |
| 221 | + * the X and Z half-extents to `la + ra` and `lb + ra` |
| 222 | + * @returns Negative inside the solid, zero on its surface, positive outside |
| 223 | + */ |
| 224 | +export const sdRhombus = tgpu.fn( |
| 225 | + [vec3f, f32, f32, f32, f32], |
| 226 | + f32, |
| 227 | +)((p, la, lb, h, ra) => { |
| 228 | + 'use gpu'; |
| 229 | + const ap = abs(p); |
| 230 | + const b = vec2f(la, lb); |
| 231 | + const f = clamp(ndot(b, b - ap.xz * 2) / dot2(b), -1, 1); |
| 232 | + const q = vec2f( |
| 233 | + length(ap.xz - b * vec2f(1 - f, 1 + f) * 0.5) * sign(ap.x * b.y + ap.z * b.x - b.x * b.y) - ra, |
| 234 | + ap.y - h, |
| 235 | + ); |
| 236 | + return min(max(q.x, q.y), 0.0) + length(max(q, vec2f())); |
| 237 | +}); |
0 commit comments