I need to calculate the angle between 3 points. For this, I do the following:
Grab the 3 points (previous, current and next, it's within a loop)
Calculate the distance between the points with Pythagoras
Calculate the angle using Math.acos
This seems to work fine for shapes without angels of over 180 degrees, however if a shape has such an corner it calculates the short-side. Here's an illustration to show what I mean (the red values are wrong):
This is the code that does the calculations:
// Pythagoras for calculating distance between two points (2D)
pointDistance = function (p1x, p1y, p2x, p2y) {
return Math.sqrt((p1x - p2x)*(p1x - p2x) + (p1y - p2y)*(p1y - p2y));
};
// Get the distance between the previous, current and next points
// vprev, vcur and vnext are objects that look like this:
// { x:float, y:float, z:float }
lcn = pointDistance(vcur.x, vcur.z, vnext.x, vnext.z);
lnp = pointDistance(vnext.x, vnext.z, vprev.x, vprev.z);
lpc = pointDistance(vprev.x, vprev.z, vcur.x, vcur.z);
// Calculate and print the angle
Math.acos((lcn*lcn + lpc*lpc - lnp*lnp)/(2*lcn*lpc))*180/Math.PI
Is there something wrong in the code, did I forget to do something, or should it be done a completely different way?
HI there your math and calculations are perfect. Your running into the same problem most people do on calculators, which is orientation. What I would do is find out if the point lies to the left or right of the vector made by the first two points using this code, which I found from
Determine which side of a line a point lies
isLeft = function(ax,ay,bx,by,cx,cy){
return ((bx - ax)*(cy - ay) - (by - ay)*(cx - ax)) > 0;
}
Where ax and ay make up your first point bx by your second and cx cy your third.
if it is to the left just add 180 to your angle
I've got a working but not necessarily brief example of how this can work:
var point1x = 0, point1y = 0,
point2x = 10, point2y = 10,
point3x = 20, point3y = 10,
point4x = 10, point4y = 20;
var slope1 = Math.atan2(point2y-point1y,point2x-point1x)*180/Math.PI;
var slope2 = Math.atan2(point3y-point2y,point3x-point2x)*180/Math.PI;
var slope3 = Math.atan2(point4y-point3y,point4x-point3x)*180/Math.PI;
alert(slope1);
alert(slope2);
alert(slope3);
var Angle1 = slope1-slope2;
var Angle2 = slope2-slope3;
alert(180-Angle1);
alert(180-Angle2);
(see http://jsfiddle.net/ZUESt/1/)
To explain the multiple steps the slopeN variables are the slopes of the individual line segments. AngleN is the amount turned at each junction (ie point N+1). A positive angle is a right turn and a negative angle a left turn.
You can then subtract this angle from 180 to get the actual interior angle that you want.
It should be noted that this code can of course be compressed and that five lines are merely outputting variables to see what is going on. I'll let you worry about optimizing it for your own use with this being a proof of concept.
You need to check boundary conditions (apparently, if points are colinear) and apply the proper calculation to find the angle.
Also, a triangle can't have any (interior) angle greater than 180 degress. Sum of angle of triangle is 180 degrees.
Related
Hello i'm currently trying to draw a line through two long/lat lines to create a triangle. So far i have manged to draw a line through but the line is not perpendicular and looks skewed. Here is my code:
startPosition = [-0.17640, 51.426700];
endPosition = [0.17640, 51.796700];
triangleSizeY = (endPosition [1] - startPosition[1]) / 6;
/*subtract
end from start to work out direction and also use this divided by 6 to
get distance*/
triangleSize *= -1;
triangleSizeX = (endPosition [0] - startPosition[0]) / 6;
/*subtract
end from start to work out direction and also use this divided by 6 to
get distance*/
triangleSize *= -1;
var cx = (startPosition[0] + endPosition[0]) / 2;
var cy = (startPosition[1] + endPosition[1]) / 2;
var dx = (endPosition[0] - startPosition[0]) / 2;
var dy = (endPosition[1] - startPosition[1]) / 2;
positions[0] = [midPoint[0] + triangleSizeX, midPoint[1] +
triangleSizeY];
positions[1] = [cx - dy, cy + dx];
positions[2] = [cx + dy, cy - dx];
This is what it looks like:
First, lat/lon are angular so you can not do linear type distances. The steps you need to take to solve this:
Compute the distance between the 2 lat/lon pairs you want a perpendicular line from.
Take half the distance computed from the above step to get you the midpoint range.
Calculate the bearing between the 2 lat/lon pairs. (see reference below on computing a bearing from 2 lat/lon's)
With the half distance and bearing, you can compute the lat/lon of the midpoint. This is called computing a lat/lon based on a range and bearing. (See the reference below.)
Now you can go perpendicular from the midpoint by adding/subtracting 90 degrees from the bearing in step 3. Decide on a range you want to compute the new lat/lon from a range/bearing like in step 4.
This site (https://www.movable-type.co.uk/scripts/latlong.html) has the calculations you need to do this. Also, since the distance is relatively small, you can use the Equirectangular approximation over Haversine for distance calculation.
function distance(x1, y1, x2, y2) {
var x = x1 - x2;
var y = y1 - y2;
return(Math.sqrt((x*x) + (y*y)))
};
function collisionCirc(circ1, circ2) {
var d = distance(circ1.x, circ1.y, circ2.x, circ2.y);
var r = circ1.radius + circ2.radius;
return(r > d);
};
function collisionCircPoint(circ1, circ2) {
var cx = ((circ1.x * circ2.radius) + (circ2.x * circ1.radius)) / (circ1.radius + circ2.radius);
var cy = ((circ1.y * circ2.radius) + (circ2.y * circ1.radius)) / (circ1.radius + circ2.radius);
var p = [cx, cy];
return p;
};
function angleDegrees(x1, y1, x2, y2) {
return (Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI) + 180;
};
function updateCollisions() {
var a;
var p;
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
Player.sprite.x = p[0];
Player.sprite.y = p[1];
};
};
I have 2 sprites on the map and each has a circle hitArea defined. I am trying to make a smooth circular collision that the player cannot pass through. I thought I could just set the Player.sprite's coordinates to the point of collision but it just warps him to the MapObjects.chest's coordinates, even though the point of collision is correct and is 20 pixels from the MapObject.chest's center. What am I doing wrong or what more information is needed to create a collision much like the JavaScript physics libraries where I can circle around a circle object?
The collision point is between the player and the obstacle. If you move the player towards the collision point, you are actually moving the player closer. For example, if there's exactly 40 px (r1+r2) between the player and the obstacle, the collision point is between them, at only 20 px from the obstacle!
When you have multiple objects, getting it right when the collision has already happened is difficult. If there is only one obstacle nearby, you can simply move the player directly away from the obstacle. However, this way the player might actually end up inside another obstacle.
Another solution is to go back to the start and try smaller movements, until there is no collision. This way you would eventually get it right, but this might also be slow.
The mathematically correct solution is to calculate the maximum distance to move before the collision happens. This is done by solving the following vector equation:
# p = player position before moving
# o = obstacle position
# u = player direction (unit vector)
# d = distance to move
distance(o, p + d * u) = o.radius + p.radius
That's mathematics, you may solve it by yourself or using a tool like Wolfram Alpha.
Solving this equation will give you zero, one or two possible values for the distance. Negative values you can dismiss, as they mean that the player is already past the obstacle. If you get only one value, it means that the player would merely brush the obstacle, which you can also dismiss. Two values mean that the collision happens between these distances; the smaller value is where the collision starts, and the larger value is where the player would already be through the obstacle. Also, if one value is positive and the other is negative, it means that the player is already inside the obstacle, which should never happen.
You should run this check for all nearby obstacles and then move the player according to the smallest non-negative result (that is, zero or positive), or less, if the player can't move that fast.
Finally, to circle around a round object, you can move the player a little bit in a perpendicular direction (either left or right, depending on which side of the obstacle the player will be passing) after a collision, if this doesn't cause any new collisions.
There are many other possible implementations.
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
if (Player.sprite.x - MapObjects.chest.x > 0) {
Player.sprite.x += 1;
} else if (Player.sprite.x + MapObjects.chest.x > 0) {
Player.sprite.x -= 1;
};
if (Player.sprite.y - MapObjects.chest.y > 0) {
Player.sprite.y += 1;
} else if (Player.sprite.y + MapObjects.chest.y > 0) {
Player.sprite.y -= 1;
};
};
};
I added that and it actually works well enough minus the player speed being slightly too fast when running into the MapObjects.chest's hitArea at certain angles. Work on that later.
EDIT: I updated the program with the answer and it works great!
I am making a program (feel free to try it out) that lets users draw polygons which it then triangulates. They can click to add vertices and hit enter to triangulate. Anyways, the algorithm works fine as long as I tell it if the points were drawn in a clockwise or counterclockwise fashion (right now I have it set only to work with clockwise polygons). I have been trying to figure this out for days, but have no idea how to determine whether the points are clockwise or counterclockwise. Try drawing shapes with the program mentioned earlier to get a better idea, you can experience what I am talking about better than I can try to explain it.
Here is how the points are defined:
function Point(x, y) {
this.x = x;
this.y = y;
}
var vertices = [];
// Called on click
function addPoint(mouseX, mouseY) {
vertices.push(new Point(mouseX, mouseY));
}
Here is an image of a clockwise polygon:
Here is an image of a counterclockwise polygon:
If you could help me figure out how to determine the "clockwise-ness" of the points, I would be very grateful!
Compute the polygon area using the shoelace formula, but without the absolute value sign. If the result is positive, the points are ordered counterclockwise, and if negative - clockwise.
function polygonArea() {
var area = 0;
for (var i = 0; i < vertices.length; i++) {
j = (i + 1) % vertices.length;
area += vertices[i].x * vertices[j].y;
area -= vertices[j].x * vertices[i].y;
}
return area / 2;
}
var clockwise = polygonArea() > 0;
In case someone is using three.js the ShapeUtils comes with an inbuilt isClockWise method which internally uses the area method to determine the sign of the calculated area.
isClockWise: function ( pts ) {
return ShapeUtils.area( pts ) < 0;
}
The ShapeUtils.isClockWise Method can be found here.
area: function ( contour ) {
var n = contour.length;
var a = 0.0;
for ( var p = n - 1, q = 0; q < n; p = q ++ ) {
a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
}
return a * 0.5;
},
The ShapeUtils.area Method can be found here.
A general idea would be to take a look at the convex hull of your polygone and guess the orientation from there. However, I think that you do not need to build the whole hull to find the orientation, but just one segment belonging to it.
So:
Find two points of your polygones so that all the other points are on one side of this line.
If all the points are on the left (just check one of the points), it's counterclockwise. If they are on the right, it's clockwise.
Example:
On the top figure: 4-5 let the figure on the right, 5-11 let the figure on the right, ...
On the bottom figure: 6-7 let the figure on the left, 7-14 let the figure on the left, ...
Warning: While "walking" on your polygon, do not restart the numeration, otherwise it will be wrong. On the top figure, 4-(n-1) let the figure on the left!
Your intuitive definition of clockwisedness is not well defined. For example, If I draw a horseshoe:
/---a-b--\
/ _d_c_ \
/ / \ \
| | | |
| | | |
\ \ / /
\ \ / /
-- --
If 0 = a < b < b < d and I look at a and b I would conclude from your description that the shape has been drawn clockwise, but if 0 = c < d < a < b I would conclude that the shape has been drawn anticlockwise. Since both of these scenarios involve the same direction in which the points were drawn, just from different starting points, I can only conclude that your definition is lacking.
The horseshoe I drew isn't the best; the idea is that it is almost a circle with just a small hole at the bottom, to allow the other side to be drawn in the opposite direction.
If you are interested in defining things more strictly, then I suggest something along the following lines:
Considering any finite simple polygon as separating the plane into two distinct areas (one finite and one infinite), we can always consider the finite area to be the interior of the polygon. In such a scenario we define a vertex ordering to be clockwise iff the order of the points runs with the exterior along its right-hand side. This is called curve orientation.
Once you have this more solid definition, implementation can be as simple as counting the winding number. Take the midpoint of any ordered pair, say 0 and 1, take a line segment to the right of the ordered pair (at any angle, say perpendicular), and count how many intersections it has with other line segments: The curve is clockwise iff the number is odd.
This is simple to implement, linear in time O(n), and adds constant space O(1).
This a function function that specialized for OpenLayers. As You Can See The Condition Of Clockwise Polygon Is area<0 This Reference Confirm It.
function IsClockwise(feature)
{
if(feature.geometry==null)return -1;
var vertices=feature.geometry.getVertices();
var area=0;
for (var i = 0; i < (vertices.length); i++)
{
j = (i + 1) % vertices.length;
area += vertices[i].x * vertices[j].y;
area -= vertices[j].x * vertices[i].y;
// console.log(area);
}
return (area < 0);
}
In the code below, I want to make sure a line is rotating clockwise in relation to another line. The line I want to go clockwise has angle targetAngle.
var dX = line.start.x - someOtherPoint.x;
var dY = line.start.y - someOtherPoint.y;
var lastAngle = Math.atan2(dY, dX);
var dX = line.start.x - line.end.x;
var dY = line.start.y - line.end.y;
var targetAngle = Math.atan2(dY, dX);
if (targetAngle < lastAngle) {
// is going counter clockwise
} else {
// is going clockwise
}
This code works in most cases, but breaks when targetAngle has gone below 0.
I've only given it a cursory glance, but if it only breaks when targetAngle is below zero, add a check like this:
if (targetAngle < 0)
targetAngle = targetAngle + 360
That way you never have to deal with negative angles. Or do something along those lines anyway.
I don't think you can do this in general unless you also know that the difference between two measurements can't be more than 180°. How would you tell the difference between a line rotated counter clockwise 90° vs one rotated clockwise 270° based on the difference between the angles alone? Otherwise, you only need to check the sign of atan2 - positive for counterclockwise, negative for clockwise (depending on your point of reference).
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/atan2
The atan2 method returns a numeric value between -pi and pi representing the angle theta of an (x,y) point. This is the counterclockwise angle, measured in radians, between the positive X axis, and the point (x,y).
Changing this:
if (targetAngle < lastAngle) {
to this:
if (targetAngle < lastAngle && !(targetAngle < 0 && lastAngle > 0)) {
seems to do the trick.
BACKGROUND:
The app allows users to upload a photo of themselves and then place a pair of glasses over their face to see what it looks like. For the most part, it is working fine. After the user selects the location of the 2 pupils, I auto zoom the image based on the ratio between the distance of the pupils and then already known distance between the center points of the glasses. All is working fine there, but now I need to automatically place the glasses image over the eyes.
I am using KinectJS, but the problem is not with regards to that library or javascript.. it is more of an algorithm requirement
WHAT I HAVE TO WORK WITH:
Distance between pupils (eyes)
Distance between pupils (glasses)
Glasses width
Glasses height
Zoom ratio
SOME CODE:
//.. code before here just zooms the image, etc..
//problem is here (this is wrong, but I need to know what is the right way to calculate this)
var newLeftEyeX = self.leftEyePosition.x * ratio;
var newLeftEyeY = self.leftEyePosition.y * ratio;
//create a blue dot for testing (remove later)
var newEyePosition = new Kinetic.Circle({
radius: 3,
fill: "blue",
stroke: "blue",
strokeWidth: 0,
x: newLeftEyeX,
y: newLeftEyeY
});
self.pointsLayer.add(newEyePosition);
var glassesWidth = glassesImage.getWidth();
var glassesHeight = glassesImage.getHeight();
// this code below works perfect, as I can see the glasses center over the blue dot created above
newGlassesPosition.x = newLeftEyeX - (glassesWidth / 4);
newGlassesPosition.y = newLeftEyeY - (glassesHeight / 2);
NEEDED
A math genius to give me the algorithm to determine where the new left eye position should be AFTER the image has been resized
UPDATE
After researching this for the past 6 hours or so, I think I need to do some sort of "translate transform", but the examples I see only allow setting this by x and y amounts.. whereas I will only know the scale of the underlying image. Here's the example I found (which cannot help me):
http://tutorials.jenkov.com/html5-canvas/transformation.html
and here is something which looks interesting, but it is for Silverlight:
Get element position after transform
Is there perhaps some way to do the same in Html5 and/or KinectJS? Or perhaps I am going down the wrong road here... any ideas people?
UPDATE 2
I tried this:
// if zoomFactor > 1, then picture got bigger, so...
if (zoomFactor > 1) {
// if x = 10 (for example) and if zoomFactor = 2, that means new x should be 5
// current x / zoomFactor => 10 / 2 = 5
newLeftEyeX = self.leftEyePosition.x / zoomFactor;
// same for y
newLeftEyeY = self.leftEyePosition.y / zoomFactor;
}
else {
// else picture got smaller, so...
// if x = 10 (for example) and if zoomFactor = 0.5, that means new x should be 20
// current x * (1 / zoomFactor) => 10 * (1 / 0.5) = 10 * 2 = 20
newLeftEyeX = self.leftEyePosition.x * (1 / zoomFactor);
// same for y
newLeftEyeY = self.leftEyePosition.y * (1 / zoomFactor);
}
that didn't work, so then I tried an implementation of Rody Oldenhuis' suggestion (thanks Rody):
var xFromCenter = self.leftEyePosition.x - self.xCenter;
var yFromCenter = self.leftEyePosition.y - self.yCenter;
var angle = Math.atan2(yFromCenter, xFromCenter);
var length = Math.hypotenuse(xFromCenter, yFromCenter);
var xNew = zoomFactor * length * Math.cos(angle);
var yNew = zoomFactor * length * Math.sin(angle);
newLeftEyeX = xNew + self.xCenter;
newLeftEyeY = yNew + self.yCenter;
However, that is still not working as expected. So, I am not sure what the issue is currently. If anyone has worked with KinectJS before and has an idea of what the issue may be, please let me know.
UPDATE 3
I checked Rody's calculations on paper and they seem fine, so there is obviously something else here messing things up.. I got the coordinates of the left pupil at zoom factors 1 and 2. With those coordinates, maybe someone can figure out what the issue is:
Zoom Factor 1: x = 239, y = 209
Zoom Factor 2: x = 201, y = 133
OK, since it's an algorithmic question, I'm going to keep this generic and only write pseudo code.
I f I understand you correctly, What you want is the following:
Transform all coordinates such that the origin of your coordinate system is at the zoom center (usually, central pixel)
Compute the angle a line drawn from this new origin to a point of interest makes with the positive x-axis. Compute also the length of this line.
The new x and y coordinates after zooming are defined by elongating this line, such that the new line is the zoom factor times the length of the original line.
Transform the newly found x and y coordinates back to a coordinate system that makes sense to the computer (e.g., top left pixel = 0,0)
Repeat for all points of interest.
In pseudo-code (with formulas):
x_center = image_width/2
y_center = image_height/2
x_from_zoom_center = x_from_topleft - x_center
y_from_zoom_center = y_from_topleft - y_center
angle = atan2(y_from_zoom_center, x_from_zoom_center)
length = hypot(x_from_zoom_center, y_from_zoom_center)
x_new = zoom_factor * length * cos(angle)
y_new = zoom_factor * length * sin(angle)
x_new_topleft = x_new + x_center
y_new_topleft = y_new + y_center
Note that this assumes the number of pixels used for length and width stays the same after zooming. Note also that some rounding should take place (keep everything double precision, and only round to int after all calculations)
In the code above, atan2 is the four-quadrant arctangent, available in most programming languages, and hypot is simply sqrt(x*x + y*y), but then computed more carefully (e.g., to avoid overflow etc.), also available in most programing languages.
Is this indeed what you were after?