Why is there a straight line between my bezier paths?

I have created a bezier curve tool and each time I draw a bezier curve segment I get a straight line between each mouse click. I don't understand why this is happening and was wondering if someone could take a look at my render function and see the error. I have been working on this program with some other users on this stack exchange.

void myDisplay()
{
glClear(GL_COLOR_BUFFER_BIT);

if ((pointsVector[0].x != NULL) && (pointsVector[0].y != NULL))
{
drawLine(pointsVector[points], Cursor);
}

for (int i = 1; i < points; i++)
{
// draw main line & dot
glColor3f(255, 0, 0);
drawDot(pointsVector[i - 1]);

// draw tangent lines and tangent dots
glColor3f(0, 255, 0);
drawLine(controlPoints[i + 1], reverseControlPoints[i + 1]);
glColor3f(0, 0, 255);
drawDot(controlPoints[i + 1]);
drawDot(reverseControlPoints[i + 1]);

vector<Point> finalPoints = final4Points();
vector<Point> finalPointsReverseP1 = final4PointsReverseP1();

Point p1;
if (firstSegmentDrawn == false)
{
p1.setxy(pointsVector[0].x, pointsVector[0].y);
}
else
{
p1.setxy(pointsVector[points - 1].x, pointsVector[points - 1].y);
}

if ((finalPoints[2].x != NULL) && (finalPoints[2].y != NULL))
{
if (firstSegmentDrawn == true)
{
float i;
// draw bezier curve
for (float j = 0; j <= 100; j++)
{
i = j / 100;
// The Green Lines
int xa = getPt(finalPointsReverseP1[0].x, finalPointsReverseP1[1].x, i);
int ya = getPt(finalPointsReverseP1[0].y, finalPointsReverseP1[1].y, i);
int xb = getPt(finalPointsReverseP1[1].x, finalPointsReverseP1[2].x, i);
int yb = getPt(finalPointsReverseP1[1].y, finalPointsReverseP1[2].y, i);
int xc = getPt(finalPointsReverseP1[2].x, finalPointsReverseP1[3].x, i);
int yc = getPt(finalPointsReverseP1[2].y, finalPointsReverseP1[3].y, i);

// The Blue Line
int xm = getPt(xa, xb, i);
int ym = getPt(ya, yb, i);
int xn = getPt(xb, xc, i);
int yn = getPt(yb, yc, i);

// The Black Dot
int x2 = getPt(xm, xn, i);
int y2 = getPt(ym, yn, i);

Point p2;
p2.setxy(x2, y2);

drawLine(p1, p2);
//drawDot(p1);

p1 = p2;
bezierPointsMyDisplay.push_back(p2);
}
}
else
{
float i;
// draw bezier curve
for (float j = 0; j <= 100; j++)
{
i = j / 100;
// The Green Lines
int xa = getPt(finalPoints[0].x, finalPoints[1].x, i);
int ya = getPt(finalPoints[0].y, finalPoints[1].y, i);
int xb = getPt(finalPoints[1].x, finalPoints[2].x, i);
int yb = getPt(finalPoints[1].y, finalPoints[2].y, i);
int xc = getPt(finalPoints[2].x, finalPoints[3].x, i);
int yc = getPt(finalPoints[2].y, finalPoints[3].y, i);

// The Blue Line
int xm = getPt(xa, xb, i);
int ym = getPt(ya, yb, i);
int xn = getPt(xb, xc, i);
int yn = getPt(yb, yc, i);

// The Black Dot
int x2 = getPt(xm, xn, i);
int y2 = getPt(ym, yn, i);

Point p2;
p2.setxy(x2, y2);

drawLine(p1, p2);
//drawDot(p1);

p1 = p2;
bezierPointsMyDisplay.push_back(p2);
}
}
}
}

for (int i = 1; i < bezierPointsMyDisplay.size(); i++)
{
drawLine(bezierPointsMyDisplay[i - 1], bezierPointsMyDisplay[i]);
}

glutSwapBuffers();
}


My drawLine() function:

void drawLine(Point p1, Point p2)
{
glBegin(GL_LINE_STRIP);
glVertex2f(p1.x, p1.y);
glVertex2f(p2.x, p2.y);
glEnd();
}

• It looks like integer truncation. The point vectors are being loaded into integers, but for the line segments to match up correctly you need that fraction as well so your drawing API draws them at the correct subpixel and they should line up better. When drawing lines or polygons you cannot really use integer coordinates. May 17 at 8:06
• @PaulHK I updated all the coordinates relating to drawing and storing the curve to floating point. The curve looks a lot smoother and matches up better but I'm still getting that line between the points. Any other ideas? May 17 at 20:59
• @PaulHK do I need to make any changes to my drawLine() function? (I've shown it above in the question now) I tried doing what Luser said but that didn't really do anything. Maybe I didn't implement it right...? May 17 at 21:13
• Did you change int x2 = getPt(xm, xn, i); int y2 = .... to be float x2 ... ? Replace all the coordinate ints inside that loop to floats. Once any coordinate is converted to an int, it will forever lose its fractional part, so even if you cast back from int->float you've still got a truncated float.. May 19 at 11:06

IIRC in another question, your said your Point class actually uses float x,y; for the coordinates. So one thing that can help is to shift the centers (the "zeros") over to the edge of the pixel, like:

int xi = pt.x + 0.5;
int yi = pt.y + 0.5;


And use those integers in calls the GLUT line drawing api.

I think this change would go inside your drawLine() function.

A more robust, but much more complicated solution would be to do the above and also implement anti-aliased line drawing.

• My point class does use float I added 0.5 to p1 and p2 in the drawLine() function but nothing new happened. I have since changed all integers in the program to float and that has made the curve a lot smoother but I'm still getting the darn line. May 17 at 21:20
• Curious but how would anti aliased line drawing solve this? I might look into that. May 17 at 23:10
• The "jaggedness" of the lines is called aliasing, because the single pixel aliases many possible fractional values nearby. Drawing a line with anti-aliasing means that you treat each pixel as a box and choose the color based on how much of the box is covered by the line. Eg. for a very small 3x3 grid, instead of just black and white, [ (0, 0, 255), (0, 255, 0), (255, 0, 0) ], you blur over the corners a little, making [ (0, 100, 255), (100, 255, 100), (255, 100, 0) ]. So, it helps make the lines look smoother. May 18 at 0:47
• @luserdroog I apologise for being an utter pedant, but "The "jaggedness" of the lines is called aliasing, because the single pixel aliases many possible fractional values" is probably not really correct. It's hard to express "in the margin" but I would say the "aliasing" really refers to the high frequency content of the line (imagine the line being represented as the sum of an infinite sequence of sine functions of increasing frequency) not being low pass filtered before sampling and those then re-appearing (alias/false names) incorrectly as low frequency content. May 19 at 12:46
• @SimonF Do not apologize. "pedant"-away! :) (Also, it's not correct to call it a blur.) May 19 at 16:51

After rewriting the application to only render in the display function I have found what the bug was. The line was continuously rewriting on top of the vector in each frame as opposed to each mouse click release. Here is the fully working code. I have also implemented the suggestion above using floating-point variables. A more thorough explanation can be found here:

#pragma once

int SCREEN_HEIGHT = 500;

//Point class for taking the points
class Point {
public:
float x, y;

void setxy(float x2, float y2)
{
x = x2; y = y2;
}

const Point& operator=(const Point& rPoint)
{
x = rPoint.x;
y = rPoint.y;
return *this;
}

};

void drawDot(Point p1)
{
glBegin(GL_POINTS);
glVertex2i(p1.x, p1.y);
glEnd();
}

void drawLine(Point p1, Point p2)
{
glBegin(GL_LINE_STRIP);
glVertex2f(p1.x, p1.y);
glVertex2f(p2.x, p2.y);
glEnd();
}

float interpolate(float n1, float n2, float perc)
{
float diff = n2 - n1;

return n1 + (diff * perc);
}


main.cpp

#include <iostream>
#include <stdlib.h>
#include <GL/glut.h>
#include <vector>
#include <math.h>

using namespace std;

vector<Point> Points;
Point Tangent;
Point inverseTangent;
Point cursorLocationLive;
int TangentsSize = 0;
vector<Point> Tangents(TangentsSize);
vector<Point> inverseTangents(TangentsSize);
vector<Point> BezierCurve;
bool MouseReleased = false;
bool PrevMouse = false;

void myMouse(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON)
{
if (state == GLUT_DOWN)
{
MouseReleased = false;
// Store points into Points vector on click
Point point;
point.setxy(x, SCREEN_HEIGHT - y);
Points.push_back(point);

// Tangents are set to the cursor position
Tangent.setxy(x, SCREEN_HEIGHT - y);
inverseTangent.x = (2 * Points[Points.size() - 1].x) - Tangent.x;
inverseTangent.y = (2 * Points[Points.size() - 1].y) - Tangent.y;

/*Add new element to Tangent & inverseTangent so when we draw the curve
the tangents are accessed at the right index*/
TangentsSize++;
}
else if (state == GLUT_UP)
{
MouseReleased = true;
// Upon mouse release store tangent and inverse tangent into separate vectors
Tangents.push_back(Tangent);
inverseTangents.push_back(inverseTangent);
}
}
}

void passiveMotion(int x, int y)
{
// Sets the location of cursor while moving with no buttons pressed
cursorLocationLive.setxy(x, SCREEN_HEIGHT - y);
}

void motion(int x, int y)
{
// Sets the coordinates of the tangents when mouse moves with a button held down
Tangent.setxy(x, SCREEN_HEIGHT - y);
inverseTangent.x = (2 * Points[Points.size() - 1].x) - Tangent.x;
inverseTangent.y = (2 * Points[Points.size() - 1].y) - Tangent.y;
}

void myDisplay()
{
glClear(GL_COLOR_BUFFER_BIT);

// Draw main points in red
glColor3f(255, 0, 0);
for (int i = 0; i < Points.size(); i++)
{
drawDot(Points[i]);
}

// If there is a starting point draw a line to cursor from last drawn point in passive motion
if (Points.size() > 0)
{
glColor3f(0, 0, 0);
drawLine(Points[Points.size() - 1], cursorLocationLive);
}

// Draw live tangent dots in green
glColor3f(0, 255, 0);
drawDot(Tangent);
drawDot(inverseTangent);
// Draw live tangent lines in blue
glColor3f(0, 0, 255);
drawLine(Tangent, inverseTangent);

for (int i = 0; i < Tangents.size(); i++)
{
// Draw stored tangent dots in green
glColor3f(0, 255, 0);
drawDot(Tangents[i]);
drawDot(inverseTangents[i]);
// Draw stored tangent lines in blue
glColor3f(0, 0, 255);
drawLine(Tangents[i], inverseTangents[i]);
}

// Loop through all points
if (Points.size() >= 2)
{
// p1 is the start of the curve set to second last point
Point p1;
p1 = Points[Points.size() - 2];

float i;
// Calculate curve coordinates
for (float j = 0; j <= 100; j++)
{
i = j / 100;
// The Green Lines
float xa = interpolate(Points[Points.size() - 2].x, Tangents[TangentsSize - 2].x, i);
float ya = interpolate(Points[Points.size() - 2].y, Tangents[TangentsSize - 2].y, i);
float xb = interpolate(Tangents[TangentsSize - 2].x, inverseTangent.x, i);
float yb = interpolate(Tangents[TangentsSize - 2].y, inverseTangent.y, i);
float xc = interpolate(inverseTangent.x, Points[Points.size() - 1].x, i);
float yc = interpolate(inverseTangent.y, Points[Points.size() - 1].y, i);

// The Blue Line
float xm = interpolate(xa, xb, i);
float ym = interpolate(ya, yb, i);
float xn = interpolate(xb, xc, i);
float yn = interpolate(yb, yc, i);

// The Black Dot
float x2 = interpolate(xm, xn, i);
float y2 = interpolate(ym, yn, i);

Point p2;
p2.setxy(x2, y2);

glColor3f(0, 0, 0);
drawLine(p1, p2);

p1 = p2;

// Prevents curves generated during mouse motion from being stored
if (PrevMouse == false && MouseReleased == true)
{
// Store curvature into Bezier Points
BezierCurve.push_back(p2);
}
}
}

// When the mouse is down PrevMouse = false so when the mouse is released
// the if statement evaluates to true and stores the new curvature once
// and then sets PrevMouse to true to prevent writing on top of the vector
PrevMouse = MouseReleased;

// Draw all bezier curvature
for (int i = 1; i < BezierCurve.size(); i++)
{
drawLine(BezierCurve[i - 1], BezierCurve[i]);
}

glutSwapBuffers();
}

void timer(int)
{
glutTimerFunc(1000 / 60, timer, 0);
glutPostRedisplay();
}

int main(int argc, char* argv[]) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(640, 500);
glutInitWindowPosition(100, 150);
glutCreateWindow("Bezier Curve");
glutDisplayFunc(myDisplay);
glutIdleFunc(myDisplay);
glutTimerFunc(0, timer, 0);
glutMouseFunc(myMouse);
glutPassiveMotionFunc(passiveMotion);
glutMotionFunc(motion);
glClearColor(255, 255, 255, 0.0);
glPointSize(3);
glMatrixMode(GL_PROJECTION);