Oh god this must be so simple. I have a heading in range (0, 2π) and two points from which I get the heading in between. I must compare them to see if one is within a range of the other. What I've got so far is.
//get the angle
float angle = atan(here.x - there.x, here.y - there.y);
//atan2 uses (-pi, pi) range, convert to (0, 2pi)
if(angle < 0) angle += 2*pi;
//subtract them pesky headings
float diff = angle - givenAngle;
//a difference of 350 degrees really is a difference of 10 degrees
if(diff > pi) diff = 2*pi - diff;
//a difference of -10 degrees really is a difference of 10 degrees
if(diff < 0) diff *= -1;
//check if the point is in range of givenAngle
if(diff > fov) do_magic(diff - fov);
However, I get all sorts of issues when both angles wrap around to zero and I've been wasting way too much brainpower in solving this solved problem.
Where am I doing it wrong? How can I find the difference between two headings correctly?
I suspect the order of your operations may be slightly wrong:
//a difference of 350 degrees really is a difference of 10 degrees
if(diff > pi) diff = 2*pi - diff;
//a difference of -10 degrees really is a difference of 10 degrees
if(diff < 0) diff *= -1;
This doesn't account for an diff of -350, but if you switch the statements it does:
//a difference of -10 degrees really is a difference of 10 degrees
if(diff < 0) diff *= -1;
//a difference of ±350 degrees really is a difference of 10 degrees
if(diff > pi) diff = 2*pi - diff;
One of the main problems was in this line:
float angle = atan(here.x - there.x, here.y - there.y);
It was kind of tricky to notice, but that's not the right argument order for atan - even the mathematical definition on wikipedia takes the y component (sine) before the x component (cosine).
float angle = atan(here.y - there.y, here.x - there.x);
A second problem is that, as it turns out, angle was off by 180 degrees. In other words, instead of calculating here - there, I should be calculating there - here.
float angle = atan(there.y - here.y, there.x - here.x);
Add in sverre's observation about the incorrect order of operations, and we have something that works much better:
float angle = atan(there.y - here.y, there.x - here.x);
if(angle < 0) angle += pi * 2; //use (0, 2pi) range, the same as angle
float diff = abs(angle - givenAngle);
if(diff > pi) diff = 2*pi - diff;
if(diff > fov) do_magic(diff - fov);
Related
I'm programming a flash game, I made an array of points (x and y positions) that some movieclips must follow. Those movieclips have a certain speed (they make steps of 5 pixels for now). When I want to move them horizontally or vertically, everything's fine, I have to add or remove 5 pixels of those clips' x or y. But sometimes they have to move diagonally and now that's complicated.
What I'm doing:
var angle:Number = Math.atan2(nextPoint.y - this.y, nextPoint.x - this.x) * 180 / Math.PI;
var xstep:Number = Math.cos(angle) * this.speed;
var ystep:Number = Math.sqrt(Math.pow(this.speed, 2) - Math.pow(xstep, 2));
this.x += xstep;
this.y += ystep;
It's only a fraction of the code, but I think it's all you need.
Basically, this makes my movieclip do a little step (of this.speed (currently set to 5) pixels).
If the current point and the next point have the same y position, it works fine. When they don't, it doesn't work. The angle is right at first but it slowly decreases (while it should stay the same). I don't know if it's the angle that isn't computed the right way or if it's the x and y steps, but it's one of those, I'm sure.
Try this instead:
var angle:Number = Math.atan2(nextPoint.y - this.y, nextPoint.x - this.x);
var xstep:Number = Math.cos(angle) * this.speed;
var ystep:Number = Math.sin(angle) * this.speed;
Because cos operates on angles in radians, you don't need to convert to degrees. Computing the y component of an angle uses sin, so it should be similar to x. I'm not able to test this, but it's possible that ystep will be backwards and may need to be multiplied by -1.
I'm working on a game in HTML5 canvas.
I want is draw an S-shaped cubic bezier curve between two points, but I'm looking for a way to calculate the coordinates of the control points so that the curve itself is always the same length no matter how close those points are, until it reaches the point where the curve becomes a straight line.
This is solvable numerically. I assume you have a cubic bezier with 4 control points.
at each step you have the first (P0) and last (P3) points, and you want to calculate P1 and P2 such that the total length is constant.
Adding this constraint removes one degree of freedom so we have 1 left (started with 4, determined the end points (-2) and the constant length is another -1). So you need to decide about that.
The bezier curve is a polynomial defined between 0 and 1, you need to integrate on the square root of the sum of elements (2d?). for a cubic bezier, this means a sqrt of a 6 degree polynomial, which wolfram doesn't know how to solve. But if you have all your other control points known (or known up to a dependency on some other constraint) you can have a save table of precalculated values for that constraint.
Is it really necessary that the curve is a bezier curve? Fitting two circular arcs whose total length is constant is much easier. And you will always get an S-shape.
Fitting of two circular arcs:
Let D be the euclidean distance between the endpoints. Let C be the constant length that we want. I got the following expression for b (drawn in the image):
b = sqrt(D*sin(C/4)/4 - (D^2)/16)
I haven't checked if it is correct so if someone gets something different, leave a comment.
EDIT: You should consider the negative solution too that I obtain when solving the equation and check which one is correct.
b = -sqrt(D*sin(C/4)/4 - (D^2)/16)
Here's a working example in SVG that's close to correct:
http://phrogz.net/svg/constant-length-bezier.xhtml
I experimentally determined that when the endpoints are on top of one another the handles should be
desiredLength × cos(30°)
away from the handles; and (of course) when the end points are at their greatest distance the handles should be on top of one another. Plotting all ideal points looks sort of like an ellipse:
The blue line is the actual ideal equation, while the red line above is an ellipse approximating the ideal. Using the equation for the ellipse (as my example above does) allows the line to get about 9% too long in the middle.
Here's the relevant JavaScript code:
// M is the MoveTo command in SVG (the first point on the path)
// C is the CurveTo command in SVG:
// C.x is the end point of the path
// C.x1 is the first control point
// C.x2 is the second control point
function makeFixedLengthSCurve(path,length){
var dx = C.x - M.x, dy = C.y - M.y;
var len = Math.sqrt(dx*dx+dy*dy);
var angle = Math.atan2(dy,dx);
if (len >= length){
C.x = M.x + 100 * Math.cos(angle);
C.y = M.y + 100 * Math.sin(angle);
C.x1 = M.x; C.y1 = M.y;
C.x2 = C.x; C.y2 = C.y;
}else{
// Ellipse of major axis length and minor axis length*cos(30°)
var a = length, b = length*Math.cos(30*Math.PI/180);
var handleDistance = Math.sqrt( b*b * ( 1 - len*len / (a*a) ) );
C.x1 = M.x + handleDistance * Math.sin(angle);
C.y1 = M.y - handleDistance * Math.cos(angle);
C.x2 = C.x - handleDistance * Math.sin(angle);
C.y2 = C.y + handleDistance * Math.cos(angle);
}
}
I have a dial which I drag around a circle to give me a reading between 0 and 1.
Something like this:
dx = mouseX-centerX;
dy = mouseY-centerY;
rad = Math.atan2(dy,dx);
rad += offset;
Tweener.addTween(knob,{y:centerY - Math.cos(rad)*radius, time:.1, transition:"easeOutSine"});
Tweener.addTween(knob,{x:centerX + Math.sin(rad)*radius, time:.1, transition:"easeOutSine"});
knob.rotation = rad * 180 / Math.PI;
This work's great, except when the slider goes from 359 degrees to 1 degree, my value between 0 and 1 returns to zero. (Which makes sense, as the value is based on the angle of my slider)
I'm trying to find a way for the dial to move from 359 degrees to 361 and onwards basically.
In my head: I need to check if the next value of my mouse drag goes past the 360 degree point and add 360 to the total, to stop it returning to zero and continue to 361 degrees.
I just cant work out how to put this into code...
On each frame when you are rotating the knob, check the change in angular distance instead of direct angle.
Save the previous frames angle and see if the difference is positive or negative.
var rad = Math.atan2(dy, dx);
var diff = rad - oldRad;
oldRad = rad;
if( diff > Math.PI )
diff -= Math.PI * 2;
if( shortestAngle < -Math.PI )
diff += Math.PI * 2;
diff should contain a value that if it's been rotated to the right, is positive (or negative if rotated left). Simply add that to the total angle.
There might be some errors in the code (took it from an old project), but that's the gist of it :)
Hope that helps!
When given 0,0 to 0,5, the y velocity becomes that number and breaks my code. I know I must have done something wrong as I just copy and pasted code (since I am horrible at maths)..
This is how I calculate the numbers:
var radian = Math.atan2(listOfNodes[j].y - listOfNodes[i].y,listOfNodes[j].x - listOfNodes[i].x);
var vy = Math.cos(radian);
var vx = Math.sin(radian);
Thanks
There i am assuming the velocity vector is FROM 0,0 TO 0,5. And 0,0 is i and 0,5 is j.
In that case the velocity vector is only along y and the y component should be 5 and x component 0. It is coming as opposite because,
cos(radian) whould be x velocity component and sin(radian) the y compunent.
And the number 6.123031769111886E-17 is actually returned in place of 0.
Look at the following figure:
Also as can be seen from the figure you do not need the trigonometric computations at all.
You can simply get the x and y components as follows:
// y2 - y1
var vy = listOfNodes[j].y - listOfNodes[i].y;
// x2 - x1
var vx = listOfNodes[j].x - listOfNodes[i].x;
This will avoid the floating point inaccuracy caused by the trig finctions due to which you are seeing 6.123031769111886E-17 instead of 0.
You only need to use atan2 if you actually need the angle θ in your code.
Update:
Well if you need only unit (normalized) vector's components you can divide the vx and vy with the length of the original vector. Like this:
// y2 - y1
var vy = listOfNodes[j].y - listOfNodes[i].y;
// x2 - x1
var vx = listOfNodes[j].x - listOfNodes[i].x;
// vector magnitude
var mag = Math.sqrt(vx * vx + vy * vy);
// get unit vector components
vy /= mag;
vx /= mag;
Using the above you will get the exactly the same results as you are getting from trig sin and cos functions.
But if you still need to use the original code and want to make 6.12...E-17 compare to 0, you can use the epsilon technique for comparing floats. So you can compare any value within epsilon's range from 0, using flllowing code:
function floatCompare(a:Number, b:Number, epsilon:Number):Boolean{
return (a >= (b - epsilon) && a <= (b + epsilon));
}
// To check for zero use this code, here i'm using 0.0001 as epsilon
if(floatCompare(vx, 0, 0.0001)){
// code here
}
So any deviation in the range of [b-epsilon, b+epsilon] would successfully compare to b. This is essential in case of floating point arithmetic.
I am trying to write a formula in ActionScript 3 that will give me var "z" (please see image below) in degrees, which I will then convert to radians.
I will already know the value of vars "x" and "y". Using trigonometry, how can I calculate the length of the hypotenuse and therefore the variable angle of var z? A solution in either AS3 or psuedocode would be very helpful. Thanks.
What you need is this:
var h:Number = Math.sqrt(x*x + y*y);
var z:Number = Math.atan2(y, x);
That should give you the angle in radians, you might need to swap x/y and possibly add or remove 90 degrees but it should do the trick! (Note that you don't even need h to get z when you're using atan2)
I use multiplication instead of Math.pow() just because Math is pretty slow, you can do:
var h:Number = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
And it should be exactly the same.
z is equivalent to 180 - angle of yH. Or:
180 - arctan(x/y) //Degrees
pi - arctan(x/y) //radians
Also, if actionscript's math libraries have it, use arctan2, which takes both the x and y and deals with signs correctly.
The angle you want is the same as the angle opposed to the one wetween y and h.
Let's call a the angle between y and h, the angle you want is actually 180 - a or PI - a depending on your unit (degrees or radians).
Now geometry tells us that:
cos(a) = y/h
sin(a) = x/h
tan(a) = x/y
Using tan(), we get:
a = arctan(x/y)
As we are looking for 180 - a, you should compute:
180 - arctan(x/y)
What #Patrick said, also the hypotenuse is sqrt(x^2 + y^2).