Why is built-in bezier function more smooth than my custom bezier function?

I’ve a linear interpolate function:

Vector2 linearInterpolate(Vector2 p1, Vector2 p2, float value)
{
  return p1.add(p2.sub(p1).mult(value));
}

I’ve quadratic bezier curve function:

Vector2 quadraticBezier(Vector2 p1, Vector2 p2, Vector2 p3, float value)
{
  Vector2 r1 = linearInterpolate(p1, p2, value);
  Vector2 r2 = linearInterpolate(p2, p3, value);
  Vector2 r3 = linearInterpolate(r1, r2, value);
  return r3;
}

This is my setup function:

void setup()
{
  size(900, 450);
  p1 = new Vector2(100, 100);
  p2 = new Vector2(350, 180);
  p3 = new Vector2(800, 100);
  //prevVec = linearInterpolation(p1, p2, .0f);
  prevVec = quadraticBezier(p1, p2, p3, .0f);
  strokeWeight(5);
  smooth();
  noFill();
}

Finally I wrote this to draw curve:

void draw()
{
  for (float i = 0; i < 1; i += 0.001f)
  {
     Vector2 tmp = prevVec;
     Vector2 p = quadraticBezier(p1, p2, p3, i);
     line(tmp.x, tmp.y, prevVec.x, prevVec.y);
     prevVec = p;
  }
}

I also draw bezier curve via built-in bezier function:

void draw()
{
  bezier(100, 260, 180, 340, 625, 340, 800, 260);
}

This is the screenshot of the curves:


You can see that my custom bezier curve is not smooth but the built-in bezier function is more smooth. Why? How can I create smooth custom curve?

1 Like

I think you will find that the built in Bezier function uses a cubic Bezier curve (4 control points) whereas your implementation is a quadratic Bezier curve (3 control points).

Generally the higher the Bezier curve order (number of control points) the more ‘complex’ the curve can be. For instance a cubic curve can have a self-intersection , that is not possible with a quadratic curve. the curve

1 Like

I wrote cubic bezier function:

Vector2 cubicBezier(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float value)
{
  Vector2 r1 = linearInterpolation(p1, p2, value);
  Vector2 r2 = linearInterpolation(p2, p3, value);
  Vector2 r3 = linearInterpolation(p3, p4, value);
  Vector2 r4 = linearInterpolation(r1, r2, value);
  Vector2 r5 = linearInterpolation(r2, r3, value);
  Vector2 r6 = linearInterpolation(r4, r5, value);
  return r6;
}

My draw function:

for (float i = 0; i < 1; i += 0.001f)
  {
    Vector2 tmp = prevVec;
    Vector2 p = cubicBezier(p1, p2, p3, p4, i);
    line(tmp.x, tmp.y, prevVec.x, prevVec.y);
    prevVec = p;
  }

But the result is same:

1 Like

I don’t know where you got the class Vector2 or the implementation of the method linearInterpolation.

So, I have recreated the problem using the PVector class and the PVector.lerp(…) method from Processing and overlaid the two curves and got this result

The yellow curve is drawn using the cubicBezier method and the green using the bezier method as you can see there is no difference.

I suggest you make sure you are using the same control point coordinates for both methods.

Here is the code I used for this sketch

PVector pp1 = new PVector(100, 260), pp2 = new PVector(180, 340), 
  pp3 = new PVector(625, 340), pp4 = new PVector(800, 260);

void setup() {
  size(820, 400);
  background(200);
  noFill();
  stroke(255, 255, 130);
  strokeWeight(6);
  drawCubicCurve(pp1, pp2, pp3, pp4);
  stroke(90, 200, 90);
  strokeWeight(2);
  bezier(pp1.x, pp1.y, pp2.x, pp2.y, pp3.x, pp3.y, pp4.x, pp4.y);
}

void drawCubicCurve(PVector p1, PVector p2, PVector p3, PVector p4) {
  PVector prevP = cubicBezier(p1, p2, p3, p4, 0);
  float deltaT = 0.001;
  for (float i = deltaT; i <= 1; i += deltaT) {
    PVector nextP =  cubicBezier(p1, p2, p3, p4, i);
    line(nextP.x, nextP.y, prevP.x, prevP.y);
    prevP = nextP;
  }
}

PVector cubicBezier(PVector p1, PVector p2, PVector p3, PVector p4, float value) {
  PVector r1 = PVector.lerp(p1, p2, value);
  PVector r2 = PVector.lerp(p2, p3, value);
  PVector r3 = PVector.lerp(p3, p4, value);
  PVector r4 = PVector.lerp(r1, r2, value);
  PVector r5 = PVector.lerp(r2, r3, value);
  PVector r6 = PVector.lerp(r4, r5, value);
  return r6;
}
4 Likes

I tested your code. But the problem is not about cubic or quadratic bezier. You can see pixels of custom bezier on your picture. You also can see my picture here (Green bezier is built-in, black bezier is custom):


I think built-in bezier uses a different algorithm instead of my custom bezier.

It was in your first post. Although you can exactly duplicate any quadratic curve with a cubic curve the reverse is not true.

Your original question is in the title of this post.

Why is built-in bezier function more smooth than my custom bezier function?

The answer is -

“If there are no errors in your Vector2 class and the linearInterpolate then they will produce the same curve.”

The tiny difference between the green and black lines are probably due to the number of lines used to draw the curve. In my custom method I am using 1000 lines (i.e. 1 / deltaT). There is certainly no difference in smoothness between the 2 implementations.

In this case I think the answer is in the lack(?) of anti-aliasing for the renderer used (ref. smooth()).
(Provided quark’s assumtions above is correct, in that there’s no errors in your custom classes).

Still using Processing 3.5.4 btw. Maybe it’s different for the new version?

  size(820, 400, P2D);
  smooth(3); // try 2,3,4 or 8

Based on quarks code above I made this little test:

Click to (un) expand
/* Quadratic  Bezier test 1

Based on quarks cubic Bezier
https://discourse.processing.org/t/why-is-built-in-bezier-function-more-smooth-than-my-custom-bezier-function/31833/3
2021.08.25
*/


PVector pp1 = new PVector(100, 260), pp2 = new PVector(180, 340), 
        pp3 = new PVector(625, 340), pp4 = new PVector(800, 260);

PVector pc1 = new PVector(100, 100),
        pc2 = new PVector(350, 180), 
        pc3 = new PVector(800, 100);

void setup() {
  size(820, 400, P2D);
  smooth(3); // try 2,3,4 or 8
  //noSmooth();
  background(255);
  noFill();
  stroke(0);
  strokeWeight(6);
  drawQuadraticCurve(pc1, pc2, pc3);
  //stroke(90, 200, 90);
  //strokeWeight(2);
  //drawCubicCurve(pp1, pp2, pp3, pp4);
  bezier(pp1.x, pp1.y, pp2.x, pp2.y, pp3.x, pp3.y, pp4.x, pp4.y);
}

void drawCubicCurve(PVector p1, PVector p2, PVector p3, PVector p4) {
  PVector prevP = cubicBezier(p1, p2, p3, p4, 0);
  float deltaT = 0.001;
  for (float i = deltaT; i <= 1; i += deltaT) {
    PVector nextP =  cubicBezier(p1, p2, p3, p4, i);
    line(nextP.x, nextP.y, prevP.x, prevP.y);
    prevP = nextP;
  }
}

PVector cubicBezier(PVector p1, PVector p2, PVector p3, PVector p4, float value) {
  PVector r1 = PVector.lerp(p1, p2, value);
  PVector r2 = PVector.lerp(p2, p3, value);
  PVector r3 = PVector.lerp(p3, p4, value);
  PVector r4 = PVector.lerp(r1, r2, value);
  PVector r5 = PVector.lerp(r2, r3, value);
  PVector r6 = PVector.lerp(r4, r5, value);
  return r6;
}

void drawQuadraticCurve(PVector p1, PVector p2, PVector p3) {
  PVector prevP = quadraticBezier(p1, p2, p3, 0);
  float deltaT = 0.001;
  for (float i = deltaT; i <= 1; i += deltaT) {
    PVector nextP =  quadraticBezier(p1, p2, p3, i);
    line(nextP.x, nextP.y, prevP.x, prevP.y);
    prevP = nextP;
  }
}

PVector quadraticBezier(PVector p1, PVector p2, PVector p3, float value) {
  PVector r1 = PVector.lerp(p1, p2, value);
  PVector r2 = PVector.lerp(p2, p3, value);
  PVector r3 = PVector.lerp(r1, r2, value);
  return r3;
}

Btw what is the default renderer anyway?

I get different results from not specifying a renderer (= default), to specifying one. The default renderer doesn’t seem to have anti-aliasing at all? Yet the documentation states:

The default renderer uses smooth(3) by default

FX2D doesn’t seem to care about different smooth() settings either, but it’s still a bit more smoothed than the default renderer.

P2D with smooth(4) or (8) is the best, I haven’t compared side-by-side but I don’t see much if any difference. I have a decent-ish GPU so I don’t think it’s the hardware (then again I use glasses… :stuck_out_tongue: )

EDIT: Slight correction: I switched up quadratic and cubic… Quark’s Bezier above is cubic, not quadratic. Reflected in code comments only, no change otherwise.
Oh and also, I believe the default renderer is named Java2D?

2 Likes