2

Take a typical cubic bezier curve drawn in JavaScript (this example I googled...) http://jsfiddle.net/atsanche/K38kM/

Specifically, these two lines:

context.moveTo(188, 130);
context.bezierCurveTo(170, 10, 350, 10, 388, 170);

We have a cubic bezier which starts at 188, 130, ends at 388, 170, and has controls points a:170, 10 and b:350, 10

My question is would it be possible to mathematically adjust the end point and control points to make another curve which is only a segment of the original curve?

The ideal result would be able to able to take a percentage slice of the bezier from the beginning, where 0.5 would draw only half of the bezier, 0.75 would draw most of the bezier (and so on)

I've already gotten working a few implementations of De Castelau which allow me to trace the contour of the bezier between [0...1], but this doesn't provide a way to mathematically recalculate the end and control points of the bezier to make a sub-bezier...

Thanks in advance

1owk3y
  • 1,115
  • 1
  • 15
  • 30
  • Is it possible? Yes. With Casteljau? Yes. But HOW?... [by proceeding in this way](http://www.helloflash.net/Fichiers/HTML/CATEGORIES/physique/physique1.htm#p12). Only saying that Casteljau is necessary isn't a sufficient answer. The correct and more difficult answer is **how to use Casteljau**... – helloflash Sep 09 '14 at 06:16

3 Answers3

8

De Casteljau is indeed the algorithm to go. For a cubic Bezier curve defined by 4 control points P0, P1, P2 and P3, the control points of the sub-Bezier curve (0, u) are P0, Q0, R0 and S0 and the control points of the sub-Bezier curve (u, 1) are S0, R1, Q2 and P3, where

Q0 = (1-u)*P0 + u*P1
Q1 = (1-u)*P1 + u*P2
Q2 = (1-u)*P2 + u*P3
R0 = (1-u)*Q0 + u*Q1
R1 = (1-u)*Q1 + u*Q2
S0 = (1-u)*R0 + u*R1

Please note that if you want to "extract" a segment (u1, u2) from the original Bezier curve, you will have to apply De Casteljau twice. The first time will split the input Bezier curve C(t) into C1(t) and C2(t) at parameter u1 and the 2nd time you will have to split the curve C2(t) at an adjusted parameter u2* = (u2-u1)/(1-u1).

fang
  • 3,473
  • 1
  • 13
  • 19
  • Just implemented your solution. Worked perfectly! To be fair this is a far more accurate answer to my question. Thanks fang! Sorry phillipp – 1owk3y Sep 20 '14 at 13:16
1

This is how to do it. You can get the left half or right half with this functin. This function is take thanks to mark from here: https://stackoverflow.com/a/23452618/1828637

I have it modified so it can be fit to a unit cell so we can use it for cubic-bezier in css transitions.

function splitCubicBezier(options) {
  var z = options.z,
      cz = z-1,
      z2 = z*z,
      cz2 = cz*cz,
      z3 = z2*z,
      cz3 = cz2*cz,
      x = options.x,
      y = options.y;

  var left = [
    x[0],
    y[0],
    z*x[1] - cz*x[0], 
    z*y[1] - cz*y[0], 
    z2*x[2] - 2*z*cz*x[1] + cz2*x[0],
    z2*y[2] - 2*z*cz*y[1] + cz2*y[0],
    z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
    z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0]];

  var right = [
    z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
    z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0],
                    z2*x[3] - 2*z*cz*x[2] + cz2*x[1],
                    z2*y[3] - 2*z*cz*y[2] + cz2*y[1],
                                    z*x[3] - cz*x[2], 
                                    z*y[3] - cz*y[2], 
                                                x[3],
                                                y[3]];

  if (options.fitUnitSquare) {
    return {
      left: left.map(function(el, i) {
        if (i % 2 == 0) {
          //return el * (1 / left[6])
          var Xmin = left[0];
          var Xmax = left[6]; //should be 1
          var Sx = 1 / (Xmax - Xmin);
          return (el - Xmin) * Sx;
        } else {
          //return el * (1 / left[7])
          var Ymin = left[1];
          var Ymax = left[7]; //should be 1
          var Sy = 1 / (Ymax - Ymin);
          return (el - Ymin) * Sy;
        }
      }),
      right: right.map(function(el, i) {
        if (i % 2 == 0) {
          //xval
          var Xmin = right[0]; //should be 0
          var Xmax = right[6];
          var Sx = 1 / (Xmax - Xmin);
          return (el - Xmin) * Sx;
        } else {
          //yval
          var Ymin = right[1]; //should be 0
          var Ymax = right[7];
          var Sy = 1 / (Ymax - Ymin);
          return (el - Ymin) * Sy;
        }
      })
    }
  } else {
   return { left: left, right: right};
  }
}

Thats the function and now to use it with your parameters.

var myBezier = {
  xs: [188, 170, 350, 388],
  ys: [130, 10, 10, 170]
};

var splitRes = splitCubicBezier({
  z: .5, //percent
  x: myBezier.xs,
  y: myBezier.ys,
  fitUnitSquare: false
});

This gives you

({
    left: [188, 130, 179, 70, 219.5, 40, 267, 45],
    right: [267, 45, 314.5, 50, 369, 90, 388, 170]
})

fiddle proving its half, i overlaid it over your original:

http://jsfiddle.net/K38kM/8/

Community
  • 1
  • 1
Noitidart
  • 35,443
  • 37
  • 154
  • 323
  • A great answer, and the perfect implementation really. Is it right to change my selected answer now for a third time? That seems like exploitation of some sort. Actually, I also made a similar implementation myself based on the previously selected answer which I was meaning to post. Thanks nonetheless for your help! – 1owk3y Oct 15 '14 at 05:16
  • Dont worry about accepting it, im just sharing to help others if they search for this. :) If you can share your method as well that would be awesome :) – Noitidart Oct 15 '14 at 05:55
0

Yes it is! Have a look at the bezier section here

http://en.m.wikipedia.org/wiki/De_Casteljau's_algorithm

It is not that difficult all in all.

philipp
  • 15,947
  • 15
  • 61
  • 106
  • 1
    Actually I found the math in the example you cited very high-level, and did not clearly explain sub-dividing a bezier. However using the diagrams from your link I pieced together the fact I needed: the end point follows the curve exactly at [0...1], and the control points approach the previous points from their original positions by a factor of [0...1]. I'm reluctant to mark this as the correct answer since now I'm tempted to post a script example, but at the same time you helped me reach a conclusion to my question, so thank you for your answer. – 1owk3y Sep 08 '14 at 11:59
  • Well the geometrical interpretation is quite simple, whereby the maths beyond are not. Beziér curves are not that simple by nature but in this case the luck is on your side. N.b.: I can recommen paper.js a nice library for vector curves on canvas... – philipp Sep 08 '14 at 13:17