-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
as per email discussion beginning http://dev.openframeworks.cc/htdig.cgi/of-dev-openframeworks.cc/2012-April/004332.html
i'd like to propose a couple of changes to the way ofPath works around handling Catmull-Rom and Bezier segments.
i'm calling this code:
ofPath path;
path.moveTo( 100, 100 );
path.lineTo( 150, 100 );
path.curveTo( 200, 150 );
path.lineTo( 200, 200 );
path.draw();
when i read this i'm visualizing a horizontal line segment and a vertical line segment with a curve smoothly connecting them, something like this (no.1):
or at least (though less desirable), this (no.2):
what i actually get is this (no.3)
whether it's a bug or not, i think no.3 is wrong. i asked for a curve! where's my curve? also why is my 3rd point being completely ignored?
possible objections to this line of thinking (aka, what Arturo said):
- 'but Catmull-Rom requires 4 points'
answer(a): but i am giving it four points: (100,100), (150,100), (200,150), (200,200) (image no.2)
answer(b): i'm a beginner and i haven't read the docs so i don't know that 'curveTo' means Catmull-Rom, nor that Catmull-Rom needs 4 points. where's my curve???
answer(c): ok, so i'm only giving the algorithm one point, which means i'm doing something wrong. but wait. if you look at my code above, based on the way the API is structured (move to here, straight line to here, curve to here, straight line to here), it doesn't look like i'm doing anything wrong. i expect it to work, and when it doesn't i become confused and angry. - 'why don't you use a bezier instead of a curve vertex?'
answer: why should i need to calculate the bezier handles myself? the code can and therefore should do this for me. intuitively, stright line followed by curve followed by straight line should automatically generate control points for a clean transition from the straight segment to curve segment without corners. we can add an explicit 'please make a corner here' bFlag to the curveTo function if we want a corner.
(there's an analogous case here for bezierTo: if i'm coming out of or joining to a straight line segment or another curve i would like an option for my bezier handles to be calculated automatically; similarly it'd be nice to have an option to automatically mirror a previous bezier segment's handle for the first control point to bezierTo, in order to avoid making a corner.)
- 'but this is how ofCurveVertex used to work'
answer: no it's not. if you switched vertex types in the middle of a pair of ofBeginShape/ofEndShape, your existing points get thrown away in some circumstances. basically ofBeginShape/endShape wasn't able to handle changing the vertex types mid-path, whereas according to its API, ofPath is.
so, what is the correct behaviour with this code?
path.moveTo( 100, 100 );
path.lineTo( 150, 100 );
path.curveTo( 200, 150 );
path.lineTo( 200, 200 );
to me, it's something like no.1, a horizontal line segment and a vertical line segment with a curve smoothly connecting them.
does anyone else expect something different?
if not, let's make ofPath work this way!
notes:
source to generate no.3:
vector<ofPoint> points;
points.push_back( ofPoint( 100, 100 ) );
points.push_back( ofPoint( 150, 100 ) );
points.push_back( ofPoint( 200, 150 ) );
points.push_back( ofPoint( 200, 200 ) );
ofNoFill();
ofSetColor( 128, 128, 128 );
for ( int i=0; i<points.size(); i++ )
ofCircle( points[i], 5 );
ofPath path;
path.moveTo( points[0] );
path.lineTo( points[1] );
path.curveTo( points[2] );
path.lineTo( points[3] );
path.setColor( ofColor( 255, 255, 255 ) );
path.setFilled(false);
path.draw();
for image no.1 substitute:
path.moveTo( points[0] );
path.lineTo( points[1] );
path.bezierTo( ofPoint( 175, 100 ), ofPoint( 200, 125 ), points[2] );
path.curveTo( points[2] );
path.lineTo( points[3] );
(there are other viable locations for the bezier control handles here, but i like the way this one looks. it's easy to calculate mathematically: cast a ray from [1] in the same direction as the [0]->[1] line segment, and find the point along the ray at (say) 1/3 of the distance between [1] and [2]; this is your first control point. do something similar for the second control point.)
image no.2 is generated by
path.moveTo( points[0] );
path.lineTo( points[1] );
path.curveTo( points[0] );
path.curveTo( points[1] );
path.curveTo( points[2] );
path.curveTo( points[3] );
path.lineTo( points[3] );
this is pretty ugly code and unfortunately it creates corners at points[1] and points[2] which isn't correct in my opinion.