Connecting path curveTo() first and last points

Hello. I’m trying to get an animation of a 2D shape like floating oil in a lava lamp. I did it first setting the path points positions taking the shape of a circle and adding to each one some noise according to timeElapsed. Then I draw this vertices connecting them with the curveTo() function. here is the code:

void ofApp::setup() {
	ofBackground(255);
	int pointsAmount = 5;
	for (int i = 0; i < pointsAmount; i++)
	{
		points.push_back(ofVec2f());
	}
	path.setColor(ofColor(100, 255, 100));
}

//--------------------------------------------------------------
void ofApp::update() {
	path.clear();
	for (int i = 0; i < points.size(); i++)
	{
        float time = ofGetElapsedTimef();
		points[i].x = cos(i* PI * 2 / points.size());
		points[i].y = sin(i* PI * 2 / points.size());
		points[i] *= ofMap(ofNoise(time, i), 0, 1, 1, 1.35) * 100;
	}

	path.curveTo(points[0]);
	for (int i = 0; i < points.size(); i++)
	{
		path.curveTo(points[i]);
	}
	path.curveTo(points[0]);
	path.curveTo(points[0]);

}

//--------------------------------------------------------------
void ofApp::draw(){
	ofTranslate(ofGetWidth() / 2, ofGetHeight() / 2);
	ofRotate(ofGetElapsedTimef()*10);
	path.draw();
}

but the result is not what I want. the first and last vertices are connected forming a sharp shape as you can see in this animation:
evp2qPQZ3t

I thought about a solution would be setting the first and last vertices as bezierTo(), then I could handle the control points manually, but I wonder if there is a better/easier solution
.
thanks

Hi, a similar question was posted last week https://github.com/openframeworks/openFrameworks/issues/6869

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.

so, in your case you need to add more points.
instead of the following

path.curveTo(points[0]);
	for (int i = 0; i < points.size(); i++)
	{
		path.curveTo(points[i]);
	}
	path.curveTo(points[0]);
	path.curveTo(points[0]);

you have to do this

path.curveTo(points[points.size()-1]); // add the last point first
	for (int i = 0; i < points.size(); i++)
	{
		path.curveTo(points[i]);
	}
	path.curveTo(points[0]);

Hi @roymacdonald , thanks for the answer.
Yes I noticed this question was made a lot of times on the forum but I couldn’t solve the problem with those threads answers. Neither with this code you told me:

path.curveTo(points[points.size()-1]); // add the last point first
	for (int i = 0; i < points.size(); i++)
	{
		path.curveTo(points[i]);
	}
	path.curveTo(points[0]);

It stills giving me the same problem.

Anyway I was working on this with the bezierTo() function instead of curveTo(). Did the whole circle with bezier segments, with some help from this stackOverflow thread:

geometry - How to create circle with Bézier curves? - Stack Overflow

And already with all points and control points stored in vectors I could achieve what I was looking for:
56D4bEylm3
There I rendered the controlPoints of each path vertex.

Here’s the code if someone is interested in:

struct ControlPoints {
	ofVec2f a, b;
};

ofPath path;
vector<ofVec2f> points;
vector<ControlPoints> controlPoints;
const int pointsAmount = 5;
const float radius = 150;
const float controlPointDistance = (4.f / 3.f) * tan(PI / (2.f * pointsAmount)) * radius;
const float difference = 1.5;
void ofApp::setup() {
	ofBackground(255);
	for (int i = 0; i < pointsAmount; i++)
	{
		points.push_back(ofVec2f());
		controlPoints.push_back(ControlPoints());
	}
	path.setColor(0);
}

//--------------------------------------------------------------
void ofApp::update() {
	path.clear();
	for (int i = 0; i <pointsAmount; i++)
	{
		float time = ofGetElapsedTimef();
		points[i].x = cos(i* PI * 2 / pointsAmount);
		points[i].y = sin(i* PI * 2 / pointsAmount);
		points[i] *= ofMap(ofNoise(time, i), 0, 1, 1, difference) * radius;

		ofVec2f controlPointDirection = points[i].getPerpendicular();

		controlPoints[i].a = controlPointDirection * controlPointDistance + points[i];
		controlPoints[i].b = -controlPointDirection * controlPointDistance + points[i];
	}

	path.moveTo(points[0]);
	for (int i = 0; i <pointsAmount; i++)
	{
		path.bezierTo
		(
			controlPoints[i].a, 
			controlPoints[(i + 1) % pointsAmount].b, 
			points[(i + 1) % pointsAmount]
		);
	}
;}

//--------------------------------------------------------------
void ofApp::draw(){
	ofTranslate(ofGetWidth() / 2, ofGetHeight() / 2);
	ofRotate(ofGetElapsedTimef()*10);
	path.draw();

	int i = 0;
	for (ControlPoints cp : controlPoints)
	{
		ofColor color;
		if (i == 0) color = ofColor(0);
		else if (i == 1) color = ofColor(255, 0, 0);
		else if (i == 2) color = ofColor(0, 255, 0);
		else if (i == 3) color = ofColor(0, 0, 255);
		else if (i == 4) color = ofColor(255, 0, 255);
		ofSetColor(color);
		ofDrawCircle(cp.a, 5);
		ofDrawCircle(cp.b, 5);
		i++;
	}
}

If there’s a better solution I would be glad to know it.

Sorry that code should have been

    path.moveTo(points[0]);
    path.curveTo(points[points.size()-1]); // add the last point first
    for (int i = 0; i < points.size(); i++)
    {
        path.curveTo(points[i]);
    }
    path.curveTo(points[0]);
    path.curveTo(points[1]);

I checked it and works.

2 Likes

Great! that’s pretty much simpler than my code, thanks.
Now I get it. with the first and second vertices you are setting the previous control point direction but the current position still being the one of the first vertex. same with the last ones

1 Like