I am attempting to create a 2d tile based game in Javascript, and I need to be able to calculate the angle between two points. I am using the atan2 function to find the angle between two points like so:
function getAngleDegrees(fromX, fromY, toX, toY, force360 = true) {
let deltaX = toX - fromX;
let deltaY = toY - fromY;
let radians = Math.atan2(deltaY, deltaX);
let degrees = (radians * 180) / Math.PI;
if (force360) {
while (degrees >= 360) degrees -= 360;
while (degrees < 0) degrees += 360;
}
return degrees;
}
However, this isn't providing me with the correct result. I have checked the code for logic or math errors and can't find any. No matter what points I input to this function the result will be off by many degrees.
I have created a JS fiddle to visualize the problem:
https://jsfiddle.net/fa6o7wdy/40/
If anyone knows how I can fix my angle function to provide the correct result please help!
Edit:
Here is a picture of the problem:
https://imgur.com/a/OXDCOux
Based on the photo sample you provide, for getting the desired angle you want with current Math.atan() function, you want to reverse first and then rotate the angle by 90 degrees couter clockwise
function getAngleDegrees(fromX,fromY,toX,toY,force360 = true) {
let deltaX = fromX-toX;
let deltaY = fromY-toY; // reverse
let radians = Math.atan2(deltaY, deltaX)
let degrees = (radians * 180) / Math.PI - 90; // rotate
if (force360) {
while (degrees >= 360) degrees -= 360;
while (degrees < 0) degrees += 360;
}
console.log('angle to degree:',{deltaX,deltaY,radians,degrees})
return degrees;
}
or simply + 90 degrees to this line without changing deltaX and deltaY
let degrees = (radians * 180) / Math.PI + 90; // rotate
Note: I haven't test out all possible edge cases
const inBlk = document.createElement('i')
, getXY = (p,xy) => Number(p.split('-')[xy==='x'?0:1])
;
for(let i=0;i<100;i++) // build Grid
{
let nI = inBlk.cloneNode()
, u1 = i%10
;
nI.textContent = u1+'-'+(i-u1)/10
grid.appendChild(nI)
}
let points = [ {x:0, y:0, old:null}, {x:0, y:0, old:null}]
, pN = 0
;
grid.onclick=e=>
{
if (!e.target.matches('i')) return
let elm = e.target.textContent
points[pN].x = getXY(elm, 'x')
points[pN].y = getXY(elm, 'y')
if (points[pN].old ) points[pN].old.classList.remove('color_0', 'color_1')
points[pN].old = e.target
points[pN].old.classList.add(`color_${pN}` )
pN = ++pN %2
if (pN==0) angle.textContent = ` angle: ${getAngleDegrees(points[0],points[1])}°`
}
function getAngleDegrees( from, to, force360 =true)
{
let deltaX = from.x - to.x
, deltaY = from.y - to.y // reverse
, radians = Math.atan2(deltaY, deltaX)
, degrees = (radians * 180) / Math.PI - 90 // rotate
;
if (force360)
{
while (degrees >= 360) degrees -= 360;
while (degrees < 0) degrees += 360;
}
return degrees.toFixed(2)
}
:root { --sz-hw: 26px; }
#grid {
font-family: 'Courier New', Courier, monospace;
font-size : 10px;
margin : calc( var(--sz-hw) /2);
}
#grid i {
display : block;
float : left;
width : var(--sz-hw);
height : var(--sz-hw);
border : 1px solid grey;
text-align : center;
margin : 2px;
line-height: var(--sz-hw);
cursor : pointer;
}
#grid i:nth-child(10n-9) {clear: both; }
.color_0 { background-color: lightcoral; }
.color_1 { background-color: lightgreen; }
#angle { padding: calc( var(--sz-hw) /2) 0 0 calc( var(--sz-hw) *13.4); }
<p id="grid"></p>
<h4 id="angle">angle: ?</h4>
DeltaX toX and fromX needed to be swapped around, same goes for DeltaY. Also, I've subtracted 90 to angle, in order to make 0 degree being North.
The % (mod) operator does same job as your 2 x while loop.
function getAngleDegrees(fromX,fromY,toX,toY,force360 = true) {
let deltaX = fromX - toX;
let deltaY = fromY - toY;
let radians = Math.atan2(deltaY, deltaX)
let degrees = ((radians * 180) / Math.PI) - 90;
if (force360) {
degrees = (degrees + 360) % 360;
}
console.log('angle to degree:',{deltaX,deltaY,radians,degrees})
return degrees;
}
Your code is working correctly, it's just that you have a bit of confusion in your coordinate system.
The angle between 2 points is relative to where you measure from. By subtracting P2 from P1, you're making the angle relative to your starting point. Thus, atan2 is giving you the clockwise angle relative to the X axis.
Traditionally, the X axis is the starting point for rotations, so a horizontal line has an angle of 0:
x = 1;
y = 0;
angle = atan2(y, x) // Equals 0
You've got your grid with Y+ going down, so as Y becomes positive, you'll get clockwise angles from the x-axis.
x = 0;
y = 1;
angle = atan2(y, x) // Equals PI/2, or 90deg
If this is confusing with Y+ going down, you may want to rethink your grid so that Y+ goes up instead.
PS: Good luck on your game!
Related
OK. I suck at math!!
I have a situation where I would like to know the (x,y) position on a bounding square based on an angle.
The angle of 0 and 360 should be straight up.
Assuming we have a bounding square of (0,0 , 99,99) then an angle of 0 deg would return a point of (49,0)
An angle of 45 deg would return a point of (99,49)
180 deg would give (49, 99), 270 would give (0,49) and 360 would be back to (49,0)
I have NO clue how to calculate this. Any suggestions of where to read up on it so I can learn would be greatly appreciated.
Even better would be pseudo-code or even something in JavaScript.
Thanks,
Mike
Recently I just provide an ans for this question in C#, see also.
Well, translate it into JS is not a difficult task.
var c=document.getElementById("myCanvas");
var rect = c.getBoundingClientRect();
var center = [rect.x + rect.width / 2.0, rect.y + rect.height/2.0]
function calCoor(theta, a, b)
{
var rad = theta * Math.PI / 180.0;
var x, y;
var tan = Math.tan(rad);
if (Math.abs(tan) > b/ a)
{
x = tan > 0 ? a : -a;
y = b / tan;
} else
{
x = a * tan;
y = tan < 0 ? b : -b;
}
return [x,y]
}
var angle = 90;
var random_post = calCoor(angle, rect.width / 2.0, rect.height/2.0)
console.log("Angle at: " + angle)
console.log("x at: " + (random_post[0] + center[0]))
console.log("y at: " + (random_post[1] + center[1]))
.square{
width:100px;
height:100px;
border-style: solid;
}
<div class="square" id="myCanvas"></div>
i'm trying to rotate a cube around a sphere, when i press the spacebar, the cube starts rotating around the sphere just fine, but it goes a lot quicker than i wanted, i wrote a function which would calculate the rotation using "angle" as a parameter. a full rotation would require the angle to go from 0 to 359 (or 1 to 360), but somehow the cube rotates around the sphere completely when the angle increases by just 7 degrees.
code: (im excluding the initialization of the cube and sphere meshes, just the functions)
var rotationAngle = 0;
function rotate(angle)
{
if(angle == 0)
{
keu.position.x = whiteBall.position.x + 1;
keu.position.z = whiteBall.position.z;
} else if(angle > 0 && angle < 90)
{
keu.position.x = whiteBall.position.x + Math.cos(angle);
keu.position.z = whiteBall.position.z - Math.sin(angle);
} else if(angle == 90)
{
keu.position.x = whiteBall.position.x;
keu.position.z = whiteBall.position.z - 1;
} else if(angle > 90 && angle < 180)
{
angle -= 90;
keu.position.x = whiteBall.position.x - Math.sin(angle);
keu.position.z = whiteBall.position.z - Math.cos(angle);
} else if(angle == 180)
{
keu.position.x = whiteBall.position.x - 1;
keu.position.z = whiteBall.position.z;
} else if(angle > 180 && angle < 270)
{
angle -= 180;
keu.position.x = whiteBall.position.x - Math.cos(angle);
keu.position.z = whiteBall.position.z + Math.sin(angle);
} else if(angle == 270)
{
keu.position.x = whiteBall.position.x;
keu.position.z = whiteBall.position.z + 1;
}else if(angle > 270 && angle < 360)
{
angle -= 270;
keu.position.x = whiteBall.position.x + Math.sin(angle);
keu.position.z = whiteBall.position.z + Math.cos(angle);
}
console.log(angle);
}
in the code above "whiteball is the sphere, and "keu" is the cube.
in my render function i have to following code to increase the angle and apply the rotation:
if(isKeyPressed)
{
if(rotationAngle < 360)
{
rotationAngle += 1;
}
if(rotationAngle == 360)
rotationAngle = 0;
}
rotate(rotationAngle);
i have no idea why an increase of just 7 degrees would cause the cube to make a full rotation around the sphere, any code snippets / advice would be appreciated.
Treat x position of the cube as Math.sin(counter) and y position as Math.cos(counter) where counter is some number being incremented in some requestAnimationFrame loop and if spacebar is down then increment the counter and if up then stop incrementing it. You can also modify the distance from your central point around which you move the cube by multiplying Math.sin(counter) by that distance (in pixels). You surely know range of sin is from -1 to 1.
So the code would look something like:
let isMoving = false;
document.body.addEventListener('keydown', event => {
if (event.key === 'Space') {
isMoving = true;
}
});
document.body.addEventListener('keyup', event => {
if (event.key === 'Space') {
isMoving = false;
}
});
const X = ...; //your central point, X coordinate of the sphere
const Y = ...// your central point, Y coordinate of the sphere
const distance = 100;
const speed = ...; // how fast do you want your cube to move around the sphere
let counter = 0;
let wholeCircle = false; // will be set to true after first round and stop further movement of the cube
function render() {
if (isMoving) {
counter += speed;
}
cube.position.x = X + distance * Math.sin(counter);
cube.position.y = Y + distance * Math.cos(counter);
}
render();
This is not a code to copy and paste, you needto adjust it to your situation and variable names. It is just to give you an idea on how to do this kind of movement. I didn't use the wholeCircle, you can surely figure it out.
My Rocket is hitting this Inertia object, as defined in handleCollision. I'm passing in a rocket which has a .r value for its theta and .power for its magnitude.
I'm wanting to update my .rotation & .magnitude according to an inelastic collision as defined by Wikipedia
When colliding from the left, my Inertia moves to the right.
But when colliding from the right it errors and moves exactly 180 degrees off. So if the rocket is up and right at a 45 degree angle from the inertia object, the object will move up and right at a 45 degree angle.
What am I missing here? I thought it might be an issue with the atan function so I converted by the y component & x component of the vector to radians first, same issue.
handleCollision(rocket) {
var angle = rocket.r * Math.PI / 180.0;
var rr = this.rotation * Math.PI / 180;
var rocketVector = {'x' : r.power * Math.cos(angle), 'y' : r.power * Math.sin(angle)};
var inertiaVector = {'x' : this.magnitude * Math.cos(rr), 'y' : this.magnitude * Math.sin(rr)};
var rMass = 10;
var shipMass = 10;
var x = (rMass * rocketVector.x) + (shipMass * inertiaVector.x);
var y = (rMass * rocketVector.y) + (shipMass * inertiaVector.y);
var xDividedByMass = x / (rMass + shipMass);
var yDividedByMass = y / (rMass + shipMass);
var yRadians = (yDividedByMass * Math.PI / 180);
var xRadians = (xDividedByMass * Math.PI / 180);
var theta = Math.atan( yRadians / xRadians);
theta = theta * 180 / Math.PI;
console.log(theta);
var hypotenuse = Math.sqrt((xDividedByMass * xDividedByMass) + (yDividedByMass * yDividedByMass));
this.magnitude = hypotenuse;
this.rotation = theta;
if (this.rotation < 0) {
this.rotation += 360;
} else if (this.rotation > 360) {
this.rotation -= 360;
}
}
If xDividedbyMass>0, you are great because you are quadrant I or IV where arctangent kicks out its values. If you do not like the negative angle, okay add 360 like you did.
But if x<0 and y>0, you will get a negative angle and want to add 180 to get to Q II (tangent has a period of 180). And if x<0, and y<0, you are in QIII and again arctan gives you something in Q1 to which you must add 180.
The logic will look something like this.
if ((x > 0) && (y<0)) {
this.rotation += 360;
} else if (x<0) {
this.rotation += 180;
}
I am working with SVG graphics to draw Pie Graphs. I am given the degrees a pie graph should be - eg 277 degrees - and the diameter - eg 200px - and I need to draw a circle of 277 degrees.
With SVG graphics I need to resolve that 277 degrees to a point where that circle will end.
I am not the greatest with math, so I have come up with a formula/javascript function that will allow me to take a degrees value & come up with a x,y point of where the circle will end.
Will my Javascript function(at the bottom) correctly resolve a degrees to a correct point? Can you help me develop my algorithm to obtain the coordinate from a degree value? Or maybe there is an existing algorithm I can use that I dont know about?
My Algorithm: (Which I require help with)
So the values I am given are: Circle Diameter: 200px, Circle size: 277 degrees.
I require the point at which 277 ends when rotating around the point 0,0.
277 ends in the 1st quadrant which means I need to use sin (is that correct?)
So the values I know now of the triangle are: the hypotenuse=100px(the radius), the angle=7 degrees(277-270).
sin(7) = o/100;
0.1219 = o/100;
o = 12.2;
Therefore the y point is 12.2 (for my sakes 0,0 is the top left corner so its really midY-x = 100-12.2 = 87.8; (is that correct?)
Now to determine the x pos, I use cos(is that correct?).
cos(7) = a/100;
a = 99.25;
Therefore the x point is 99.25 or 100-99.25=0.75;
So the x,y coordinate of 277 degrees is 0.75,87.8. Is that correct?
So in code this algorithm would be:
function resolveToPoint( deg, diameter )
{
if ( deg <= 0)
return 0;
var x = 0;
var y = 0;
var angle = 0;
var rad = diameter/2;
var midX = rad;
var midY = rad;
if (deg <= 90)
angle = 90 - deg;
else if (deg <= 180)
angle = deg - 90;
else if (deg <= 270)
angle = deg - 180;
else if (deg <= 360)
angle = deg - 270;
// Q: Will I ALWAYS use cos to determine the x & sin for the x NO MATTER what quadrant the angle is in??
x = Math.cos(angle) * rad;
y = Math.sin(angle) * rad;
if (deg <= 90)
{
x = midX + x;
y = midY - y;
}
else if (deg <= 180)
{
x = midX + x;
y = midY + y;
}
else if (deg <= 270)
{
x = midX - x;
y = midY + y;
}
else if (deg <= 360)
{
x = midX - x;
y = midY - y;
}
return {mX: x, mY: y};
}
Then I'll use it in a SVG like so:
function outputPiegraph( point, rad, diameter )
{
var svg = '<svg width="%spx" height=""%spx" id='pie' style="background-color: green;">
<path d="M%spx,%spx L%spx, %spx A%spx,"%spx 1 1,1 %spx,%spx z"
fill="red" stroke="blue" stroke-width="2" />"
</svg>';
return sprintf(svg, diameter, diameter, point.mX, point.mY, rad, rad, rad, diameter);
}
This is simple conversion from polar to Cartesian coordinates:
function resolveToPoint(deg, diameter) {
var rad = Math.PI * deg / 180;
var r = diameter / 2;
return {mX: r * Math.cos(rad), mY: r * Math.sin(rad)};
}
http://en.wikipedia.org/wiki/Polar_coordinates#Converting_between_polar_and_Cartesian_coordinates
If you consider the unit circle, then the X-coordinate for a given (radians-based) angle is given by the cosine, likewise, the Y-coordinate is given by the sine. So, you can solve this easily as follows.
function resolveToPoint(deg, diameter) {
var radians = angle_in_degrees / 180 * Math.PI;
var x = diameter / 2 * cos(radians);
var y = diameter / 2 * sin(radians);
return {mX : x, mY: y};
}
Basically I've managed to layout my DIV elements into a circle shape but I've not managed to work out how to calculate the deg of rotation need to have them face OUTWARD from the center of the circle.
$(document).ready(function(){
var elems = document.getElementsByClassName('test_box');
var increase = Math.PI * 2 / elems.length;
var x = 0, y = 0, angle = 0;
for (var i = 0; i < elems.length; i++) {
var elem = elems[i];
// modify to change the radius and position of a circle
x = 400 * Math.cos(angle) + 700;
y = 400 * Math.sin(angle) + 700;
elem.style.position = 'absolute';
elem.style.left = x + 'px';
elem.style.top = y + 'px';
//need to work this part out
var rot = 45;
elem.style['-moz-transform'] = "rotate("+rot+"deg)";
elem.style.MozTransform = "rotate("+rot+"deg)";
elem.style['-webkit-transform'] = "rotate("+rot+"deg)";
elem.style['-o-transform'] = "rotate("+rot+"deg)";
elem.style['-ms-transform'] = "rotate("+rot+"deg)";
angle += increase;
console.log(angle);
}
});
does anyone have to knowledge on how I can do this.
Cheers -C
Note that rot depends on angle, except angle is in radians.
DRY, so either convert from angle to rot:
// The -90 (degrees) makes the text face outwards.
var rot = angle * 180 / Math.PI - 90;
Or just use angle when setting the style (but use radians as a unit), and drop rot's declaration:
// The -0.5*Math.PI (radians) makes the text face outwards.
elem.style.MozTransform = "rotate("+(angle-0.5*Math.PI)+"rad)";
Try this:
var rot = 90 + (i * (360 / elems.length));
Demo at http://jsfiddle.net/gWZdd/
I've added the 90 degrees at the start there to ensure the baseline of the divs face towards the centre, however you can adjust this to suit your needs.