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.

Vesica

Regarding the vesica shape, there are some complex derivations online, but I think you just need to judge the distance from point $P$ to point $B$ , and the distance from point $P$ to point $G$ . I might have missed something, here is the code:

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)
    );
}

Uneven Capsule

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 $\theta$ of v1 is given by $$\sin(\theta) = \frac{r1 - r2}{h}$$
Thus, the vector v1 is $$(\sin(\theta), \cos(\theta))$$
and the vector v2 is $$(-\cos(\theta), \sin(\theta))$$
Depending on the magnitude of the projection k of the vector $\overrightarrow{OP}$ on v2, you can divide it into 3 regions. Eventually, you get the code:

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;
}

Egg

To draw an egg shape as referenced in the illustration above, with point A's coordinates being $(d, 0)$ and a radius of $r$ . To solve for the SDF of an egg, it is primarily necessary to determine the three points $B$ , $D$ , and $O$ in the illustration as the centers of the circles. Decide whether to choose $B$ or $D$ based on the left and right side of the line $DF$ , and choose $B$ or $O$ based on the sign of the y-coordinate of point $P$ . Finally, the code is given:

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;
        } 
    
    }
    
}

Moon

To draw a moon shape as referenced in the illustration above, with point B's coordinates being $(d, 0)$ and a radius of $r2$ . The central circle has a radius of $r1$ . The black bold part in the illustration depicts $d$ , $r1$ , and $r2$ . For the distance to the moon, it is divided into three regions, which involves calculating the distances from point $P$ to points $B$ , $C$ , and $D$ . Region determination is mainly carried out through vector cross product to judge the direction. The key is to calculate the coordinates of point $C$ . This problem can be translated into solving the equations of two circles: The equations of the two circles are:

  1. Circle A, center at $(0, 0)$ , radius $r_1$ : $x^2 + y^2 = r_1^2$
  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 $y^2$ , producing a relationship for $x$ : $$x^2 - (x - d)^2 = r_1^2 - r_2^2$$
Expanding yields: $$x^2 - (x^2 - 2xd + d^2) = r_1^2 - r_2^2$$
Simplify this to a linear equation for (x): $$2xd - d^2 = r_1^2 - r_2^2$$
The valid (x) is thus: $$x = \frac{r_1^2 - r_2^2 + d^2}{2d}$$
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: $$y^2 = r_1^2 - x^2$$
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
    );
   
}

Heart

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 $EC$ and circle $D$ . The distance inside the heart, on the left side of vector $BC$ , is the distance to the circle, and on the right side, it is the shortest distance to point $B$ or the line segment $EC$ . Finally, the following code is provided:

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);

}
⚠️ **GitHub.com Fallback** ⚠️