2
\$\begingroup\$

I am creating cubic bezier curves using this code from another question I asked. I would like to know how I can express Logarithmic and Arithmetic spirals as segmented cubic bezier curves.

\$\endgroup\$
  • 1
    \$\begingroup\$ There's nothing particularly special about logarithmic or arithmetic spirals that require doing something radically different when approximating them as Bézier splines. The steps are exactly the same: 1) choose how far along the curve you want to begin/end your segment (this is a judgement call you make, trading off between simple/sparse and dense/accurate) 2) sample the source curve at those inputs to get the start & end anchor points for this segment 3) sample the source curve's derivative at those inputs to get the start & end control points. Where in this process do you need help? \$\endgroup\$ – DMGregory Nov 5 '18 at 20:19
  • \$\begingroup\$ @DMGregory 1) From my understanding, this is based on an initial single cubic segment, from 2) and 3) I don’t quite understand what you mean by sampling the source curve. Which inputs are you referring to? \$\endgroup\$ – hutfelt Nov 5 '18 at 20:56
  • \$\begingroup\$ You can think of either curve as a parametric functions eg. Vector3 ArchimedeanSpiral(Vector3 center, float a, float b, float theta) { return center + (b * theta) * Quaternion.Euler(0, 0, theta + a) * Vector3.right; } - sampling the curve means evaluating this parametric function for your chosen curve (center, a, b) and position on that curve (theta). I don't understand what you mean by an initial cubic segment for part 1) - how are you choosing that first segment at the moment? \$\endgroup\$ – DMGregory Nov 5 '18 at 21:03
  • \$\begingroup\$ @DMGregory I thought "choose how far along the curve you want to begin/end your segment" meant you needed an initial cubic curve that would form the basis for the trade offs for the whole spiral. Not sure how it would be chosen or how the trade offs will be determined though \$\endgroup\$ – hutfelt Nov 5 '18 at 21:14
  • \$\begingroup\$ That's a judgement call on your part. You could make one cubic segment for each quarter-turn of the spiral, for instance. Or you could divide each turn of the spiral into 50 segments. Or you could use some kind of adaptive spacing, with fewer segments on some parts of the curve and more segments on other parts. This is something usually better-handled in a problem-solving context though, eg. "I tried spacing my segments like this... but then the result is too dense / too inaccurate in these cases... how can I improve my choice of spacing?" \$\endgroup\$ – DMGregory Nov 5 '18 at 21:32
2
\$\begingroup\$

Here I'll use a utility function to produce points on an Archimedean spiral at a particular angle, along with the derivative of the function at that point. To reduce the number of parameters, I'll use the Transform of our object to apply translation/scaling/rotation of the curve. You can also use a matrix or add these parameters as additional arguments.

Vector3 ArchimedeanSpiral(float spacing, float angle, out Vector3 derivative) {
    float radians = angle * Mathf.Deg2Rad;
    Vector3 direction = new Vector3(Mathf.Cos(radians), Mathf.Sin(radians), 0); 
    Vector3 perpendicular = new Vector3(-direction.y, direction.x, 0);

    float b = spacing/360f;

    derivative = transform.TransformVector(b * (direction + radians * perpendicular));
    return transform.TransformPoint(b * angle * direction);
}

Now we can choose points on a Bezier spline approximating this spiral like so...

Vector3[] ArchimedeanSpiralBezier(float spacing, float startAngle, float endAngle) {
    // Chop the spiral into segments, roughly this many degrees apiece.
    float interval = 90f;
    float angleSpan = endAngle - startAngle;
    int segmentCount = Mathf.Max(Mathf.RoundToInt(Mathf.Abs(angleSpan / interval)), 2);
    Vector3[] bezierPoints = new Vector3[segmentCount * 3 + 1];
    float step = angleSpan / segmentCount;

    // 1.05f is a fudge factor determined empirically,
    // to "puff out" the curve so it doesn't cut the corner too shallowly.
    float derivativeScale = step * 1.05f/3f;
    // The division by 3 is because in an nth order Bezier curve, 
    // the velocity at an anchor point is n times the vector to the next point.

    // Initialize the first anchor point on the spline.
    Vector3 derivative;
    bezierPoints[0] = ArchimedeanSpiral(spacing, startAngle, out derivative);
    derivative *= derivativeScale;

    for(int i = 1; i <= segmentCount; i++) {
        int end = i * 3;

        // The next control point is the previous anchor plus the velocity.
        bezierPoints[end - 2] = bezierPoints[end - 3] + derivative;

        // Place the next anchor point.
        float angle = startAngle + i * step;            
        bezierPoints[end] = ArchimedeanSpiral(spacing, angle, out derivative);
        derivative *= derivativeScale;

        // Backtrack from this anchor point by the velocity to place its control point.
        bezierPoints[end - 1] = bezierPoints[end] - derivative;
    }

    return bezierPoints;
}

For a logarithmic spiral, the same principles apply, just with a different utility function:

Vector3 LogarithmicSpiral(float spacing, float angle, out Vector3 derivative) {
    float radians = angle * Mathf.Deg2Rad;
    Vector3 direction = new Vector3(Mathf.Cos(radians), Mathf.Sin(radians), 0); 
    Vector3 perpendicular = new Vector3(-direction.y, direction.x, 0);

    float b = spacing/360f;
    float radius = Mathf.Exp(b * angle);

    derivative = transform.TransformVector(radius * (b * direction + Mathf.Deg2Rad * perpendicular));
    return transform.TransformPoint(radius * direction);
}
\$\endgroup\$
  • \$\begingroup\$ This is really incredible. Please pardon me for not replying earlier. \$\endgroup\$ – hutfelt Nov 13 '18 at 16:26
  • \$\begingroup\$ I'm glad it's useful to you @hutfelt. :) There's nothing particularly incredible about it though - it just follows the normal workflow for drawing cubic Bézier splines: place your anchor points at intervals along the curve, then place the control points to match the direction the curve is travelling at that spot. The choice to use roughly-90 degree increments was just a first guess that turned out to be "good enough" experimentally. So, don't hesitate to dive in and experiment to see what works. \$\endgroup\$ – DMGregory Nov 13 '18 at 17:13
  • \$\begingroup\$ I made sliders for most of the main parameters so I can adjust the spiral in edit mode. \$\endgroup\$ – hutfelt Nov 14 '18 at 12:01

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.