ofPolyline curveTo - The first point is off from vertex
Hi,
I just noticed that ofPolyline draws the first point slightly off from the position I set.
The first point of the line isn’t in the center of the red circle P2, which is where I set the second vertex of the ofPolyline. It workds perfectly with the last point, which falls exactly in the middle of the third vertex (P3).
Here’s the code to reproduce that (everything inside draw() just to simplify testing):
void ofApp::draw()
{
ofBackground(255);
ofSetLineWidth(1);
ofPoint p1(300, 100);
ofPoint p2(450, 100);
ofPoint p3(600, 100);
ofPoint p4(750, 100);
ofPolyline line;
line.curveTo(p1);
line.curveTo(p2);
line.curveTo(p3);
line.curveTo(p4);
// Draw line
ofSetColor(0);
line.draw();
// Draw Circles
ofNoFill();
ofSetColor(255, 0, 0);
ofCircle(p1.x, p1.y, 5);
ofCircle(p2.x, p2.y, 5);
ofCircle(p3.x, p3.y, 5);
ofCircle(p4.x, p4.y, 5);
}
I asked about that on OF forum: https://forum.openframeworks.cc/t/ofpolyline-curveto-the-first-point-is-off-from-vertex/39256
And Zach mentioned this:
good question. I took a look and believe this may be a bug in ofPolyline.inl (curveTo)
for (int i = 1; i <= curveResolution; i++){
should be (I think)
for (int i = 0; i <= curveResolution; i++){
can you open an issue on GitHub ? I think the fix is easy but it’s worth looking at a little longer (to see why that for loop starts from 1 and not 0
I don't think this is a bug. I remember seeing this long ago and I figured out why, but cant recall any details. I will inspect and report back.
thanks roy! one thing I was thinking about is that it's maybe not a bug if you've added points already to the polyline and you want to append a curve. in that case, skipping the first point might make sense to prevent you from duplicating that point. ie,
line.addVertex(p2 - ofPoint(0,100));
line.addVertex(p2);
// appending a curve
line.curveTo(p1);
line.curveTo(p2);
line.curveTo(p3);
line.curveTo(p4);
@ofZach right. it was something like that which I remember. Like having to put explicitly the first vertex of the curve. So far we have had the ofPolyline working like this for quite a while, so just simply changing the starting index in that for loop will definitively break things. You can notice that the same applies to bezier curves. I will check how is that this exactly works and add that to the documentation sounds like a better thing to do.
@roymacdonald but how would someone, with the current ofPolyline.inl version, be able to draw this:

Instead of getting something like this?

when using curveTo() ?
would this work
line.addVertex(p2);
// appending a curve
line.curveTo(p1);
line.curveTo(p2);
line.curveTo(p3);
line.curveTo(p4);
I think it's almost a kind of semantic question of if "curveTo" includes or doesn't include the starting point. I think the idea is that you'd be building a shape out of curve commands so you don't need to include the start. but I can see how it breaks when you use it just on it's own.
Hi @kaua-melo Try the following code ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp{
public:
void draw();
void keyReleased(int key);
void mouseDragged(int x, int y, int button);
void mousePressed(int x, int y, int button);
void mouseReleased(int x, int y, int button);
void mouseExited(int x, int y);
bool bDragging = false;
glm::vec3 pressOffset;
vector<glm::vec3> p = {{100,100,0}, {200,100,0}, {300, 100,0}, {450, 100,0}, {600, 100,0}, {750, 100,0}, {800, 100,0} };
size_t draggingIndex = 0;
bool bUseBezier = false;
};
ofApp.cpp
#include "ofApp.h"
//--------------------------------------------------------------
void ofApp::draw(){
ofBackground(255);
ofSetLineWidth(1);
ofPolyline line;
if(bUseBezier){
//we first need to add where the line begins.
line.addVertex(p[0]);
line.bezierTo(p[1], p[2], p[3]);
// no neeed to add a vertex before this call to bezierTo because it will use the last point in the previous call.
line.bezierTo(p[4], p[5], p[6]);
}else{
line.addVertex(p[1]);
for(auto& pt: p){
line.curveTo(pt);
}
}
// Draw line
ofPushStyle();
ofSetLineWidth(1);
if(bUseBezier){
ofSetColor(120);
//these are just to visualize better which are the bezier control points
ofDrawLine(p[0], p[1]);
ofDrawLine(p[2], p[3]);
ofDrawLine(p[3], p[4]);
ofDrawLine(p[5], p[6]);
}
ofSetLineWidth(2);
ofSetColor(0);
line.draw();
ofPopStyle();
ofNoFill();
//draw circles with their index
for(size_t i = 0; i < p.size(); i++){
ofSetColor(0);
ofDrawBitmapString(ofToString(i), p[i].x - 5, p[i].y + 15);
ofSetColor(255, 0, 0);
ofDrawCircle(p[i].x, p[i].y, 5);
}
// just some onscreen info
stringstream ss;
ss << "press the space bar to toggle between bezier and curve" <<endl;
ss << "Current mode: " << (bUseBezier?"bezier":"curve");
ofDrawBitmapStringHighlight(ss.str(), 30, 30);
}
//--------------------------------------------------------------
void ofApp::keyReleased(int key){
if(key == ' '){
bUseBezier ^= true;
}
}
//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){
if(bDragging){
p[draggingIndex] = glm::vec3(x, y, 0) - pressOffset;
}
}
//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){
glm::vec3 m(x, y, 0);
for(size_t i = 0; i < p.size(); i++){
if(glm::distance(m, p[i])<= 5 ){
draggingIndex = i;
pressOffset = m-p[i];
bDragging = true;
break;
}
// draggingIndex
}
//pressPoint
}
//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
bDragging = false;
}
//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y){
bDragging = false;
}
So, the way curveTo works is that is an "auto" curve. Thus it creates the curve automatically without having to specify anything else than some points where you want it to pass through, but for it to work and draw a curve between 2 points it needs to have points before and after it, which are also defined with curveTo. Thus a minimum of 4 calls to curveTo need to be done to be able to draw a curve, then any additional call is just appended and a new section of the curve is drawn. This is why you have to pass the second point to the addVertex function in the following code
line.addVertex(p2);
// appending a curve
line.curveTo(p1);
line.curveTo(p2);
line.curveTo(p3);
line.curveTo(p4);
I think that all this makes more sense when used in the bezierTo I feel.
Hope this helps
@roymacdonald thanks for the explanation!
@ofZach , yes that works too :)
line.addVertex(p2);
// appending a curve
line.curveTo(p1);
line.curveTo(p2);
line.curveTo(p3);
line.curveTo(p4);
I've used ofPolyline several times and never noticed any issue. But now I'm exploring an idea where I need to be very precise on the starting/ending tips of a curveTo and I couldn't understand why that was happening.
As @roymacdonald mentioned, if changing the for (int i = 1; i <= curveResolution; i++) on ofPolyline.inl would probably break other things, maybe the best thing to do would be to add a simple line.addVertex(p2); to the curveTo documentation page: https://openframeworks.cc/documentation/graphics/ofPolyline/#show_curveTo
For example here:
We end up with a line that starts at v0, heads to v1, then heads to v2 and finally ends at v3. But if we had instead done:
p.addVertex(v1); // We need to add the starting point of the curve (v1) when using only curveTo otherwise the first point will be drawn slightly off v1.
// Appending curve:
p.curveTo(v0);
p.curveTo(v1);
p.curveTo(v2);
p.curveTo(v3);
Hi, Yes, simply adding it to the documentation would be the way to go, I think. @kaua-melo can you add this bit to the documentation? you can do so here
Hi @roymacdonald
Sure, I just added it here: https://github.com/openframeworks/ofSite/compare/master...kaua-melo:patch-1