1
$\begingroup$

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.

enter image description here

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();
}
$\endgroup$
4
  • 2
    $\begingroup$ 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. $\endgroup$
    – PaulHK
    Commented May 17, 2022 at 8:06
  • $\begingroup$ @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? $\endgroup$ Commented May 17, 2022 at 20:59
  • $\begingroup$ @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...? $\endgroup$ Commented May 17, 2022 at 21:13
  • $\begingroup$ 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.. $\endgroup$
    – PaulHK
    Commented May 19, 2022 at 11:06

2 Answers 2

1
$\begingroup$

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.

$\endgroup$
5
  • $\begingroup$ 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. $\endgroup$ Commented May 17, 2022 at 21:20
  • $\begingroup$ Curious but how would anti aliased line drawing solve this? I might look into that. $\endgroup$ Commented May 17, 2022 at 23:10
  • 1
    $\begingroup$ 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. $\endgroup$ Commented May 18, 2022 at 0:47
  • 2
    $\begingroup$ @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. $\endgroup$
    – Simon F
    Commented May 19, 2022 at 12:46
  • 1
    $\begingroup$ @SimonF Do not apologize. "pedant"-away! :) (Also, it's not correct to call it a blur.) $\endgroup$ Commented May 19, 2022 at 16:51
0
$\begingroup$

enter image description here

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:

header.h

#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;
    }

    //operator overloading for '=' sign
    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>
#include "header.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);
    glLoadIdentity();
    gluOrtho2D(0.0, 640.0, 0.0, 500.0);
    glutMainLoop();

    return 0;
}
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.