2024-09-04

hints for programming bezier curves

  • quadratic: one control point
  • cubic: two control points
  • alternatives: cubic hermite, centripetal catmull-rom, b-spline

links

example implementations in scheme

using the "de casteljau" algorithm

(define (bezier-curve n . points)
  "number:0..1 #(number ...) ... -> #(number ...)
   get a point for a bezier curve at fractional offset n.
   no limit on the number of control points.
   no limit on the dimension of point vectors.
   at least one point must be given.
   uses the \"de casteljau\" algorithm"
  (if (null? (tail points)) (first points)
    (let ((left n) (right (- 1 n)))
      ; length of points is reduced by one for each recursive call
      (apply bezier-curve n
        (pair-fold-right
          (lambda (a result)
            ; ignore last point
            (if (null? (tail a)) result
              (let ((p1 (first a)) (p2 (second a)))
                (pair
                  (vector-map
                    ; map all point dimensions
                    (lambda (p1-d p2-d)
                      (+ (* right p1-d) (* left p2-d)))
                    p1 p2)
                  result))))
          null points)))))

to generate the first point for example

(bezier-curve 0 (vector 0 10) (vector 10 50) (vector 5 20) (vector 8 20))

optimized for only cubic bezier curves

(define (cubic-bezier-curve n p1 p2 p3 p4)
  "number vector ... -> vector
    return coordinates for one point of a cubic 4-point bezier curve at fractional offset n.
    the intermediate points between p1 and p4 are the control points.
    there is no limit on the dimensions of point vectors"
  (let*
    ( (left n)
      (right (- 1 n))
      (l-square (* left left))
      (r-square (* right right))
      (l-cube (* left l-square))
      (r-cube (* right r-square))
      (l-square (* 3 right l-square))
      (r-square (* 3 left r-square)))
    (apply vector-map
      ; map all point dimensions
      (l (d1 d2 d3 d4)
        (+ (* r-cube d1) (* l-square d2) (* r-square d3) (* l-cube d4)))
      p1 p2 p3 p4)))

example implementation in coffeescript / javascript

cubic_bezier_curve = (n, p1, p2, p3, p4) ->
  # number [number, number] ... -> [number, number]
  # return coordinates for one point of a cubic bezier curve at fractional offset n
  left = n
  right = 1 - left
  l_cube = Math.pow left, 3
  r_cube = Math.pow right, 3
  l_square = 3 * right * Math.pow(left, 2)
  r_square = 3 * left * Math.pow(right, 2)
  x =
    r_cube * p1[0] +
    l_square * p2[0] +
    r_square * p3[0] +
    l_cube * p4[0]
  y =
    r_cube * p1[1] +
    l_square * p2[1] +
    r_square * p3[1] +
    l_cube * p4[1]
  [x, y]
cubic_bezier_curve 0, 100, [0, 10], [10, 50], [5, 20], [8, 20]