سؤال

I have an app that draws a bezier curve in a UIView and I need to find the X intersect when I set a value for Y. First, as I understand, there isn’t a way to find a point directly of a UIBezierPath but you can locate a point of a CGPath.

First, if I “stroke” my UIBezierPath (as I have done in my code) is this actually creating a CGPath or do I need to take further steps to actually convert this to a CGPath?

Second, I want to find the curves intersect at X by providing the value for Y.

My intention is to automatically calculate X for the given value of Y as the user moves the slider (which moves the curve left or right respectively).

My starting display.

My starting display

What happens when I currently adjust slider.

What happens when I currently adjust slider

What I want my display too look like.

What I want my display too look like

GraphView.h

#import <UIKit/UIKit.h>

@interface GraphView : UIView
{
    float adjust;
    int x;
    int y;
}

- (IBAction)sliderChanged:(id)sender;
- (IBAction)yChanged:(id)sender;

@property (weak, nonatomic) IBOutlet UISlider *sliderValue;
@property (weak, nonatomic) IBOutlet UITextField *xValue;
@property (weak, nonatomic) IBOutlet UITextField *yValue;

@end

GraphView.m

#import "GraphView.h"

@interface GraphView ()


@end

@implementation GraphView

@synthesize sliderValue, xValue, yValue;

- (id)initWithCoder:(NSCoder *)graphView
{
    self = [super initWithCoder:graphView];
    if (self) {
        adjust = 194;
        y = 100;
    }
    return self;
}

- (IBAction)sliderChanged:(id)sender
{
    adjust = sliderValue.value;
    // Calcualtion of the X Value and setting of xValue.text textField goes here
    [self setNeedsDisplay];
}

- (IBAction)yChanged:(id)sender
{
    y = yValue.text.intValue;
    [self setNeedsDisplay];
    [self resignFirstResponder];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch * touch = [touches anyObject];
    if(touch.phase == UITouchPhaseBegan) {
        y = yValue.text.intValue;
        [self setNeedsDisplay];
        [yValue resignFirstResponder];
    }
}

- (void)drawRect:(CGRect)rect
{
    UIBezierPath *lines = [[UIBezierPath alloc] init];
    [lines moveToPoint:CGPointMake(0, y)];
    [lines addLineToPoint:CGPointMake(200, y)];
    [lines addLineToPoint:CGPointMake(200, 280)];
    [lines setLineWidth:1];
    [[UIColor redColor] setStroke];
    float dashPattern[] = {2, 2};
    [lines setLineDash:dashPattern count:2 phase:0.0];
    [lines stroke];

    UIBezierPath *curve = [[UIBezierPath alloc] init];
    [curve moveToPoint:CGPointMake(0, 280)];
    [curve addCurveToPoint:CGPointMake(280, 0) controlPoint1:CGPointMake(adjust, 280) controlPoint2:CGPointMake(adjust, 0)];
    [curve setLineWidth:2];
    [[UIColor blueColor] setStroke];
    [curve stroke];

}

@end
هل كانت مفيدة؟

المحلول

A cubic Bézier curve is defined by 4 points

P0 = (x0, y0) = start point,
P1 = (x1, y1) = first control point,
P2 = (x2, y2) = second control point,
P3 = (x3, y3) = end point,

and consists of all points

x(t) = (1-t)^3 * x0 + 3*t*(1-t)^2 * x1 + 3*t^2*(1-t) * x2 + t^3 * x3
y(t) = (1-t)^3 * y0 + 3*t*(1-t)^2 * y1 + 3*t^2*(1-t) * y2 + t^3 * y3

where t runs from 0 to 1.

Therefore, to calculate X for a given value of Y, you first have to calculate a parameter value T such that 0 <= T <= 1 and

 Y = (1-T)^3 * y0 + 3*T*(1-T)^2 * y1 + 3*T^2*(1-T) * y2 + T^3 * y3      (1)

and then compute the X coordinate with

 X = (1-T)^3 * x0 + 3*T*(1-T)^2 * x1 + 3*T^2*(1-T) * x2 + T^3 * x3      (2)

So you have to solve the cubic equation (1) for T and substitute the value into (2).

Cubic equations can be solved explicitly (see e.g. http://en.wikipedia.org/wiki/Cubic_function) or iteratively (for example using the http://en.wikipedia.org/wiki/Bisection_method).

In general, a cubic equation can have up to three different solutions. In your concrete case we have

P0 = (0, 280), P1 = (adjust, 280), P3 = (adjust, 0), P4 = (280, 0)

so that the equation (1) becomes

Y = (1-T)^3 * 280 + 3*T*(1-T)^2 * 280

which simplifies to

Y/280 = 1 - 3*T^2 + 2*T^3    (3)

The right hand side of (3) is a strictly decreasing function of T in the interval [0, 1], so it is not difficult to see that (3) has exactly one solution if 0 <= Y <= 280. Substituting this solution into (2) gives the desired X value.

نصائح أخرى

It took me a while but the code below is how I solved finding a point on a bezier curve. The math only finds one of the potential 3 values so I suspect if there is more than one it will fail, but in my circumstance my bezier should only ever have one solution since my curve should never cross the same X or Y plane more than once. I wanted to share what I have and I welcome any questions, comments, or suggestions.

#import "Calculation.h"

@implementation Calculation

@synthesize a, b, c, d, xy;

- (float) calc
{

    float squareRootCalc =
    sqrt(
    6*pow(xy,2)*b*d
    +4*a*pow(c,3)
    -3*pow(b,2)*pow(c,2)
    +9*pow(xy,2)*pow(c,2)
    -6*a*c*b*d
    +6*a*xy*c*b
    -18*pow(xy,2)*b*c
    +6*a*pow(xy,2)*c
    -12*a*xy*pow(c,2)
    -2*pow(a,2)*xy*d
    +pow(a,2)*pow(d,2)
    +4*pow(b,3)*d
    +pow(xy,2)*pow(d,2)
    -4*pow(b,3)*xy
    -4*pow(c,3)*xy
    +pow(a,2)*pow(xy,2)
    +6*c*b*d*xy
    +6*a*c*d*xy
    +6*a*b*d*xy
    -12*pow(b,2)*d*xy
    +6*xy*c*pow(b,2)
    +6*xy*b*pow(c,2)
    -2*a*pow(xy,2)*d
    -2*a*xy*pow(d,2)
    -6*c*d*pow(xy,2)
    +9*pow(xy,2)*pow(b,2)
    -6*a*pow(xy,2)*b)
    ;

    float aCalc = 24*c*d*xy + 24*a*pow(c,2) - 36*xy*pow(c,2) + 4 * squareRootCalc * a;

    float bCalc = -12 * squareRootCalc * b;

    float cCalc = 12 * squareRootCalc * c;

    float dCalc = -4 * squareRootCalc * d;


    float xyCalc =
    24*xy*a*b
    -24*xy*b*d
    -12*b*a*d
    -12*c*a*d
    -12*c*b*d
    +8*xy*a*d
    +8*pow(b,3)
    +8*pow(c,3)
    +4*pow(a,2)*d
    +24*pow(b,2)*d
    -4*xy*pow(a,2)
    -4*xy*pow(d,2)
    +4*a*pow(d,2)
    -12*c*pow(b,2)
    -12*b*pow(c,2)
    -12*a*b*c
    -24*xy*a*c
    +72*xy*c*b
    -36*xy*pow(b,2)
    ;

    float cubeRootCalc = cbrt(aCalc + bCalc + cCalc + dCalc + xyCalc);

    float denomCalc = (a-3*b+3*c-d);

    float secOneCalc = 0.5 * cubeRootCalc / denomCalc;

    float secTwoCalc = -2 * ((a*c - a*d - pow(b,2) + c*b + b*d - pow(c,2)) / (denomCalc * cubeRootCalc));

    float secThreeCalc = (a - 2*b + c) / denomCalc;

    return secOneCalc + secTwoCalc + secThreeCalc;


}

- (Calculation *) initWithA:(float)p0 andB:(float)p1 andC:(float)p2 andD:(float)p3 andXy:(float)xyValue
{
    self = [super init];

    if (self) {
        [self setA:p0];
        [self setB:p1];
        [self setC:p2];
        [self setD:p3];
        [self setXy:xyValue];
    }
    return self;
}

- (void) setA:(float)p0 andB:(float)p1 andC:(float)p2 andD:(float)p3 andXy:(float)xyValue
{
    [self setA:p0];
    [self setB:p1];
    [self setC:p2];
    [self setD:p3];
    [self setXy:xyValue];
}

@end
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top