3
\$\begingroup\$

From the image below, A is an open bezier curve with it's points positioned to represent a circle. AP3 and AP1 have the same position. From B, How can I move the points (AP1 and AP3 along the bigger circle of radius r whiles the CP's reduce in length as CP1 and CP4 also rotate) till they form a symmetric bezier curve at the center of the bigger circle marked as the red curve?

enter image description here

\$\endgroup\$
  • \$\begingroup\$ What determines the length / rotation of the control point handles in the modified curve? They don't seem to relate in an obvious way to either the inner nor the outer circle. \$\endgroup\$ – DMGregory Feb 8 at 22:55
  • \$\begingroup\$ @DMGregory Interesting question. I haven't been able to find a precise relation between the curve I desire and the circles beyond my above description. All control point handles have the same length so I guess several types of symmetric curves will qualify for my above description, but I think some factor that can the determine the length of the handle as it reduces consistently from a circle to an arc should do. I guess something similar should apply to the rotation. \$\endgroup\$ – Zizo Feb 8 at 23:30
  • \$\begingroup\$ Try this from another angle then: why do you want to transform the curve in this way? What gameplay function does it serve? That may let us identify options that are best suited to that function. \$\endgroup\$ – DMGregory Feb 8 at 23:34
  • \$\begingroup\$ I want a character to be enclosed within the circle or ride on arcs. I thought of using a semi-circle instead of an arc but I feel it will water done the effect I desire. \$\endgroup\$ – Zizo Feb 8 at 23:47
  • 1
    \$\begingroup\$ @DMGregory I thought my earlier suggestion might work where some factor determines how much the handles rotate and reduce in length over the period of transitioning from a circle to an arc whiles preserving symmetry. I know it sounds easier than implementing. \$\endgroup\$ – Zizo Feb 12 at 13:30
3
\$\begingroup\$

To keep things simple & consistent, let's stick with circular arcs throughout.

We'll have our first circle with radius \$r_1\$, shown in red below.

Then we'll use a second radius \$r_2\$ to define the new positions of our endpoints as the curve swings open by an angle \$\alpha\$, shown on the green circle below.

Then we'll draw a third circle, through our moved anchor points \$AP_1\$, \$AP_3\$ and the one we left stationary \$AP_2\$, shown in blue below. This gives us a unique arc to follow.

Diagram of osculating circles labelled with centers & radii

Since we already know how to trace a circular arc with a cubic Bézier curve, once we find this circle we'll be able to draw the arc along it trivially. So I'll focus on finding the parameters of the circle below:

Let's get down to placing the points. For simplicity I'm going to assume that the fixed point is always at the rightmost edge of the red circle, and that the centers & radii of the circles \$ \vec C_1, r_1, \vec C_2,r_2\$ are known, along with the angle \$ \alpha > 0\$ you want to open the initial curve by. (For \$\alpha = 0\$, we just draw the original curve as in your previous question)

Now, we know our top anchor point is at...

$$\vec {AP_1} = \vec C_2 + r_2 \cdot \left(-\cos \alpha, \sin \alpha \right)$$

and our fixed anchor point is at...

$$\vec {AP_2} = \vec C_1 + \left( r_1, 0 \right)$$

Then the midpoint of the line joining these is at...

$$ \vec M = \frac 1 2 \cdot \left( \vec {AP_1} + \vec {AP_2} \right)$$

A perpendicular \$\vec p\$ from this midpoint will point the way to our third center \$\vec C_3\$

$$ \vec p = \left( -M_y, M_x - {AP_2}_x \right)$$

(If \$p_y = 0\$ then we've opened the curve all the way to a straight line, so just draw a straight line from \$AP_1\$ to \$AP_3\$ and you're done)

We can scale this by a factor \$t\$ to reach the horizontal axis and locate our center:

$$t = \frac {{AP_2}_y - M_y} {p_y}$$

$$\vec C_3 = \vec M + t \cdot \vec p$$

$$r_3 = {AP_2}_x - {C_3}_x$$

And now we can find our angle \$\theta\$ with:

$$\theta = atan2(\vec {AP_1} - \vec C_3)$$

(Just note that in many APIs, the first argument of atan2 is y not x, so be sure you put the components in the right order)

Now we have a center (\$\vec C_3\$), a radius (\$r_3\$), a start angle (0), and an end angle (\$\theta\$), so we can draw a cubic Bézier curve along this circular arc using the technique described in my earlier answer.

For the lower half, just flip the y coordinates along the horizontal.


Here's an animation of this method in action:

Animation showing inner circle opening out to a line then re-closing on the opposite side

With a little attention to our start points, we can even make this work starting from a less-than-full circle:

Animation showing a partial circular arc opening out in the same way

Here's the code I used:

// Buffer for our final spline's anchor & control points.
Vector2[] _bezierPoints = new Vector2[7];

// How much of the inner circle should we start with?
[Range(0,180)]
public float circleCoverageDegrees= 180f;

// Size & center of our inner circle (red).
public float firstRadius = 1.0f;
public Vector2 firstCenter;

// Size & center of our second circle (green).
public float secondRadius = 2.0f;
Vector2 _secondCenter;  // Computed from radius & coverage.

// How far should we swing the anchor points out around the green circle?
[Range(0, 180)]
public float swingOutDegrees = 0f;

// Size & center of our third circle (blue).
Vector2 _thirdCenter;
float _thirdRadius;

// Used to mark the line from the second circle's center
// to our inner anchor points before we've swung them outward.
Vector2 _secondToInnerStart;

void OnValidate()
{
    if (secondRadius < firstRadius)
        secondRadius = firstRadius;

    // Place the anchor points of the original circle.
    // (For now, I assume its center is (0, 0), and add it as an offset later)
    Vector2 innerArcStart = PointOnCircle(firstRadius, circleCoverageDegrees);
    Vector2 fixedAnchor = PointOnCircle(firstRadius, 0f);

    // Place the second center so the green circle passes through the endpoints.
    float horizontalDeviation = Mathf.Sqrt(secondRadius * secondRadius - innerArcStart.y * innerArcStart.y);
    _secondCenter = new Vector2(innerArcStart.x + horizontalDeviation, 0f);

    _secondToInnerStart = innerArcStart - _secondCenter;

    float startDegrees = Mathf.Approximately(_secondToInnerStart.x, 0) ? 90f 
                      : Mathf.Rad2Deg * Mathf.Atan(-_secondToInnerStart.y / _secondToInnerStart.x);

    // Swing out our anchor points along the green circle.
    Vector2 finalArcStart = _secondCenter + PointOnCircle(secondRadius, 180 - startDegrees - swingOutDegrees);      

    // Find the center of the blue circle joining these anchors.
    Vector2 midpoint = (finalArcStart + fixedAnchor) / 2f;
    Vector2 perpendicular = new Vector2(-midpoint.y, midpoint.x - fixedAnchor.x);

    // If they're in a vertical line, abort and just draw a straight line.
    if(Mathf.Approximately(perpendicular.y, 0f)) {
        _thirdRadius = float.PositiveInfinity;

        _bezierPoints[0] = firstCenter + finalArcStart;
        _bezierPoints[3] = firstCenter + fixedAnchor;
        _bezierPoints[6] = 2 * firstCenter - finalArcStart;

        _bezierPoints[1] = Vector2.Lerp(_bezierPoints[0], _bezierPoints[3], 1f / 3f);
        _bezierPoints[2] = Vector2.Lerp(_bezierPoints[0], _bezierPoints[3], 2f / 3f);
        _bezierPoints[4] = Vector2.Lerp(_bezierPoints[3], _bezierPoints[6], 1f / 3f);
        _bezierPoints[5] = Vector2.Lerp(_bezierPoints[3], _bezierPoints[6], 2f / 3f);
        return;
    }

    // Phew, we have a non-infinite circle! Place its center & radius.
    float t = -midpoint.y / perpendicular.y;

    _thirdCenter = new Vector2(midpoint.x + t * perpendicular.x, 0f);
    _thirdRadius = Mathf.Abs(_thirdCenter.x - fixedAnchor.x);

    // Find the angle of the endpoints around this blue circle.
    Vector2 thirdToFinalStart = finalArcStart - _thirdCenter;
    float angle = Mathf.Rad2Deg * Mathf.Atan2(thirdToFinalStart.y, thirdToFinalStart.x);

    // Handle reversing concavity correctly.
    float pivot = _thirdCenter.x < fixedAnchor.x ? 0 : 180;

    // Populate our Bezier curve buffers using code from previous answer.
    BezierCircle(_thirdCenter, _thirdRadius, angle, pivot, _bezierPoints, 0);
    BezierCircle(_thirdCenter, _thirdRadius, pivot, 2 * pivot - angle, _bezierPoints, 3);
}

// Convenience method for plotting a point in polar coordinates.
static Vector2 PointOnCircle( float radius, float angleDegrees) {
    float angleRadians = angleDegrees * Mathf.Deg2Rad;
    return radius * new Vector2(Mathf.Cos(angleRadians), Mathf.Sin(angleRadians));
}
\$\endgroup\$
  • \$\begingroup\$ Thanks so much. This is precisely what I need. I've realized that I'm having issues with geometry in general and I plan to do stuff even more complex than this. I would really be grateful if you could recommend some material, whether books, video tutorials or free online courses that touch on similar but related aspects of bezier curves, circles and ellipse transformation and manipulation. Thanks again. \$\endgroup\$ – Zizo Feb 15 at 19:10
  • 1
    \$\begingroup\$ All I really use on Bézier curves is what's in the Wikipedia article, or occasionally De Casteljau's algorithm. Some grounding in calculus helps to be able to understand the relationship between the control handles and velocity / acceleration, which lets you do some cool things. For basic geometry / vector algebra, maybe Khan Academy? \$\endgroup\$ – DMGregory Feb 15 at 21:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.