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.
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.
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.
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.
Related
I'm using UIBezierPath to draw lines(in multiple angles) with two points, but I want to draw lines a little shorter than the distance between the two points.
I tried the following codes to find a point between the two points:
let x3 = x2 + 0.9 * (x1 - x2);
let y3 = y2 + 0.9 * (y1 - y2);
It works in 1 or 2 angles but fails in others. How can I get the correct point? Thanks.
=== Edited ===
By now I got some idea from the search, but I still cannot get it works
Get the distance between the two points, and then minus 15, since I want it shorter
let distance = sqrt(pow((p2.x - p1.x), 2) + pow((p2.y - p1.y), 2)) - 15
Get the line angle:
let angle = (p2.y - p1.y) / (p2.x - p1.x)
Get point 3 with distance and angle:
let x = p1.x + (distance * cos(angle))
let y = p1.y - (distance * sin(angle))
It's a problem of wrong angle, function atan2 gives a correct angle value. Now the whole code work perfect.
let angle = atan2((p2.y - p1.y), (p2.x - p1.x))
I have this function which returns x and y position an just adding up degrees, it make objects to move around in circular movements like a satellite around a planet.
In my case it moves like an ellipse because I added +30 to dist.
-(CGPoint)circularMovement:(float)degrees moonDistance:(CGFloat)dist
{
if(degrees >=360)degrees = 0;
float x = _moon.position.x + (dist+30 + _moon.size.height/2) *cos(degrees);
float y = _moon.position.y + (dist + _moon.size.height/2) *sin(degrees);
CGPoint position= CGPointMake(x, y);
return position;
}
What I would like is to reverse this function, giving the x and y position of an object and getting back the dist value.
Is this possible?
If so, how would I go about achieving it?
If you have an origin and a target, the origin having the coordinates (x1, y1) and the target has the coordinates (x2, y2) the distance between them is found using the Pythagorean theorem.
The distance between the points is the square root of the difference between x2 and x1 plus the difference between y2 and y1.
In most languages this would look something like this:
x = x2 - x1;
y = y2 - y1;
distance = Math.SquareRoot(x * x + y * y);
Where Math is your language's math library.
float x = _moon.position.x + (dist+30 + _moon.size.height/2) *cos(degrees);
float y = _moon.position.y + (dist + _moon.size.height/2) *sin(degrees);
is the way you have originally calculated the values, so the inverse formula would be:
dist = ((y - _moon.position.y) / (sin(degrees))) - _moon.size.height/2
You could calculate it based on x as well, but there is no point, it is simpler based on y.
Given two CGPoints, lets say P1 and P2, I would like to:
find the line that connects them
find all CGPoints along that line
Any suggestions?
As far as now I got the following way to derive the various points:
1) I first of all start from the equation of a line defined as
following:
y = m * x + b
2) To find m:
m = (P2.y - P1.y) / (P2.x - P2.x)
Then, to find b we shall remember the quation of the line and we can apply this to any of the two points we have (P1 and P2), so:
b = y - m*x
becomes:
b = P1.y - m*P1.x (where we have both P1 and m)
This equation has an expection, which is the case when P1 and P2 are one above the other (P1.x == P2.x). In this case become an equation where the x does not vary, hence we have only one variable defined by the y part of the point.
[Cit. a good interactive guide to find/derive this type of answer is here.]
at least this is wrong:
m = (P2.y - P1.y) / (P2.x - P2.x)
it should be
m = (P2.y - P1.y) / (P2.x - P1.x)
or better
denom = P2.x - P1.x;
if (denom != 0) {
m = (P2.y - P1.y) / denom;
} else {
//handle special case
m = 0;
}
From your comment I see you want to make a collision detection of a line with a rectangualr boundin box.
So i would search for "line and rectangle intersection". The result gives you intersection points: if there is one, the line has hit the bounding rectangle.
Well the question says it all,
I know the function Line(), which draws line segment between two points.
I need to draw line NOT a line segment, also using the two points of the line segment.
[EN: Edit from what was previously posted as an answer for the question]
I used your solution and it performed good results in horizontal lines, but I still got problems in vertical lines.
For example, follows below an example using the points [306,411] and [304,8] (purple) and the draw line (red), on a image with 600x600 pixels. Do you have some tip?
I see this is pretty much old question. I had exactly the same problem and I used this simple code:
double Slope(int x0, int y0, int x1, int y1){
return (double)(y1-y0)/(x1-x0);
}
void fullLine(cv::Mat *img, cv::Point a, cv::Point b, cv::Scalar color){
double slope = Slope(a.x, a.y, b.x, b.y);
Point p(0,0), q(img->cols,img->rows);
p.y = -(a.x - p.x) * slope + a.y;
q.y = -(b.x - q.x) * slope + b.y;
line(*img,p,q,color,1,8,0);
}
First I calculate a slope of the line segment and then I "extend" the line segment into image's borders. I calculate new points of the line which lies in x = 0 and x = image.width. The point itself can be outside the Image, which is a kind of nasty trick, but the solution is very simple.
You will need to write a function to do that for yourself. I suggest you put your line in ax+by+c=0 form and then intersect it with the 4 edges of your image. Remember if you have a line in the form [a b c] finding its intersection with another line is simply the cross product of the two. The edges of your image would be
top_horizontal = [0 1 0];
left_vertical = [1 0 0];
bottom_horizontal = [0 1 -image.rows];
right_vertical = [1 0 -image.cols];
Also, if you know something about the distance between your points you could also just pick points very far along the line in each direction, I don't think the points handed to Line() need to be on the image.
I had the same problem and found out that there it is a known bug on 2.4.X OpenCV, fixed already for newer versions.
For the 2.4.X versions, the solution is to clip the line before plot it using cv::clipLine()
Here there is a function I did to myself that works fine on the 2.4.13 OpenCV
void Detector::drawFullImageLine(cv::Mat& img, const std::pair<cv::Point, cv::Point>& points, cv::Scalar color)
{
//points of line segment
cv::Point p1 = points.first;
cv::Point p2 = points.second;
//points of line segment which extend the segment P1-P2 to
//the image borders.
cv::Point p,q;
//test if line is vertical, otherwise computes line equation
//y = ax + b
if (p2.x == p1.x)
{
p = cv::Point(p1.x, 0);
q = cv::Point(p1.x, img.rows);
}
else
{
double a = (double)(p2.y - p1.y) / (double) (p2.x - p1.x);
double b = p1.y - a*p1.x;
p = cv::Point(0, b);
q = cv::Point(img.rows, a*img.rows + b);
//clipline to the image borders. It prevents a known bug on OpenCV
//versions 2.4.X when drawing
cv::clipLine(cv::Size(img.rows, img.cols), p, q);
}
cv::line(img, p, q, color, 2);
}
This answer is forked from pajus_cz answer but a little improved.
We have two points and we need to get the line equation y = mx + b to be able to draw the straight line.
There are two variables we need to get
1- Slope(m)
2- b which can be retrieved through the line equation using any given point from the two we have already after calculating the slope b = y - mx .
void drawStraightLine(cv::Mat *img, cv::Point2f p1, cv::Point2f p2, cv::Scalar color)
{
Point2f p, q;
// Check if the line is a vertical line because vertical lines don't have slope
if (p1.x != p2.x)
{
p.x = 0;
q.x = img->cols;
// Slope equation (y1 - y2) / (x1 - x2)
float m = (p1.y - p2.y) / (p1.x - p2.x);
// Line equation: y = mx + b
float b = p1.y - (m * p1.x);
p.y = m * p.x + b;
q.y = m * q.x + b;
}
else
{
p.x = q.x = p2.x;
p.y = 0;
q.y = img->rows;
}
cv::line(*img, p, q, color, 1);
}
I better explain my problem with an Image
I have a contour and a line which is passing through that contour.
At the intersection point of contour and line I want to draw a perpendicular line at the intersection point of a line and contour up to a particular distance.
I know the intersection point as well as slope of the line.
For reference I am attaching this Image.
If the blue line in your picture goes from point A to point B, and you want to draw the red line at point B, you can do the following:
Get the direction vector going from A to B. This would be:
v.x = B.x - A.x; v.y = B.y - A.y;
Normalize the vector:
mag = sqrt (v.x*v.x + v.y*v.y); v.x = v.x / mag; v.y = v.y / mag;
Rotate the vector 90 degrees by swapping x and y, and inverting one of them. Note about the rotation direction: In OpenCV and image processing in general x and y axis on the image are not oriented in the Euclidian way, in particular the y axis points down and not up. In Euclidian, inverting the final x (initial y) would rotate counterclockwise (standard for euclidean), and inverting y would rotate clockwise. In OpenCV it's the opposite. So, for example to get clockwise rotation in OpenCV: temp = v.x; v.x = -v.y; v.y = temp;
Create a new line at B pointing in the direction of v:
C.x = B.x + v.x * length; C.y = B.y + v.y * length;
(Note that you can make it extend in both directions by creating a point D in the opposite direction by simply negating length.)
This is my version of the function :
def getPerpCoord(aX, aY, bX, bY, length):
vX = bX-aX
vY = bY-aY
#print(str(vX)+" "+str(vY))
if(vX == 0 or vY == 0):
return 0, 0, 0, 0
mag = math.sqrt(vX*vX + vY*vY)
vX = vX / mag
vY = vY / mag
temp = vX
vX = 0-vY
vY = temp
cX = bX + vX * length
cY = bY + vY * length
dX = bX - vX * length
dY = bY - vY * length
return int(cX), int(cY), int(dX), int(dY)