Related
I'm having issues understanding how the error accumulation part works in Bresenham's line drawing algorithm.
Say we have x1 and x2. Let's assume that x1 < x2, y1 < y2, and (x2 - x1) >= (y2 - y1) for simplicity:
Let's start with the naive way of drawing a line. It would look something like:
void DrawLine(int x1, int y1, int x2, int y2)
{
float y = y1 + 0.5f;
float slope = (float)(y2 - y1) / (x2 - x1);
for (int x = x1; x <= x2; ++x)
{
PlotPixel(x, (int)y);
y += slope;
}
}
Let's make it more Bresenham'ish, and separate the integer and floating-point parts of y:
void DrawLine(int x1, int y1, int x2, int y2)
{
int yi = y1;
float yf = 0.5f;
float slope = (float)(y2 - y1) / (x2 - x1);
for (int x = x1; x <= x2; ++x)
{
PlotPixel(x, yi);
yf += slope;
if (yf >= 1.0f)
{
yf -= 1.0f;
++yi;
}
}
}
At this point we could multiply yf and slope by 2 * (x2 - x1) to make them integers, no more floats. I understand that.
The part I don't fully understand, is this:
if (yf >= 1.0f)
{
yf -= 1.0f;
++yi;
}
How does that actually work? why are we comparing against 1.0 and then decrementing by it?
I know that the basic question of Bresenham is: If we're currently at pixel x, y and we want to draw the next one, should we pick x + 1, y or x + 1, y + 1? - I just don't understand how that check is helping us answer this question.
Some people call it error term, some call it threshold, I just don't get what it represents.
Any explanations is appreciated,
thanks.
Bresenham's line rasterization algorithm performs all the calculations in integer arithmetic. In your code you are using float types and you shouldn't.
First consider that you know two pixels that are on the line. The starting pixel and the end pixel. What the algorithm calculates are the pixels that approximate the line such that the rasterized line starts and stops on the two input pixels.
Second, all lines drawn are reflections of lines with slope between 0 and 0.5. There is a special case for vertical lines. If your algorithm is correct for this input, then you need to initialize the starting state of the rasterizer to correctly rasterize a line: start pixel (x, y), ∆x, ∆y, and D the decision variable.
Since you can assume all lines are drawn from left to right, have positive slope equal to or less than 0.5, the problem boils down to:
is the next rasterized pixel to the current pixels right or to the right and up one pixel.
You can make this decision by keeping track of how much your rasterized line deviates from the true line. To do so, the line equation is re-written into an implicit function, F(x, y) = ∆yx - ∆xy + ∆xb = 0 and you repeatedly evaluate it F(x + 1 y + 0.5). Since that requires floating point math, you focus on identifying if you are on, above, or below the true line. Therefore, F(x + 1 y + 0.5) = ∆y - 0.5∆x and multiplying by two 2 * F(x + 1 y + 0.5) = 2∆y - ∆x. That's the first decision, if the result is less than zero, add one to x but zero to y.
The second decision and subsequent decisions follow similarly and the error is accumulated. A decision variable D is initialized to 2∆y - ∆x. If D < 0, then D = D + 2∆y; else y = y + 1 and D = D + 2(∆y - ∆x). The x variable is always incremented.
Jim Arvo had a great explanation of Bresenham's algorithm.
In your implementation yf is a 0.5 + distance between real floating-point Y coordinate and drawn (integral) Y coordinate. This distance is the current error of your drawing. You want to keep the error within at most half-of-pixel between real line and drawn line (-0.5..+0.5), so your yf which is 0.5+error should be between 0 and 1. When it exceeds one, you just increase your drawn Y coordinate (yi) by one and you need to decrease an error by one. Let's take an example:
slope = 0.3;
x = 0; yf = 0.5; y = 0; // start drawing: no error
x = 1; yf = 0.8; y = 0; // draw second point at (1, 0); error is +0.3
x = 2; yf = 1.1; y = 0; // error is too big (+0.6): increase y
yf = 0.1; y = 1; // now error is -0.4; draw point at (2, 1)
x = 3; yf = 0.4; y = 1; // draw at (3, 1); error is -0.1
x = 4; yf = 0.7; y = 1; // draw at (4, 1); error is +0.2
x = 5; yf = 1.0; y = 1; // error is too big (+0.5); increase y
yf = 0.0; y = 2; // now error is -0.5; draw point at (5, 2)
And so on.
I'm currently trying to filter the depth information using OpenCV. For that reason I need to transform Project Tango's depth information XYZij into a image like depthmap. (Like the output of Microsoft Kinect) Unfortunately the official APIs lacking the ij part of XYZij. That's why I'm trying to project the XYZ part using the camera intrinsics projection, wich is explained in the official C API Dokumentation. My current approach looks like this:
float fx = static_cast<float>(ccIntrinsics.fx);
float fy = static_cast<float>(ccIntrinsics.fy);
float cx = static_cast<float>(ccIntrinsics.cx);
float cy = static_cast<float>(ccIntrinsics.cy);
float k1 = static_cast<float>(ccIntrinsics.distortion[0]);
float k2 = static_cast<float>(ccIntrinsics.distortion[1]);
float k3 = static_cast<float>(ccIntrinsics.distortion[2]);
for (int k = 0; k < xyz_ij->xyz_count; ++k) {
float X = xyz_ij->xyz[k][0];
float Y = xyz_ij->xyz[k][1];
float Z = xyz_ij->xyz[k][2];
float ru = sqrt((pow(X, 2) + pow(Y, 2)) / pow(Z, 2));
float rd = ru + k1 * pow(ru, 3) + k2 * pow(ru, 5) + k3 * pow(ru, 7);
int x = X / Z * fx * rd / ru + cx;
int y = X / Z * fy * rd / ru + cy;
// drawing into OpenCV Mat in red
depth.at<cv::Vec3b>(x, y)[0] = 240;
}
The resulting depthmap can be seen in the lower right corner. But it seems that this calculation result in a linear representation ... Does anyone has already done something similar? Are the XYZ points already correct positioned for this projection?
I have actually found a solution ... Just skipped the distortion calculation like they do in the rgb-depth-sync-example. My code now looks like this:
float fx = static_cast<float>(ccIntrinsics.fx);
float fy = static_cast<float>(ccIntrinsics.fy);
float cx = static_cast<float>(ccIntrinsics.cx);
float cy = static_cast<float>(ccIntrinsics.cy);
int width = static_cast<int>(ccIntrinsics.width);
int height = static_cast<int>(ccIntrinsics.height);
for (int k = 0; k < xyz_ij->xyz_count; ++k) {
float X = xyz_ij->xyz[k * 3][0];
float Y = xyz_ij->xyz[k * 3][1];
float Z = xyz_ij->xyz[k * 3][2];
int x = static_cast<int>(fx * (X / Z) + cx);
int y = static_cast<int>(fy * (Y / Z) + cy);
uint8_t depth_value = UCHAR_MAX - ((Z * 1000) * UCHAR_MAX / 4500);
cv::Point point(y % height, x % width);
line(depth, point, point, cv::Scalar(depth_value, depth_value, depth_value), 4.5);
}
And the working OpenCV result looks like this:
I'm struggling with some quaternion code in iOS. I have an open cube, which i've rotated into an isometric view. i am able to rotate the cube with touch and rotate about its axis and also zoom in/out. I also have labels associated with the cube - which also need to rotate with the cube. Again, i've managed to do this.
However, i'm now trying to implement being able to drag the label (ie. translate it) from one position, to another. If we look at the image below, what i've tried to illustrate is that i want to be able to translate the label from "label from" to the position "label to". Then, when i come to rotating the cube, the label should stay in its new position and rotate with the cube. However, i'm making a cock-up of this translation and when i try rotating the cube, the label jumps to a new position since i've not set the label coordinates properly.
I have the quaternion associated with the cube.
With the following code, i have been able to translate the label properly when the quaternion is set to [0, 0, 0, 1] (so that the cube is front-on - looks like a square from this position).
- (void) rotateWithAngle:(float) radians andVector:(GLKVector3) axis andScale:(float) scale
{
if (radians != self.lastRadians
|| (axis.v[0] != self.lastAxis.v[0] || axis.v[1] != self.lastAxis.v[1] || axis.v[2] != self.lastAxis.v[2])
|| scale != self.lastScale)
{
GLKMatrix4 m = GLKMatrix4MakeTranslation(self.normX, self.normY, self.normZ);
if (radians != 0)
m = GLKMatrix4Rotate(m, radians, axis.x, -axis.y, axis.z);
m = GLKMatrix4Scale(m, scale, scale, scale);
float x = (m.m00 * m.m30) + (m.m01 * m.m31) + (m.m02 * m.m32) + (m.m03 * m.m33);
float y = (m.m10 * m.m30) + (m.m11 * m.m31) + (m.m12 * m.m32) + (m.m13 * m.m33);
float z = (m.m20 * m.m30) + (m.m21 * m.m31) + (m.m22 * m.m32) + (m.m23 * m.m33);
x /= m.m33;
y /= m.m33;
z /= m.m33;
float w = (((x+self.winSz) / (self.winSz * 2.0)) * self.parentFrame.size.width) + self.parentFrame.origin.x;
float h = (((y+self.winSz) / (self.winSz * 2.0)) * self.parentFrame.size.height) + self.parentFrame.origin.y;
self.lastRadians = radians;
self.lastAxis = axis;
self.lastScale = scale;
[self setCenter:CGPointMake(w,h)];
}
}
- (void) translateFromTouch:(UIPanGestureRecognizer *) pan
{
CGPoint translation = [pan translationInView:self];
CGPoint imageViewPosition = self.center;
GLKVector3 axis = GLKQuaternionAxis(*_quaternion);
float rot = GLKQuaternionAngle(*_quaternion);
CGFloat h = self.parentFrame.size.height;
CGFloat w = self.parentFrame.size.width;
imageViewPosition.x += translation.x;
imageViewPosition.y += translation.y;
self.center = imageViewPosition;
// recalculate the norm position
float x = ((2.0 * self.winSz * (imageViewPosition.x - self.parentFrame.origin.x)) / w) - self.winSz;
float y = ((2.0 * self.winSz * (imageViewPosition.y - self.parentFrame.origin.y)) / h) - self.winSz;
self.normX = x;
self.normY = y;
[pan setTranslation:CGPointZero inView:self];
}
These methods are hit if a label (based on a UILabel) is either dragged or the cube (or the opengl scene) is rotated.
This works when we are looking front-on, so that the x,y values can easily be converted from pixel coords into normal or world coords.
However, when the axis is not front-on, i'm struggling to figure it out. For instance, we we have the quaternion set at (0, sqrt(2)/2, 0, sqrt(2)/2) then all x translations correspond to z world coords. So how do i make this connection/calculation? I'm sure it's fairly easy but i've hit a wall with this.
(winSz i have set to 1.5. model coords very between -1 and 1)
I'm trying to find the best way to calculate the biggest (in area) rectangle which can be contained inside a rotated rectangle.
Some pictures should help (I hope) in visualizing what I mean:
The width and height of the input rectangle is given and so is the angle to rotate it. The output rectangle is not rotated or skewed.
I'm going down the longwinded route which I'm not even sure if it will handle the corner cases (no pun intended). I'm certain there is an elegant solution to this. Any tips?
EDIT: The output rectangle points don't necessarily have to touch the input rectangles edges. (Thanks to Mr E)
I just came here looking for the same answer. After shuddering at the thought of so much math involved, I thought I would resort to a semi-educated guess. Doodling a bit I came to the (intuitive and probably not entirely exact) conclusion that the largest rectangle is proportional to the outer resulting rectangle, and its two opposing corners lie at the intersection of the diagonals of the outer rectangle with the longest side of the rotated rectangle. For squares, any of the diagonals and sides would do... I guess I am happy enough with this and will now start brushing the cobwebs off my rusty trig skills (pathetic, I know).
Minor update... Managed to do some trig calculations. This is for the case when the Height of the image is larger than the Width.
Update. Got the whole thing working. Here is some js code. It is connected to a larger program, and most variables are outside the scope of the functions, and are modified directly from within the functions. I know this is not good, but I am using this in an isolated situation, where there will be no confusion with other scripts: redacted
I took the liberty of cleaning the code and extracting it to a function:
function getCropCoordinates(angleInRadians, imageDimensions) {
var ang = angleInRadians;
var img = imageDimensions;
var quadrant = Math.floor(ang / (Math.PI / 2)) & 3;
var sign_alpha = (quadrant & 1) === 0 ? ang : Math.PI - ang;
var alpha = (sign_alpha % Math.PI + Math.PI) % Math.PI;
var bb = {
w: img.w * Math.cos(alpha) + img.h * Math.sin(alpha),
h: img.w * Math.sin(alpha) + img.h * Math.cos(alpha)
};
var gamma = img.w < img.h ? Math.atan2(bb.w, bb.h) : Math.atan2(bb.h, bb.w);
var delta = Math.PI - alpha - gamma;
var length = img.w < img.h ? img.h : img.w;
var d = length * Math.cos(alpha);
var a = d * Math.sin(alpha) / Math.sin(delta);
var y = a * Math.cos(gamma);
var x = y * Math.tan(gamma);
return {
x: x,
y: y,
w: bb.w - 2 * x,
h: bb.h - 2 * y
};
}
I encountered some problems with the gamma-calculation, and modified it to take into account in which direction the original box is the longest.
-- Magnus Hoff
Trying not to break tradition putting the solution of the problem as a picture:)
Edit:
Third equations is wrong. The correct one is:
3.w * cos(α) * X + w * sin(α) * Y - w * w * sin(α) * cos(α) - w * h = 0
To solve the system of linear equations you can use Cramer rule, or Gauss method.
First, we take care of the trivial case where the angle is zero or a multiple of pi/2. Then the largest rectangle is the same as the original rectangle.
In general, the inner rectangle will have 3 points on the boundaries of the outer rectangle. If it does not, then it can be moved so that one vertex will be on the bottom, and one vertex will be on the left. You can then enlarge the inner rectangle until one of the two remaining vertices hits a boundary.
We call the sides of the outer rectangle R1 and R2. Without loss of generality, we can assume that R1 <= R2. If we call the sides of the inner rectangle H and W, then we have that
H cos a + W sin a <= R1
H sin a + W cos a <= R2
Since we have at least 3 points on the boundaries, at least one of these inequality must actually be an equality. Let's use the first one. It is easy to see that:
W = (R1 - H cos a) / sin a
and so the area is
A = H W = H (R1 - H cos a) / sin a
We can take the derivative wrt. H and require it to equal 0:
dA/dH = ((R1 - H cos a) - H cos a) / sin a
Solving for H and using the expression for W above, we find that:
H = R1 / (2 cos a)
W = R1 / (2 sin a)
Substituting this in the second inequality becomes, after some manipulation,
R1 (tan a + 1/tan a) / 2 <= R2
The factor on the left-hand side is always at least 1. If the inequality is satisfied, then we have the solution. If it isn't satisfied, then the solution is the one that satisfies both inequalities as equalities. In other words: it is the rectangle which touches all four sides of the outer rectangle. This is a linear system with 2 unknowns which is readily solved:
H = (R2 cos a - R1 sin a) / cos 2a
W = (R1 cos a - R2 sin a) / cos 2a
In terms of the original coordinates, we get:
x1 = x4 = W sin a cos a
y1 = y2 = R2 sin a - W sin^2 a
x2 = x3 = x1 + H
y3 = y4 = y2 + W
Edit: My Mathematica answer below is wrong - I was solving a slightly different problem than what I think you are really asking.
To solve the problem you are really asking, I would use the following algorithm(s):
On the Maximum Empty Rectangle Problem
Using this algorithm, denote a finite amount of points that form the boundary of the rotated rectangle (perhaps a 100 or so, and make sure to include the corners) - these would be the set S decribed in the paper.
.
.
.
.
.
For posterity's sake I have left my original post below:
The inside rectangle with the largest area will always be the rectangle where the lower mid corner of the rectangle (the corner near the alpha on your diagram) is equal to half of the width of the outer rectangle.
I kind of cheated and used Mathematica to solve the algebra for me:
From this you can see that the maximum area of the inner rectangle is equal to 1/4 width^2 * cosecant of the angle times the secant of the angle.
Now I need to figure out what is the x value of the bottom corner for this optimal condition. Using the Solve function in mathematica on my area formula, I get the following:
Which shows that the x coordinate of the bottom corner equals half of the width.
Now just to make sure, I'll going to test our answer empirically. With the results below you can see that indeed the highest area of all of my tests (definately not exhaustive but you get the point) is when the bottom corner's x value = half of the outer rectangle's width.
#Andri is not working correctly for image where width > height as I tested.
So, I fixed and optimized his code by such way (with only two trigonometric functions):
calculateLargestRect = function(angle, origWidth, origHeight) {
var w0, h0;
if (origWidth <= origHeight) {
w0 = origWidth;
h0 = origHeight;
}
else {
w0 = origHeight;
h0 = origWidth;
}
// Angle normalization in range [-PI..PI)
var ang = angle - Math.floor((angle + Math.PI) / (2*Math.PI)) * 2*Math.PI;
ang = Math.abs(ang);
if (ang > Math.PI / 2)
ang = Math.PI - ang;
var sina = Math.sin(ang);
var cosa = Math.cos(ang);
var sinAcosA = sina * cosa;
var w1 = w0 * cosa + h0 * sina;
var h1 = w0 * sina + h0 * cosa;
var c = h0 * sinAcosA / (2 * h0 * sinAcosA + w0);
var x = w1 * c;
var y = h1 * c;
var w, h;
if (origWidth <= origHeight) {
w = w1 - 2 * x;
h = h1 - 2 * y;
}
else {
w = h1 - 2 * y;
h = w1 - 2 * x;
}
return {
w: w,
h: h
}
}
UPDATE
Also I decided to post the following function for proportional rectange calculating:
calculateLargestProportionalRect = function(angle, origWidth, origHeight) {
var w0, h0;
if (origWidth <= origHeight) {
w0 = origWidth;
h0 = origHeight;
}
else {
w0 = origHeight;
h0 = origWidth;
}
// Angle normalization in range [-PI..PI)
var ang = angle - Math.floor((angle + Math.PI) / (2*Math.PI)) * 2*Math.PI;
ang = Math.abs(ang);
if (ang > Math.PI / 2)
ang = Math.PI - ang;
var c = w0 / (h0 * Math.sin(ang) + w0 * Math.cos(ang));
var w, h;
if (origWidth <= origHeight) {
w = w0 * c;
h = h0 * c;
}
else {
w = h0 * c;
h = w0 * c;
}
return {
w: w,
h: h
}
}
Coproc solved this problem on another thread (https://stackoverflow.com/a/16778797) in a simple and efficient way. Also, he gave a very good explanation and python code there.
Below there is my Matlab implementation of his solution:
function [ CI, T ] = rotateAndCrop( I, ang )
%ROTATEANDCROP Rotate an image 'I' by 'ang' degrees, and crop its biggest
% inner rectangle.
[h,w,~] = size(I);
ang = deg2rad(ang);
% Affine rotation
R = [cos(ang) -sin(ang) 0; sin(ang) cos(ang) 0; 0 0 1];
T = affine2d(R);
B = imwarp(I,T);
% Largest rectangle
% solution from https://stackoverflow.com/a/16778797
wb = w >= h;
sl = w*wb + h*~wb;
ss = h*wb + w*~wb;
cosa = abs(cos(ang));
sina = abs(sin(ang));
if ss <= 2*sina*cosa*sl
x = .5*min([w h]);
wh = wb*[x/sina x/cosa] + ~wb*[x/cosa x/sina];
else
cos2a = (cosa^2) - (sina^2);
wh = [(w*cosa - h*sina)/cos2a (h*cosa - w*sina)/cos2a];
end
hw = flip(wh);
% Top-left corner
tl = round(max(size(B)/2 - hw/2,1));
% Bottom-right corner
br = tl + round(hw);
% Cropped image
CI = B(tl(1):br(1),tl(2):br(2),:);
sorry for not giving a derivation here, but I solved this problem in Mathematica a few days ago and came up with the following procedure, which non-Mathematica folks should be able to read. If in doubt, please consult http://reference.wolfram.com/mathematica/guide/Mathematica.html
The procedure below returns the width and height for a rectangle with maximum area that fits into another rectangle of width w and height h that has been rotated by alpha.
CropRotatedDimensionsForMaxArea[{w_, h_}, alpha_] :=
With[
{phi = Abs#Mod[alpha, Pi, -Pi/2]},
Which[
w == h, {w,h} Csc[phi + Pi/4]/Sqrt[2],
w > h,
If[ Cos[2 phi]^2 < 1 - (h/w)^2,
h/2 {Csc[phi], Sec[phi]},
Sec[2 phi] {w Cos[phi] - h Sin[phi], h Cos[phi] - w Sin[phi]}],
w < h,
If[ Cos[2 phi]^2 < 1 - (w/h)^2,
w/2 {Sec[phi], Csc[phi]},
Sec[2 phi] {w Cos[phi] - h Sin[phi], h Cos[phi] - w Sin[phi]}]
]
]
Here is the easiest way to do this... :)
Step 1
//Before Rotation
int originalWidth = 640;
int originalHeight = 480;
Step 2
//After Rotation
int newWidth = 701; //int newWidth = 654; //int newWidth = 513;
int newHeight = 564; //int newHeight = 757; //int newHeight = 664;
Step 3
//Difference in height and width
int widthDiff ;
int heightDiff;
int ASPECT_RATIO = originalWidth/originalHeight; //Double check the Aspect Ratio
if (newHeight > newWidth) {
int ratioDiff = newHeight - newWidth;
if (newWidth < Constant.camWidth) {
widthDiff = (int) Math.floor(newWidth / ASPECT_RATIO);
heightDiff = (int) Math.floor((originalHeight - (newHeight - originalHeight)) / ASPECT_RATIO);
}
else {
widthDiff = (int) Math.floor((originalWidth - (newWidth - originalWidth) - ratioDiff) / ASPECT_RATIO);
heightDiff = originalHeight - (newHeight - originalHeight);
}
} else {
widthDiff = originalWidth - (originalWidth);
heightDiff = originalHeight - (newHeight - originalHeight);
}
Step 4
//Calculation
int targetRectanleWidth = originalWidth - widthDiff;
int targetRectanleHeight = originalHeight - heightDiff;
Step 5
int centerPointX = newWidth/2;
int centerPointY = newHeight/2;
Step 6
int x1 = centerPointX - (targetRectanleWidth / 2);
int y1 = centerPointY - (targetRectanleHeight / 2);
int x2 = centerPointX + (targetRectanleWidth / 2);
int y2 = centerPointY + (targetRectanleHeight / 2);
Step 7
x1 = (x1 < 0 ? 0 : x1);
y1 = (y1 < 0 ? 0 : y1);
This is just an illustration of Jeffrey Sax's solution above, for my future reference.
With reference to the diagram above, the solution is:
(I used the identity tan(t) + cot(t) = 2/sin(2t))
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 4 years ago.
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
Lets say you have this:
P1 = (x=2, y=50)
P2 = (x=9, y=40)
P3 = (x=5, y=20)
Assume that P1 is the center point of a circle. It is always the same.
I want the angle that is made up by P2 and P3, or in other words the angle that is next to P1. The inner angle to be precise. It will always be an acute angle, so less than -90 degrees.
I thought: Man, that's simple geometry math. But I have looked for a formula for around 6 hours now, and only find people talking about complicated NASA stuff like arccos and vector scalar product stuff. My head feels like it's in a fridge.
Some math gurus here that think this is a simple problem? I don't think the programming language matters here, but for those who think it does: java and objective-c. I need it for both, but haven't tagged it for these.
If you mean the angle that P1 is the vertex of then using the Law of Cosines should work:
arccos((P122
+ P132 - P232) / (2 *
P12 * P13))
where P12 is the length of the segment from P1 to P2, calculated by
sqrt((P1x -
P2x)2 +
(P1y -
P2y)2)
It gets very simple if you think it as two vectors, one from point P1 to P2 and one from P1 to P3
so:
a = (p1.x - p2.x, p1.y - p2.y)
b = (p1.x - p3.x, p1.y - p3.y)
You can then invert the dot product formula:
to get the angle:
Remember that just means:
a1*b1 + a2*b2 (just 2 dimensions here...)
The best way to deal with angle computation is to use atan2(y, x) that given a point x, y returns the angle from that point and the X+ axis in respect to the origin.
Given that the computation is
double result = atan2(P3.y - P1.y, P3.x - P1.x) -
atan2(P2.y - P1.y, P2.x - P1.x);
i.e. you basically translate the two points by -P1 (in other words you translate everything so that P1 ends up in the origin) and then you consider the difference of the absolute angles of P3 and of P2.
The advantages of atan2 is that the full circle is represented (you can get any number between -π and π) where instead with acos you need to handle several cases depending on the signs to compute the correct result.
The only singular point for atan2 is (0, 0)... meaning that both P2 and P3 must be different from P1 as in that case doesn't make sense to talk about an angle.
Let me give an example in JavaScript, I've fought a lot with that:
/**
* Calculates the angle (in radians) between two vectors pointing outward from one center
*
* #param p0 first point
* #param p1 second point
* #param c center point
*/
function find_angle(p0,p1,c) {
var p0c = Math.sqrt(Math.pow(c.x-p0.x,2)+
Math.pow(c.y-p0.y,2)); // p0->c (b)
var p1c = Math.sqrt(Math.pow(c.x-p1.x,2)+
Math.pow(c.y-p1.y,2)); // p1->c (a)
var p0p1 = Math.sqrt(Math.pow(p1.x-p0.x,2)+
Math.pow(p1.y-p0.y,2)); // p0->p1 (c)
return Math.acos((p1c*p1c+p0c*p0c-p0p1*p0p1)/(2*p1c*p0c));
}
Bonus: Example with HTML5-canvas
Basically what you have is two vectors, one vector from P1 to P2 and another from P1 to P3. So all you need is an formula to calculate the angle between two vectors.
Have a look here for a good explanation and the formula.
If you are thinking of P1 as the center of a circle, you are thinking too complicated.
You have a simple triangle, so your problem is solveable with the law of cosines. No need for any polar coordinate tranformation or somesuch. Say the distances are P1-P2 = A, P2-P3 = B and P3-P1 = C:
Angle = arccos ( (B^2-A^2-C^2) / 2AC )
All you need to do is calculate the length of the distances A, B and C.
Those are easily available from the x- and y-coordinates of your points and
Pythagoras' theorem
Length = sqrt( (X2-X1)^2 + (Y2-Y1)^2 )
I ran into a similar problem recently, only I needed to differentiate between a positive and negative angles. In case this is of use to anyone, I recommend the code snippet I grabbed from this mailing list about detecting rotation over a touch event for Android:
#Override
public boolean onTouchEvent(MotionEvent e) {
float x = e.getX();
float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
//find an approximate angle between them.
float dx = x-cx;
float dy = y-cy;
double a=Math.atan2(dy,dx);
float dpx= mPreviousX-cx;
float dpy= mPreviousY-cy;
double b=Math.atan2(dpy, dpx);
double diff = a-b;
this.bearing -= Math.toDegrees(diff);
this.invalidate();
}
mPreviousX = x;
mPreviousY = y;
return true;
}
Very Simple Geometric Solution with Explanation
Few days ago, a fell into the same problem & had to sit with the math book. I solved the problem by combining and simplifying some basic formulas.
Lets consider this figure-
We want to know ϴ, so we need to find out α and β first. Now, for any straight line-
y = m * x + c
Let- A = (ax, ay), B = (bx, by), and O = (ox, oy). So for the line OA-
oy = m1 * ox + c ⇒ c = oy - m1 * ox ...(eqn-1)
ay = m1 * ax + c ⇒ ay = m1 * ax + oy - m1 * ox [from eqn-1]
⇒ ay = m1 * ax + oy - m1 * ox
⇒ m1 = (ay - oy) / (ax - ox)
⇒ tan α = (ay - oy) / (ax - ox) [m = slope = tan ϴ] ...(eqn-2)
In the same way, for line OB-
tan β = (by - oy) / (bx - ox) ...(eqn-3)
Now, we need ϴ = β - α. In trigonometry we have a formula-
tan (β-α) = (tan β + tan α) / (1 - tan β * tan α) ...(eqn-4)
After replacing the value of tan α (from eqn-2) and tan b (from eqn-3) in eqn-4, and applying simplification we get-
tan (β-α) = ( (ax-ox)*(by-oy)+(ay-oy)*(bx-ox) ) / ( (ax-ox)*(bx-ox)-(ay-oy)*(by-oy) )
So,
ϴ = β-α = tan^(-1) ( ((ax-ox)*(by-oy)+(ay-oy)*(bx-ox)) / ((ax-ox)*(bx-ox)-(ay-oy)*(by-oy)) )
That is it!
Now, take following figure-
This C# or, Java method calculates the angle (ϴ)-
private double calculateAngle(double P1X, double P1Y, double P2X, double P2Y,
double P3X, double P3Y){
double numerator = P2Y*(P1X-P3X) + P1Y*(P3X-P2X) + P3Y*(P2X-P1X);
double denominator = (P2X-P1X)*(P1X-P3X) + (P2Y-P1Y)*(P1Y-P3Y);
double ratio = numerator/denominator;
double angleRad = Math.Atan(ratio);
double angleDeg = (angleRad*180)/Math.PI;
if(angleDeg<0){
angleDeg = 180+angleDeg;
}
return angleDeg;
}
In Objective-C you could do this by
float xpoint = (((atan2((newPoint.x - oldPoint.x) , (newPoint.y - oldPoint.y)))*180)/M_PI);
Or read more here
You mentioned a signed angle (-90). In many applications angles may have signs (positive and negative, see http://en.wikipedia.org/wiki/Angle). If the points are (say) P2(1,0), P1(0,0), P3(0,1) then the angle P3-P1-P2 is conventionally positive (PI/2) whereas the angle P2-P1-P3 is negative. Using the lengths of the sides will not distinguish between + and - so if this matters you will need to use vectors or a function such as Math.atan2(a, b).
Angles can also extend beyond 2*PI and while this is not relevant to the current question it was sufficiently important that I wrote my own Angle class (also to make sure that degrees and radians did not get mixed up). The questions as to whether angle1 is less than angle2 depends critically on how angles are defined. It may also be important to decide whether a line (-1,0)(0,0)(1,0) is represented as Math.PI or -Math.PI
Recently, I too have the same problem... In Delphi
It's very similar to Objective-C.
procedure TForm1.FormPaint(Sender: TObject);
var ARect: TRect;
AWidth, AHeight: Integer;
ABasePoint: TPoint;
AAngle: Extended;
begin
FCenter := Point(Width div 2, Height div 2);
AWidth := Width div 4;
AHeight := Height div 4;
ABasePoint := Point(FCenter.X+AWidth, FCenter.Y);
ARect := Rect(Point(FCenter.X - AWidth, FCenter.Y - AHeight),
Point(FCenter.X + AWidth, FCenter.Y + AHeight));
AAngle := ArcTan2(ClickPoint.Y-Center.Y, ClickPoint.X-Center.X) * 180 / pi;
AngleLabel.Caption := Format('Angle is %5.2f', [AAngle]);
Canvas.Ellipse(ARect);
Canvas.MoveTo(FCenter.X, FCenter.Y);
Canvas.LineTo(FClickPoint.X, FClickPoint.Y);
Canvas.MoveTo(FCenter.X, FCenter.Y);
Canvas.LineTo(ABasePoint.X, ABasePoint.Y);
end;
Here's a C# method to return the angle (0-360) anticlockwise from the horizontal for a point on a circle.
public static double GetAngle(Point centre, Point point1)
{
// Thanks to Dave Hill
// Turn into a vector (from the origin)
double x = point1.X - centre.X;
double y = point1.Y - centre.Y;
// Dot product u dot v = mag u * mag v * cos theta
// Therefore theta = cos -1 ((u dot v) / (mag u * mag v))
// Horizontal v = (1, 0)
// therefore theta = cos -1 (u.x / mag u)
// nb, there are 2 possible angles and if u.y is positive then angle is in first quadrant, negative then second quadrant
double magnitude = Math.Sqrt(x * x + y * y);
double angle = 0;
if(magnitude > 0)
angle = Math.Acos(x / magnitude);
angle = angle * 180 / Math.PI;
if (y < 0)
angle = 360 - angle;
return angle;
}
Cheers,
Paul
function p(x, y) {return {x,y}}
function normaliseToInteriorAngle(angle) {
if (angle < 0) {
angle += (2*Math.PI)
}
if (angle > Math.PI) {
angle = 2*Math.PI - angle
}
return angle
}
function angle(p1, center, p2) {
const transformedP1 = p(p1.x - center.x, p1.y - center.y)
const transformedP2 = p(p2.x - center.x, p2.y - center.y)
const angleToP1 = Math.atan2(transformedP1.y, transformedP1.x)
const angleToP2 = Math.atan2(transformedP2.y, transformedP2.x)
return normaliseToInteriorAngle(angleToP2 - angleToP1)
}
function toDegrees(radians) {
return 360 * radians / (2 * Math.PI)
}
console.log(toDegrees(angle(p(-10, 0), p(0, 0), p(0, -10))))
there IS a simple answer for this using high school math..
Let say that you have 3 points
To get angle from point A to B
angle = atan2(A.x - B.x, B.y - A.y)
To get angle from point B to C
angle2 = atan2(B.x - C.x, C.y - B.y)
Answer = 180 + angle2 - angle
If (answer < 0){
return answer + 360
}else{
return answer
}
I just used this code in the recent project that I made, change the B to P1.. you might as well remove the "180 +" if you want
well, the other answers seem to cover everything required, so I would like to just add this if you are using JMonkeyEngine:
Vector3f.angleBetween(otherVector)
as that is what I came here looking for :)
Atan2 output in degrees
PI/2 +90
| |
| |
PI ---.--- 0 +180 ---.--- 0
| |
| |
-PI/2 +270
public static double CalculateAngleFromHorizontal(double startX, double startY, double endX, double endY)
{
var atan = Math.Atan2(endY - startY, endX - startX); // Angle in radians
var angleDegrees = atan * (180 / Math.PI); // Angle in degrees (can be +/-)
if (angleDegrees < 0.0)
{
angleDegrees = 360.0 + angleDegrees;
}
return angleDegrees;
}
// Angle from point2 to point 3 counter clockwise
public static double CalculateAngle0To360(double centerX, double centerY, double x2, double y2, double x3, double y3)
{
var angle2 = CalculateAngleFromHorizontal(centerX, centerY, x2, y2);
var angle3 = CalculateAngleFromHorizontal(centerX, centerY, x3, y3);
return (360.0 + angle3 - angle2)%360;
}
// Smaller angle from point2 to point 3
public static double CalculateAngle0To180(double centerX, double centerY, double x2, double y2, double x3, double y3)
{
var angle = CalculateAngle0To360(centerX, centerY, x2, y2, x3, y3);
if (angle > 180.0)
{
angle = 360 - angle;
}
return angle;
}
}