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.
What happens when I currently adjust slider.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With