10

enter image description here

I drew the above BezierPath by doing: // location is where the user touches screen. // location will be the maximum of the graph CGPoint origin = CGPointMake(xStart, 620.0); CGPoint endpt = CGPointMake(xEnd, 620.0); CGPoint midpt1 = midPointForPoints(origin, location); CGPoint midpt2 = midPointForPoints(location, endpt);

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:origin];
[path addQuadCurveToPoint:location controlPoint:CGPointMake(midpt1.x, midpt1.y+50)];
[path addQuadCurveToPoint:endpt controlPoint:CGPointMake(midpt2.x, midpt2.y+50)];

[shapeLayer setPath:path.CGPath];

Now, I want to retrieve y-coordinates for certain x-coordinates that lie on the path. For example, given x = 0.0, I want to get y = 0.0, or given x = 300.0, y = 50.0.

I looked at some references like this question and sample code I am still not sure. Update: basically, I want to do something like this. enter image description here

Update: Following @Fang's advice:

Given the equation

X = (1-t)^2*X0 + 2*t*(1-t)*X1 + t^2 *X2

I solve for t

t = ((2.0 * x0 - x1) + sqrt(((-2.0 * x0 + x1) ** 2.0) 
- ((4 * (x0 - 2.0 * x1 + x2)) * (x0 - x)))) / (2.0 * (x0 - 2.0 * x1 + x2))

or

t = ((2.0 * x0 - x1) - sqrt(((-2.0 * x0 + x1) ** 2.0) 
- ((4 * (x0 - 2.0 * x1 + x2)) * (x0 - x)))) / (2.0 * (x0 - 2.0 * x1 + x2))

Using this value, find Y that corresponds to X (we used X to find the above t value)

Y = (1-t)^2*Y0 + 2*t*(1-t)*Y1 + t^2 *Y2

Following the above equation, I am supposed to get the y-value of the point that lies on the Bezier curve but I get a point that's far from the right one. Any further help will be very much appreciated..

Concern: I think one possible problem is that I am calling addQuadCurveToPoint() twice with two control points instead of once with two control points. Does it mean I draw two Bezier paths and combine them? I am also looking at this to see what's wrong with my computation and the only difference seems to be that he uses two control points when calling addQuadCurveToPoint().

Update after Fang's intense consulting:

- (float)getYFromBezierPath:(float)x location:(CGPoint)location ctrlpt1:(CGPoint)ctrlpt1 ctrlpt2:(CGPoint)ctrlpt2 endpt:(CGPoint)endpt {
    float yVal;
    float tVal;
    if (x <= location.x) {
        tVal = [self getTvalFromBezierPath:x x0Val:0.0 x1Val:ctrlpt1.x x2Val:location.x];
        yVal = [self getCoordFromBezierPath:tVal origin:0.0 p1Val:ctrlpt1.y p2Val:location.y];
    } else {
        // THIS PART IS THE PROBLEM //
        tVal = [self getTvalFromBezierPath:x x0Val:location.x x1Val:ctrlpt2.x x2Val:endpt.x];
        yVal = [self getCoordFromBezierPath:tVal origin:location.y p1Val:ctrlpt2.y p2Val:endpt.y];
    }
    return yVal;
}

- (float)getTvalFromBezierPath:(float)x x0Val:(float)x0 x1Val:(float)x1 x2Val:(float)x2 {
    float tVal = (x-x0)/(2*(x1-x0));
    return tVal;
}

- (float)getCoordFromBezierPath:(float)t origin: (float)origin p1Val: (float)p1 p2Val: (float)p2 {
// tVal = (sqrt((-2.0 * x * x1) + (x * x0) + (x * x2) + pow(x1, 2) - (x0 * x2)) + x0 - x1) / (x0 - (2.0 * x1) + x2);
    return (pow((1-t),2) * origin) + (2 * t * (1-t) * p1) + (pow(t,2) * p2);
}

Last question: for the second Bezeir path, y-value should decrease as t-value increases. Right now, y-value keeps increasing. How should I fix this? After intensive debugging I haven't found why this is happening because everything conforms to the document.

Community
  • 1
  • 1
Maximus S
  • 10,759
  • 19
  • 75
  • 154
  • 2
    Nice video here showing the mechanics of how Bezier paths are actually drawn: http://vimeo.com/106757336 – matt Nov 13 '14 at 01:50
  • This is such a great video. Anyone who uses Bezier path must watch this. thanks @matt – Maximus S Nov 13 '14 at 03:10
  • Are you sure you solved for t correctly here? It doesn't quite feel right, though it may just be a different factorization. http://www.wolframalpha.com/input/?i=x+%3D+%281-t%29%5E2*X_0+%2B+2*t*%281-t%29*X_1+%2B+t%5E2+*X_2+solve+for+t – Rob Napier Nov 13 '14 at 05:33
  • I am not sure how to check whether I solved for t right. This is my python version: https://gist.github.com/woniesong92/c7f79c812f1ae8be21f5 – Maximus S Nov 13 '14 at 05:38
  • also, when you say you don't get the correct point, how far off is it? Does it ever work (for straight lines, or simpler curves)? Bezier can be very sensitive to small errors in t at different points in the curve. – Rob Napier Nov 13 '14 at 05:39
  • First of all, thanks for helping me @RobNapier. The first problem that I recognize is that t-value is not in the bounds [0,1] when X is greater than some value (midpoint of the screen). I would love to get help from you in a chat too if that's possible. – Maximus S Nov 13 '14 at 05:43
  • sure; that's probably easiest. – Rob Napier Nov 13 '14 at 05:46
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/64835/discussion-between-maximus-s-and-rob-napier). – Maximus S Nov 13 '14 at 05:48
  • @RobNapier There is only one last problem left. Please take a look at it when you have a few minuts and tell me if you have an idea what's going on.. – Maximus S Nov 13 '14 at 08:26
  • 1
    I'd validate that location.y > endpt.y here (I know you intend it to be; just make sure it really is at this point in the code) and that 0 – Rob Napier Nov 13 '14 at 13:19
  • @RobNapier Your hint was awesome! The problem was in `yVal = [self getCoordFromBezierPath:tVal origin:0.0 p1Val:ctrlpt1.y p2Val:location.y];` origin should've been `startpt.y` instead of `0.0` because the graph starts from middle of the screen. – Maximus S Nov 13 '14 at 14:24

2 Answers2

5

It should be possible to get points along a Bezier path as addQuadCurveToPoint is to add a quadratic Bezier segment into the path. So, the three control points of your first quadratic Bezier curve are (refer to the code piece in original post)

P(0) = origin
P(1) = (midpt1.x, midpt1.y+50)
P(2) = location

You can compute as many points on this quadratic Bezier curve as you want by varying the parameter t from 0 to 1 by any small increment value as

C(t) = (1-t)^2*P(0) + 2*t*(1-t)*P(1) + t^2 *P(2)

To get the Y value from a given X value, you will have to solve for the t value from the given X value from this quadratic polynomial of t:

X = (1-t)^2*X0 + 2*t*(1-t)*X1 + t^2 *X2

where X0, X1 and X2 are the X coordinates of P(0), P(1) and P(2), which means X0=origin.x, X1=midpt1.x and X2=location.x.

From this, we can obtain a quadratic equation

(X0-2*X1+X2)t^2 + 2(X1-X0)*t + (X0-X) = 0

You can solve for t using the quadratic formula. If your X0, X1 and X2 values happens to make the coefficient of t^2 term zero, you can solve for t directly as t = (X-X0)/(2*(X1-X0)).

Once you have the t value, you can easily evaluate the corresponding Y value.

fang
  • 3,473
  • 1
  • 13
  • 19
  • Hey @fang, I am still trying to understand your answer. Let me ask you questions if I get stuck. Just to confirm, this is a way to get the points that lie on the graph, right? I just updated my question to better explain my need. – Maximus S Nov 13 '14 at 01:29
  • Yes. Evaluating the quadratic Bezier curve is the way to get points on the curve unless addQuadCurveToPoint() does not create quadratic Bezier curve as it claims to be. – fang Nov 13 '14 at 01:32
  • That is just perfect. Let me do the math and see if it works for me! – Maximus S Nov 13 '14 at 01:33
  • I think I get it. Once you've found the t-value, you set Y = (1-t)^2*Y0 + 2*t*(1-t)*Y1 + t^2 *Y2 ! – Maximus S Nov 13 '14 at 03:29
  • I just computed a bunch of tValues and sometimes the value is not [0, 1]. For example, if X = 358.0, X0 = 0.0, X1 = 179.0, X2 = 364.0, t = 1.88 or -31.71. What should I do in this case? I updated the question. – Maximus S Nov 13 '14 at 05:17
  • 1
    You should at least get a root that is within [0, 1] if X value is within [X0, X2]. In your example, X=358, which is very close to X2 (364), so the t value should be very close to 1. Actual computation shows that the two roots for t are t=0.9837796 and t=-60.650446 and you choose t=0.9837796, which is very close to 1. – fang Nov 13 '14 at 05:42
  • Thanks, I guess that means my equation that I solved for `t` is wrong? This is my gist: https://gist.github.com/woniesong92/c7f79c812f1ae8be21f5 and I am not sure which part is wrong. How did you solve for t? I wrote down my equation for t in my question. Did I make a mistake? – Maximus S Nov 13 '14 at 05:45
  • Like you said, I think my equation for t is wrong. I am checking on that. – Maximus S Nov 13 '14 at 05:53
  • I think the (2.0*x0-x1) should be 2.0*(x0-x1). Also, the -2.0*x0+x1 should be -2.0*(x0-x1). – fang Nov 13 '14 at 05:54
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/64838/discussion-between-maximus-s-and-fang). – Maximus S Nov 13 '14 at 06:04
  • this is my last question: for the second Bezeir path, I y-value should decrease as t-value increases. Right now, y-value increases. How should I fix this? After intensive debugging I haven't found why this is happening because everything conforms to the document. – Maximus S Nov 13 '14 at 08:07
  • 1
    For your last question, what are the actual values for x0Val, x1Val and x2Val for the 2nd Bezier segment? Do location.x, ctrlpt2.x and endpt.x happen to make the t^2 coefficient zero like that for the first Bezier segment? – fang Nov 13 '14 at 17:15
  • Hi fang I solved the problem. The problem was that I was using `0.0` as y value's origin, but it should've been `startpt.y` instead :) – Maximus S Nov 13 '14 at 17:19
1

CGPath is opaque data types, ie. in this case, we can only get the points on which we define on the creation, like eg. the graph you create, there are only three points that can be obtained.

Like the sample code, you obtain those points using CGPathApply. If you append below code after your codes, it will output 3 points.

    ...
    [shapeLayer setPath:path.CGPath];
    NSMutableArray *keyPoints = [NSMutableArray array];
    CGPathApply(path.CGPath, (__bridge void *)keyPoints, getPointsFromBezier);

    NSLog(@"Points = %@", points);
}

// copied from the sample code.
void getPointsFromBezier(void *info, const CGPathElement *element)
{
    NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;
    CGPathElementType type = element->type;
    CGPoint *points = element->points;
    if (type != kCGPathElementCloseSubpath)
    {
        if ((type == kCGPathElementAddLineToPoint) ||
            (type == kCGPathElementMoveToPoint))
            [bezierPoints addObject:VALUE(0)];
        else if (type == kCGPathElementAddQuadCurveToPoint)
            [bezierPoints addObject:VALUE(1)];
        else if (type == kCGPathElementAddCurveToPoint)
            [bezierPoints addObject:VALUE(2)];
    }
}

So, in short, you cannot get every single coordinate on that graph like you required given its x/y counterpart.

Peter
  • 1,109
  • 7
  • 5
  • Thanks Peter, but getting such points is not what I want. I want to find a set of points that lie on the path, so I can get the y-coordinate when I have an x-coordinate. – Maximus S Nov 11 '14 at 06:35
  • Yes, I just added that explanation. Basically, you cannot, even NSPath, its Cocoa counterpart, does not provide this capability – Peter Nov 11 '14 at 06:36
  • Another way is, if you create your CGPath using multiple sample points that generated using your mathematical formula (define every single x,y that is possible), then you can access these points. – Peter Nov 11 '14 at 06:38