Can I make a half-bezier from full bezier?

2019-04-02 00:43发布

问题:

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

回答1:

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



回答2:

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/



回答3:

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.