1

I have a point (circled in pink) which has a known X co-ordinate and a known Y co-ordinate but the Y co-ordinate is incorrect. It is currently resting upon the point where the target bezier curve (the curve partially in a white square) would be if it were a line between its two points. I need to calculate the correct Y co-ordinate for my circled point so that it ends up on the red cross.

enter image description here

I am a C# programmer and not a mathematician so if this answer could be expressed in code or with an explanation of each parameter involved in the equation then this would be of most meaning to me. I may even end up extending Blender with a Python script for this purpose if I can get the answer I'm after.

UPDATE: I have amended the question and its image to better express the problem I'm having. I simply want a means of finding out what the Y co-ordinate is on the red cross. Having thought about this it may be trigonometry and have nothing to do with bezier curves but I will need to have a means of figuring out the Y co-ordinate for a marker placed at any point on that curve, not just on the first segment/line of it.

Sphynx
  • 135
  • 2
  • 12
  • some more information is required - do you have a point (x,y) that you would like to give a different `y` value, or do you have a *curve* that (x,y) lies on, and you would like to change the *entire curve* so that for at least your (x,y) coordinate, the `y` coordinate now has the desired target value? Because the first case doesn't seem very meaningful, whereas the second case requires some more details about what kind of curve your curve is, what kind of curve the target curve is, whether more points need to line up, etc. – Mike 'Pomax' Kamermans Mar 22 '16 at 19:35
  • It is the first case I need; the point in the pink circle `(1,-0.6)` which needs to become `(1,?)` but I don't know what the `Y` value of `?` is. Why is that not meaningful? I have a series of these points I need to do the same thing for in order to make the bottom half of a mesh I have match up with the top half I've already created. – Sphynx Mar 22 '16 at 19:54

2 Answers2

1

True maths answer: if you want precise answers, you need maths, the end; Bezier curves are parametric curves and can have multiple y values for a single x value (and vice versa) so you're not going to find those without actually understanding what you're doing.

The easier way to do this, imprecisely, as a programming exercise is to simply build coordinate LookUp Tables for each curve (e.g. {t=0 -> {x:...,y:...}, t=0.01 -> {x:...y:...}, ...}), and then simply search your LUT for a given x or y value in order to find (possibly multiple) corresponding y or x values. If you need y values for some x, you sort your LUT on x, and then do a binary search to find best matches. If you need x for some y, you sort on y and do the same.

Nice and simple.

But, if you want precise solutions, which as a programmer you really should care about, then what you want is to reorient the curve along the "line" you're trying to find values for (for instance, "all y values for some x" means you're looking for all intersections between the curve and the line (x=a, y=-inf)--(x=a,y=inf)) and then you simply check which t values you get when you do intersection detection.

If you want the actual true answers here, have a look at this Primer on Bezier Curves' section on intersection detection between curves and lines (which in turn relies on the section about root finding). If you're dealing with cubic curves, you'll need to understand how to align curves so that finding intersections is reduced to root finding, after which you need to run Cardano's algorithm to find the (possibly three) values you're interested in.

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
  • When you say "Bezier curves are parametric curves and can have multiple y values for a single x value" is that because bezier curves have a resolution (my curve is a 16-point line)? That's to say, given a fixed resolution, won't there be just 1 y value for an x value? – Sphynx Mar 21 '16 at 22:09
  • no, it's because they are *parametric functions*. Have a look at [this explanation](http://pomax.github.io/bezierinfo/#explanation), but the gist is that there is no direct relation between one `x` value and one `y` value in parametric functions at all, so we can't ask about getting "the" `y` value for some `x` value. There can be one, two, three, or even infinitely many. The simplest case is a circle, where `x=cos(t)` and `y=sin(t)`. What is "the" `y` value for `x=0`? There isn't one. There are two. The same, but even more extreme, holds for Bezier curves: you can have up to three `y` values. – Mike 'Pomax' Kamermans Mar 21 '16 at 22:18
  • If `x == 0` in my example then `y == 0`; I don't understand how `y` can have more than one value on a curve that's already been calculated and isn't going to change. If the first angled point on my curve had the co-ordinates of `1,-0.2` and my point I was trying to move had the `x` value of `1` then the correct `y` value would be `-0.2` since it would then lie directly on the curve's line. What else could `y` be given this logic? – Sphynx Mar 21 '16 at 22:43
  • Then you need to go back to your question, reread it, notice it doesn't make a lot of sense, and rephrase it so that people don't waste time answering a question with information that isn't relevant. You're being mighty vague, and your graphic isn't very clear, so I'll be happy to look at it again if you're willing to take the time to clean it up and make it much, much clearer what your current situation is, what you want, what you tried, and how that failed. Adding code isn't a bad idea, and removing bits that don't matter (especially in your image) will be very helpful to others. – Mike 'Pomax' Kamermans Mar 22 '16 at 01:02
  • @Sphynx Bezier curve may have form of 'S' letter or '2' digit. Note that vertical line may cross these figures upto three times. – MBo Mar 22 '16 at 05:55
  • @Mike'Pomax'Kamermans I'm sorry my original question mislead you and it seems Spektre also. I think what I am after is much simpler than what you have provided (although I do seek to increase my understanding of linear algebra anyway so this information won't go to waste). I think my problem is a basic trigonometry one, I just need to map one curve to another but I cannot do so as the points don't line up. – Sphynx Mar 22 '16 at 18:32
0

Bezier curves are parametric

x(t)=fx(t)
y(t)=fy(t)

Where (x(t),y(t)) is point on Bezier curve, t is parameter and fx(),fy() are polynomial functions.

That means Bezier curves are not necessarily functions (can have multiple y values per single x). So you need to find all parameters t for such the x(t)=x0 where x0 is your x-coordinate.

As @Mike 'Pomax' Kamermans mentions in his answer you can use LUT or compute the roots of fx(t)-x0=0 (x0 moved to the other side of the equation x0 = fx(t)) polynomial algebraically.

There is also "simpler" option with use of approximation search (similar to binary search but usable also on non monotonic functions) which does not require too high math knowledge (but is slower of coarse). For example something like this C++ code:

double x0,y0;           // your point x0 valid y0 not yet
double ax0,ax1,ax2,ax3; // cubic Bezier fx() polynomial coefficients
double ay0,ay1,ay2,ay3; // cubic Bezier fy() polynomial coefficients
double ee,x,t,tt,ttt;
approx aa;
for (aa.init(0.0,1.0,0.025,6,&ee); !aa.done; aa.step()) // search t
{
  t = aa.a;
  tt = t*t;
  ttt = tt*t;
  x = ax0 + ax1*t + ax2*tt + ax3*ttt; // compute the x=fx(t)
  ee = fabs(x-x0);              // compute error of solution for the approximation search
}
// here aa.a holds found `t0`
t = aa.a;
tt = t*t;
ttt = tt*t;
y0 = ay0 + ay1*t + ay2*tt + ay3*ttt; // compute the final y0=fx(t0)

If you need all the possible points then you need to recursively subdivide the search interval t=<0.0,1.0> to <0.0,t0) and (t0,1.0> until there is no new found solution.

[edit1] some more info (requested by Mike 'Pomax' Kamermans)

As 2D Bezier curves are not necessarily functions there may be more y values for single x coordinate. Like on this example:

example

The numbered blue points are control points of rendered 2D cubic Bezier curve. The yellow ones are found solutions with the recursion described above. Here some C++ example of this:

double x0;              // input x coordinate
List<double> y0;        // output y coordinates

double ax0,ax1,ax2,ax3; // cubic coefficients
double ay0,ay1,ay2,ay3;
double px0,px1,px2,px3; // control points
double py0,py1,py2,py3;
//---------------------------------------------------------------------------
bool find_point(double t0,double t1,int layer)
    {
    approx aa;
    double ee,x,y,t,tt,ttt,dt;
    const double _zero=1e-4;    
    dt=0.025*(t1-t0); // approximation search step 
    if (dt<=_zero) return false; // stop if too small interval to search to avoid stack overflows
    if (layer>10) return false; // stop if too high recursion layer to avoid stack overflows (this also limits the max found solutions
    for (aa.init(t0,t1,dt,6,&ee); !aa.done; aa.step()) // search t
        {
        t  = aa.a;
        tt = t*t;
        ttt= tt*t;
        x  = ax0 + ax1*t + ax2*tt + ax3*ttt;    // compute the x=fx(t)
        ee = fabs(x-x0);                        // compute error of solution for the approximation search
        }
    // check the error of found solution
    if (aa.e0>_zero) return false;
    // here aa.aa holds found `t0`
    t  = aa.aa;
    // check bounds can cross the border a bit
    if (t<t0) return false;
    if (t>t1) return false;
    // add new solution
    tt = t*t;
    ttt= tt*t;
    y  = ay0 + ay1*t + ay2*tt + ay3*ttt;        // compute the final y0=fx(t0)
    y0.add(y);                                  // add to list of solutions
    // recursion to check for other solutions by dividing the interval by found solution
    if (t0<t-dt) find_point(t0,t-dt,layer+1);
    if (t1>t+dt) find_point(t+dt,t1,layer+1);
    }
//---------------------------------------------------------------------------
// here usage
void test()
 {
 // just compute the Bezier cubic polynomials from control points
 ax0=                              (    px0);
 ax1=                    (3.0*px1)-(3.0*px0);
 ax2=          (3.0*px2)-(6.0*px1)+(3.0*px0);
 ax3=(    px3)-(3.0*px2)+(3.0*px1)-(    px0);
 ay0=                              (    py0);
 ay1=                    (3.0*py1)-(3.0*py0);
 ay2=          (3.0*py2)-(6.0*py1)+(3.0*py0);
 ay3=(    py3)-(3.0*py2)+(3.0*py1)-(    py0);
 // Find the points from mouse x0 coordinate
 y0.num=0;              // clear found solutions list
 find_point(0.0,1.0,0); // recursively found solutions on interval t=<0,1> ,as highest recursion level 0
 }

This have a problem with vertical lines because there is infinite number of solutions there ... It will found just few of them instead. Otherwise is this safe to use. (added many checks so it should run smoothly)

Armen Michaeli
  • 8,625
  • 8
  • 58
  • 95
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • This code may need some explaining as it isn't really clear what `aa.init()` does, what the iteration variable is, what `a.done` does, and what `aa.step()` does. It'd be a good idea to rewrite this code a little so that it's minimal and complete. And you may need to explain when this solution does not work, because there are an infinite number of curves for which this code will not find all solutions, because it can only find one, even if there are two or three. – Mike 'Pomax' Kamermans Mar 22 '16 at 13:43
  • @Mike'Pomax'Kamermans all about approx inner workings an usage is explained in the linked question. And also if you read the last sentence here it contains description how to obtain other points. But you're right I should do something more clearer I am thinking about adding **QA** about mine approx search as I am referencing it a lot lately. – Spektre Mar 22 '16 at 15:00
  • You may want to rework that question and your edits to answer it as a webpage on the topic ("approximation search" is not an easily googled term) so you can link people to the whys and hows around it, and then send people there (that's how https://pomax.github.io/bezierinfo was born). Making this answer a little more explicit will be quite valuable, especially given the differences between python and C++ (there is no reasonable expectation that someone who writes python understands what's going on in a pointer/reference-heavy bit of C++) – Mike 'Pomax' Kamermans Mar 22 '16 at 15:13
  • @Mike'Pomax'Kamermans see [How approximation search works](http://stackoverflow.com/q/36163846/2521214) – Spektre Mar 22 '16 at 19:53
  • I have to agree with the first comment: this should be a post on the web, rather than a "question" on SO that is actually an explanation – Mike 'Pomax' Kamermans Mar 22 '16 at 21:30
  • @Spektre I've just had another look at this post and the image with the 3 points in yellow has clarified to me how 3 `Y` values are possible. I'll go through this in more depth this weekend as I need this solution for a personal project of mine I need to complete rather soon. It's true what they say about a picture saying 1000 words! – Sphynx Mar 23 '16 at 15:38