2D SDF(6): Some Curve - OhBonsai/art GitHub Wiki
After understanding the concepts of vector angles, polar coordinates, symmetry, and the Signed Distance Function (SDF) of line segments, the fundamental approaches to common polygons are consistent. This section begins to derive shapes that are comprised of two circles of different sizes.
- shadertoy: https://www.shadertoy.com/view/4cGGWR
Regarding the vesica shape, there are some complex derivations online, but I think you just need to judge the distance from point
float sd_vesica(vec2 P, float r, float d)
{
P = abs(P);
vec2 PB = vec2(-d, 0.0);
float h = sqrt(r*r - d*d);
vec2 PG = vec2(0.0, h);
return min(
distance(P, PB) - r,
distance(P, PG)
);
}
- geobebra: https://www.geogebra.org/classic/vcxefvd7
- shadertoy: https://www.shadertoy.com/view/4cGGWR
Given a circle c1 with the center at the origin and a radius of r1, and another circle c2 with the center at (0, h) and a radius of r2. The key is to find the blue vector v1 in the diagram, v2 is perpendicular to v1, and the angle
Thus, the vector v1 is
and the vector v2 is
Depending on the magnitude of the projection k of the vector
float sdf_uneven_capsule(vec2 p, float r1, float r2, float h) {
p.x = abs(p.x);
float sin_theta = (r1-r2) / h;
float normalize_y = sin_theta * 1.0;
float normalize_x = sqrt(1.0 - (normalize_y * normalize_y));
float cos_theta = normalize_x;
vec2 v2 = vec2(-normalize_y, normalize_x);
vec2 v = vec2(normalize_x, normalize_y);
float k = dot(p, vec2(v2));
// area1
if( k < 0.0 ) return length(p) - r1;
// area3
if( k > cos_theta*h ) return length(p-vec2(0.0,h)) - r2;
// area2
return dot(p, v ) - r1;
}
To draw an egg shape as referenced in the illustration above, with point A's coordinates being
float sdf_mossegg(vec2 P, float r, float d )
{
P.x = abs(P.x);
vec2 B = vec2(-d, 0.);
float hh = r-d;
vec2 D = vec2(0, hh);
if (P.y < 0.) {
return length(P) - hh;
} else {
float t = cross2(P-B, D-B);
if (t < 0.0) {
float tt = sqrt(hh *hh + d * d);
float rr =r - tt;
return distance(P, D) - rr ;
} else {
return distance(P, B) - d - hh;
}
}
}
To draw a moon shape as referenced in the illustration above, with point B's coordinates being
- Circle A, center at
$(0, 0)$ , radius$r_1$ :$x^2 + y^2 = r_1^2$ - Circle B, center at
$(d, 0)$ , radius$r_2$ :$(x - d)^2 + y^2 = r_2^2$
To solve these two equations, start by subtracting one from the other to eliminate
Expanding yields:
Simplify this to a linear equation for (x):
The valid (x) is thus:
Then the value of (x) can be substituted back into either circle's equation to solve for (y). Substituting (x) into the equation for circle A:
Finally, the code is given:
float sdf_moon(vec2 P, float r1, float r2, float d )
{
P.y = abs(P.y);
float a = (r1*r1 - r2*r2 + d*d)/(2.0*d);
float b = sqrt(max(r1*r1-a*a,0.0));
vec2 PointC = vec2(a, b);
vec2 PointB = vec2(d, 0.);
vec2 PointD = vec2(0., 0.);
vec2 VectorCB = PointB - PointC;
vec2 VectorDC = PointC - PointD;
vec2 VectorCP = P - PointC;
vec2 VectorDP = P - PointD;
float t1 = cross2(VectorCB, VectorCP);
float t2 = cross2(VectorDC, VectorDP);
if (t1 > 0.0 && t2 < 0.0) {
return distance(P, PointC);
}
return max(
r2 - distance(P, PointB),
length(P) - r1
);
}
To draw a heart as referenced in the illustration above, the distance outside of the heart can be converted into the SDF of line segment
float sd_heart( in vec2 P, float r )
{
P.x = abs(P.x);
vec2 PointB = vec2(0., r);
vec2 PointA = vec2(r, 0.0);
vec2 PointC = vec2(0.5*r, 0.5*r);
vec2 PointD = vec2(0.25 * r, 0.75 *r );
vec2 VectorBA = PointA - PointB;
vec2 VectorBP = P - PointB;
float t = cross2(VectorBA, VectorBP);
if (t > 0.0) {
return distance(P, PointD) - 0.25 * r * sqrt(2.);
}
float hh = clamp(dot(P, PointC) / dot(PointC, PointC), 0., 1.);
return min(
distance(P, PointB),
length(P-hh*PointC)
) * sign(P.x-P.y);
}