3

If I have a simple 2-D matrix with normalized values on x-axis between 0 and 1 and y-axys between 0 and 1, and I have 3 points in this matrix e.g. P1 (x=0.2,y=0.9), P2 (x=0.5,y=0.1) and P3 (x=0.9,y=0.4).

How can I simply calculate a curve thru this points, meaning having a function which is giving me the y for any x.

I now that there are any number of possible curves thru 3 points. But hey, you know what I mean: I want a smooth curve thru it, usable for audio-sample-interpolation, usable for calculation a volume-fade-curve, usable for calculating a monster-walking-path in a game.

Now I have searched the net for this question about 3 days, and I cannot believe that there is no usable solution for this task. All the text dealing about Catmull-rom-Splines, bezier-curves and all that theroretical stuff has all at least one point which doesn't make it for me usable. For example Catmull-Rom-splines need to have a fix distance between the control-points (I would use this code and set the 4. point-y to the 3. point y) :

void CatmullRomSpline(float *x,float *y,float x1,float y1,float x2,float y2,float x3,float y3,float x4,float y4,float u)
{
//x,y are calculated for x1,y1,x2,y2,x3,y3 and x4,y4 if u is the normalized distance (0-1) in relation to the distance between x2 and x3 for my whiched point

float u3,u2,f1,f2,f3,f4;

u3=u*u*u;
u2=u*u;
f1=-0.5f * u3 + u2 -0.5f *u;
f2= 1.5f * u3 -2.5f * u2+1.0f;
f3=-1.5f * u3 +2.0f * u2+0.5f*u;
f4=0.5f*u3-0.5f*u2;

*x=x1*f1+x2*f2+x3*f3+x4*f4;
*y=y1*f1+y2*f2+y3*f3+y4*f4;

}

But I don't see that x1 to x4 have any affect on the calculation of y, so I think x1 to x4 must have the same distance?

...

Or bezier-code doesn't calcuate the curve thru the points. The points (at least the 2. point) seem only to have a force-effect on the line.

typedef struct Point2D
{
double x;
double y;
} Point2D;

class bezier
{
std::vector<Point2D> points;
bezier();
void PushPoint2D( Point2D point );
Point2D GetPoint( double time );
~bezier();
};

void bezier::PushPoint2D(Point2D point)
{
points.push_back(point);
}

Point2D bezier::GetPoint( double x )
{
int i;
Point2D p;

p.x=0;
p.y=0;

if( points.size() == 1 ) return points[0];
if( points.size() == 0 ) return p;

bezier b;
for (i=0;i<(int)points.size()-1;i++)
{
    p.x = ( points[i+1].x - points[i].x ) * x + points[i].x;
    p.y = ( points[i+1].y - points[i].y ) * x + points[i].y;
    if (points.size()<=2) return p;
    b.PushPoint2D(p);
}

return b.GetPoint(x);
}

double GetLogicalYAtX(double x)
{
bezier bz;
Point2D p;

p.x=0.2;
p.y=0.9;
bz.PushPoint2D(p);

p.x=0.5;
p.y=0.1;
bz.PushPoint2D(p);

p.x=0.9;
p.y=0.4;
bz.PushPoint2D(p);

p=bz.GetPoint(x);

return p.y;
}

This is better than nothing, but it is 1. very slow (recursive) and 2. as I said doesn't really calculate the line thru the 2. point.

Is there a mathematical brain outside which could help me?

2 Answers2

0
static bezier From3Points(const Point2D &a, const Point2D &b, const Point2D &c)
{
    bezier result;
    result.PushPoint2D(a);

    Point2D middle;
    middle.x = 2*b.x - a.x/2 - c.x/2;
    middle.y = 2*b.y - a.y/2 - c.y/2;
    result.PushPoint2D(middle);

    result.PushPoint2D(c);
    return result;
}

Untested, but should return a bezier curve where at t=0.5 the curve passes through point 'b'.

Additionally (more untested code ahead), you can calculate your points using bernstein basis polynomials like so.

static int binomialcoefficient (int n, int k)
{
    if (k == 0)
        return 1;
    if (n == 0)
        return 0;

    int result = 0;
    for (int i = 1; i <= k; ++i)
    {
        result += (n - (k - i))/i;
    }
    return result;
}

static double bernstein (int v, int n, double t)
{
    return binomialcoefficient(v,n) * pow(t,v) * pow(1 - t,n - v);
}

Point2D GetPoint (double t)
{
    Point2D result;
    result.x = 0;
    result.y = 0;

    for (int i = 0; i < points.size(); ++i)
    {
        double coeff = bernstein (i,points.size(),t);
        result.x += coeff * points[i].x;
        result.y += coeff * points[i].y;
    }

    return result;
}
Tocs
  • 756
  • 3
  • 16
  • Shouldn't you have another `PushPoint2D` in there somewhere? A Bezier requires 4 points, unless you're using the rarer [Quadradic](http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_curves) form rather than a Cubic. – Mark Ransom Feb 21 '13 at 20:26
  • Bezier's are just recursively defined? I don't see why you need 4 points. So long as you supply the proper Bernstein basis polynomials it shouldn't matter. The quadratic Bezier curve is (1-t)^2P0 + 2(1-t)tP1 + t^2P2 And if you look at the bernstein polynomials B0,2 = (1 - x)^2 B1,2 = 2(1-x)x and B2,2 = x^2 They match, so there shouldn't be a problem regardless of pointcount. Op however is doing the recursive definition it appears, he would benifit from switching to the more straightforward sum definition. – Tocs Feb 21 '13 at 20:45
  • @infact, yes theoretically a Bezier can be defined with as many points as you'd like. However the most commonly used form is the Cubic with 4 points; if more than 4 points are needed then the curve is broken into multiple independent segments with the end point of one defining the start point of the next. – Mark Ransom Feb 21 '13 at 22:03
  • @MarkRansom I'm not sure how that would work, you wouldn't get a smooth curve at those intersections... Where are you getting this from? – Tocs Feb 22 '13 at 00:41
  • @Tocs everything from Adobe works that way, for example Postscript/PDF and Photoshop. You get a smooth curve by ensuring that the control points on either side of an endpoint are 180 degrees from each other, and ideally at the same distance. The only real world example I know for Quadratic Beziers is TrueType fonts. – Mark Ransom Feb 22 '13 at 01:26
  • @Tocs hi Tocs, I wanted to try the Bernstein code, but I don't know how you mentioned to use it. I've tried: double curve::GetBernsteinPoint(double x) { Point2D result; result.x = 0; result.y = 0; for (int i = 0; i < (int)points.size(); ++i) { double coeff = bernstein (i,points.size(),x); result.x += coeff * points[i].x; result.y += coeff * points[i].y; } return result.y; } and the call: GetLogicalYAtX(double x) ..... return cv.GetBernsteinPoint(x); But it only gives me lines instead of a curve. – Tobias Findeisen Feb 22 '13 at 23:57
  • It's not that the end points of the piece-wise curve coincide, but rather that the derivatives are the same at that point. Also, in GUI modeling packages I think you get little widgets to control the tangents to the curve. Artists/designers seem to like this. –  Mar 07 '13 at 09:35
0

Thank you TOCS (Scott) for providing your code, I will also try it if I have some time. But what I have tested now is the hint by INFACT (answer 3): This "Largrange polynomials" are very very close to what I am searching for:

I have renamed my class bezier to curve, because I have added some code for lagrangeinterpolation. I also have added 3 pictures of graphical presentation what the code is calculation.

In Picture 1 you can see the loose middle point of the old bezier-function.

In Picture 2 you can now see the going thru all-points-result of lagrange interpolation.

In Picture 3 you can see the only problem, or should I say "thing which I also need to be solved" (anyway its the best solution till now): If I move the middle point, the curve to going to fast, to quick to the upper or lower boundaries). I would like it to go more smoothely to the upper and lower. So that it looks more logarithm-function like. So that it doesn't exeed the y-boundaries between 0 and 1 too soon.

Now my code looks like this:

curve::curve(void)
{
}

void curve::PushPoint2D(Point2D point)
{
    points.push_back(point);
}

Point2D curve::GetPoint( double x )
{
//GetPoint y for x with bezier-mathematics...

//was the only calculating function in old class "bezier"
//now the class is renamed "curve"
int i;
Point2D p;

p.x=0;
p.y=0;

if( points.size() == 1 ) return points[0];
if( points.size() == 0 ) return p;

curve b;
for (i=0;i<(int)points.size()-1;i++)
{
    p.x = ( points[i+1].x - points[i].x ) * x + points[i].x;
    p.y = ( points[i+1].y - points[i].y ) * x + points[i].y;
    if (points.size()<=2) return p;
    b.PushPoint2D(p);
}

return b.GetPoint(x);
}

//THIS IS NEW AND VERY VERY COOL
double curve::LagrangeInterpolation(double x)
{
double y = 0;

for (int i = 0; i <= (int)points.size()-1; i++)
{
    double numerator = 1;
    double denominator = 1;

    for (int c = 0; c <= (int)points.size()-1; c++)
    {
        if (c != i)
        {
            numerator *= (x - points[c].x);
            denominator *= (points[i].x - points[c].x);
        }
    }

    y += (points[i].y * (numerator / denominator));

}

return y;
}

curve::~curve(void)
{
}


double GetLogicalYAtX(double x)
{
curve cv;
Point2D p;

p.x=0; //always left edge
p.y=y1; //now by var
cv.PushPoint2D(p);

p.x=x2; //now by var
p.y=y2; //now by var
cv.PushPoint2D(p);

p.x=1; //always right edge
p.y=y3; //now by var
cv.PushPoint2D(p);

//p=cv.GetPoint(x);

//return p.y;

return cv.LagrangeInterpolation(x);
}

Old bezier curve Lagrange, cool but... ...clipping so soon...

Do you have any ideas how I could get the new solution a little bit more "soft"? So that I can move the 2. Point in larger areas without the curve going out of boundaries? Thank you