Say you have two x,y coordinates: Starting point of: (100, 100) and ending point of (96, 105). So I need to go left and up. I want to have an arrow point the way to the destination, rotated by the css rotate property, so I need the degree heading (from 0 to 360). 0 would mean go straight up, 180 would mean go straight down, 90 would be go right, etc.
Most posts say to get the bearing in degrees by using: (Math.atan2(105 - 100, 96 - 100) * 180) / Math.PI;
. But I can't get that to work. With the above coordinates, I get 128.6598...
which would be down and to the right. It also gives negative numbers which would throw it off as well. I've tried adding various degrees depending on if it's positive or negative, but haven't gotten anything to work.
Is there a way to get the correct heading (from 0 to 360) for the two points? Thanks!
I think this isn't the best way to do that, but this should work:
const getVectorAngle = ([x1, y1], [x2, y2]) => {
const x = x2 - x1
const y = y2 - y1
return (((Math.acos(y / Math.sqrt(x * x + y * y)) * (Math.sign(x) || 1)) * 180 / Math.PI) + 360) % 360
}
console.log(getVectorAngle([100, 100], [96, 105])) //321.3401
console.log(getVectorAngle([100, 100], [100, 101])) //0
console.log(getVectorAngle([100, 100], [101, 101])) //45
console.log(getVectorAngle([100, 100], [101, 100])) //90
console.log(getVectorAngle([100, 100], [101, 99])) //135
console.log(getVectorAngle([100, 100], [100, 99])) //180
console.log(getVectorAngle([100, 100], [ 99, 99])) //225
console.log(getVectorAngle([100, 100], [ 99, 100])) //270
console.log(getVectorAngle([100, 100], [ 99, 101])) //315
console.log(getVectorAngle([100, 100], [100, 100])) //NaN, start and end values are the same
Visalisation made by Geogebra Geometry
r is the displacement vector, which is given, and we're looking for α (alpha), the angle of that vector.
const getVectorAngle = ([x1, y1], [x2, y2]) => {
const x = x2 - x1
const y = y2 - y1
return (
(
( //Getting angle by `y = cos(alpha) * r` -> `alpha = cos^-1(y / r)`
Math.acos(
y /
Math.sqrt(x * x + y * y) //Pythagorean theorem to get the length of r, `r^2 = x^2 + y^2` -> `r = sqrt(x^2 + y^2)`
) //= angle in interval [0°; 180°] (in radians)
* ( //Detect the direction of angle by changing its sign
Math.sign(x) //Sign of x, +1 if positive, -1 if negative
|| 1 //Return +1 even if x is 0, to avoid cancelling out 180°
)
) //= angle in interval ]-180°; 180°] (still in radians)
* 180 / Math.PI //Convert angle from radians to degrees
) //= angle in degrees
+ 360 //Add 360° to avoid negative values
) //= angle in interval ]180°; 540°]
% 360 //Modulo by 360° to simplify angles >=360°
//= angle in degrees in interval [0°; 360°[
}
Somewhat later, I realized, that it can be made to work with Math.atan2
as well:
const getVectorAngle = ([x1, y1], [x2, y2]) => {
const x = x2 - x1
const y = y2 - y1
return x || y
? ((Math.atan2(x, y) * 180 / Math.PI) + 360) % 360
: NaN
}
console.log(getVectorAngle([100, 100], [96, 105])) //321.3401
console.log(getVectorAngle([100, 100], [100, 101])) //0
console.log(getVectorAngle([100, 100], [101, 101])) //45
console.log(getVectorAngle([100, 100], [101, 100])) //90
console.log(getVectorAngle([100, 100], [101, 99])) //135
console.log(getVectorAngle([100, 100], [100, 99])) //180
console.log(getVectorAngle([100, 100], [ 99, 99])) //225
console.log(getVectorAngle([100, 100], [ 99, 100])) //270
console.log(getVectorAngle([100, 100], [ 99, 101])) //315
console.log(getVectorAngle([100, 100], [100, 100])) //NaN, start and end values are the same
As you may notice, the outer shell of the calculation remained the same. That's because Math.atan2
already returns an angle in radians in the interval ]-180°;180°]
; so it's functionally same as first my solution's inner part.
The only change is an additional check for a null vector: Math.atan2
doesn't return NaN
in this case.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments