tags:

views:

142

answers:

6

To me, this is the obvious way to do this. This being : given a start point and an end point, tell me if the end point is up / down / left / upleft / downright... etc of the start point. Heres the core of the logic :

function getSector() {
    var y = startY - endY;
    var x = startX - endX;
    var angle = Math.atan2(y,x) * 57.29578;//~57 deg per radian
    //atan2(y,x) returns a num between -PI and PI, represents angle in radians
    console.log("angle:" + angle);
    if( (angle > -22.5) && (angle < 22.5) ) attackDir = "left";
    if( (angle > 22.5) && (angle < 67.5) ) attackDir = "upleft";
    if( (angle > 67.5) && (angle < 112.5) ) attackDir = "up";
    if( (angle > 112.5) && (angle < 157.5) ) attackDir = "upright";
    if( (angle > 157.5) && (angle <= 180) ) attackDir = "right";
    if( (angle > -179) && (angle <= -157) ) attackDir = "right";
    if( (angle > -157) && (angle < -112.5) ) attackDir = "downright";
    if( (angle > -112.5) && (angle < -67.5) ) attackDir = "down";
    if( (angle > -67.5) && (angle < -22.5) ) attackDir = "downleft";
    console.log("attackDir:" + attackDir);
}

Im interested in seeing a better way, but more so, HOW you arrived at that way.

I guess a key of sorts would be in order : left = 0 up = 90 right = 179 OR -179 (down is like a negative mirror of up) down = -90

+4  A: 

When doing bounds checking, it's much more readable if you create a function named Between(min, val, max);

function Between(min, val, max)
{
 return (val > min) && (val < max);
}

In addition, you can test for right/left, and then after that test for down/up.. while appending to a string. Then your logic is cut down to 4 if statements.

var final="";
if(/*IsUp*/) final="up"
else if(/*IsDown*/) final="down"

if(/*IsRight*/) final+= "right";
else if(/*IsLeft*/) final += "left";

Edit: As stated by Stefan, 57.29578 is a magical number. I didn't even realize you meant 180/(Math.Pi)... As he also stated, the "optimization" you're making makes your code just illegible, and the performance gain is moot.

You should always check for u/d/l/r by x/y position relative to an origin, not by angles.

ItzWarty
damn, that is damn nice. It returns true if both conditions are met. Why cant I come up with that kind of code? =) I spent the better part of 2 hours playing with my example, and you answer within 10 mins. Thank you.
jason
Why the one -1?
ItzWarty
and why was this downvoted too?
Anurag
I dont know who / why the downvotes. Should be a way to tell. The above is a good answer, gonna mark it as the answer. TY all!
jason
There is an argument that you might not consider a direction to be up+left if it was almost entirely up and only slightly left. A common option for that is a "dead zone". Another is to say that if your up is at least k times your left, you count it as pure up. You can of course calculate k (at compile time) to give the same effect as the original angle-based calculation. Handling the quadrants/signs is a minor hassle for this.
Steve314
My k factor stuff is pretty close to Muad'dibs answer - it's the slope value for one possible case. Having 4 different slope values (and a sign check to double the cases) is roughly equivalent to having a single k value and doing symmetry over the quadrants/signs for the 8 cases, each case being a dividing line between two result directions.
Steve314
I didn't downvote this, BUT this answer isn't that good IMHO. 1. can you elaborate on to form of IsUp / IsDown / IsLeft / IsRight in the answer, they are quite crucial and easily be misinterpreted. 2. I don't find Between(q,w,e) clearer. Not only is it in general not clear which arguments specify the range (e.g. q < w < e versus w < q < e) but especially it DOESN'T SPECIFY if q == w and q == e will return True too! The shown implementation wouldn't work in the questioner's code for the corner cases `val == min` or `val == max` exactly. But exact same bug is easy to spot in the original code.
catchmeifyoutry
Why is `Between` capitalized? That's quite funky, as capitalized functions tend to be constructors.
trinithis
+1  A: 

How about using slope?

m = dy/dx

Muad'Dib
can you elaborate?
jason
why was this downvoted?
Anurag
How do you differentiate between 45 degrees and 225 degrees with slope? The slopes of the lines are exactly the same.
ItzWarty
@ItzWarty - yes, but one is negative.
Anurag
@Anurag, no, one is not negative. The slope between the origin and 1,1 is exactly the same as the slope from the origin to -1,-1. The line is exactly the same. As such, topright will have the same slope as bottomleft...
ItzWarty
@ItzWarty - you're right, slope will only give the general direction of the line, but need an additional step to disambiguate.
Anurag
yeah, I realized after I posted this that there would be no way to differentiate all the directions this way. I deserve the downvote.
Muad'Dib
A: 

Don't use angles. Also, if you're using 180/PI, just write 180/PI. You get very little speed for a huge readability impact.

var dy = endY - startY;
var dx = endX - startX;
var isUp = dy < 0;
var isDown = dy > 0;//probably use some fudge here for a better neutral zone, if you're parsing input
var isLeft = dx < 0;
var isRight = dx > 0;

This assumes a coordinate system that has (0,0) in the top left, and (n,n) in the bottom right, as in many graphics applications.

Stefan Kendall
ahh i see what you did there... clever.could go even further :var direction = endY < startY ? "top" : "bottom";direction += endX < startX ? "left" : "right";and that would make everything turn out to a topleft / topright / bottomleft / bottomright.. which if you think of it, most gestures arent perfectly vertical or horizontal.
jason
If you need gestures, you'll want to change "0" to some "fudge" number.
Stefan Kendall
A: 

You might have less code by indexing a suitable array of strings ("right","downright","downright", ...) with parseInt((angle+180)/22.5).

ssegvic
A: 
if      (angle <= -157)  attackDir = "right";
else if (angle < -112.5) attackDir = "downright";
else if (angle < -67.5)  attackDir = "down";
else if (angle < -22.5)  attackDir = "downleft";
else if (angle < 22.5)   attackDir = "left";
else if (angle < 67.5)   attackDir = "upleft";
else if (angle < 112.5)  attackDir = "up";
else if (angle < 157.5)  attackDir = "upright";
else if (angle <= 180)   attackDir = "right";
phoffer
+1  A: 

Since the function just functions by binning the angle range [-PI, PI] in 8 equal bins, there is a simple linear mapping from the angle to a bin index. This is a general pattern for adding labels over a fixed number of equally sized bins over a fixed range.

Example:

var angle = Math.atan2(y,x) * 57.29578;//~57 deg per radian

where angle lies within range [-180, 180]. So, (angle + 157.5) / 45.0 is within [-0.5, 7.5] range, and (angle+157.5)/45.0 + 0.5 within [0.0, 8.0] range. This bins are seperated at 0.5, 1.5, 2.5 ... 7.5, so rounding the value will bring everythin at the left/right of the boundry to a correct bin index:

var bin_index = parseInt(Math.round((angle + 157.5) / 45.0 + 0.5));

Since bin 8 and bin 0 are actually the same, we use the module operator to merge these bin indices next:

bin_index = bin_index % 8; // bin_index == 8 becomes bin_index == 0 too

Now you can look up your result string using the bin index :)

var bin_labels = ['right', 'downright', 'down', 'downleft', 'left', 'upleft', 'up', 'upright'];
return bin_labels[bin_index];

EDIT: putting above lines together:

function getSector() {
    var dy = startY - endY;
    var dx = startX - endX;
    var angle = Math.atan2(dy,dx) * 180.0 / Math.PI; // we can assume -180.0 <= angle <= 180.0

    // partition angle space in 8 equal bins,
    // lowest bin should have index 0, the highest 7
    var bin_index = parseInt(Math.round((angle + 157.5) / 45.0 + 0.5));
    bin_index = bin_index % 8; // ensure bin_index == 8 -> bin_index == 0

    // return the correct bin label
    var bin_labels = ['right', 'downright', 'down', 'downleft', 'left', 'upleft', 'up', 'upright'];
    return bin_labels[bin_index];
}
catchmeifyoutry