views:

76

answers:

3

Background: I have 8 images for every sprite in my bird's view JavaScript game, representing top, top-right, right, right-bottom etc., depending on the player's space ship speed.

Question: Given the values sprite.speed.x and sprite.speed.y (which could be something like 4 and -2.5, or 2 and 0 for instance), how do I get the correct angle in degrees? Given that angle, I could then have a lookup for which degrees value represents which sprite image. Or perhaps there's an even easier way. (Currently I'm just using something like "if x below zero use left image" etc. which will result in diagonal images used almost all of the time.)

Searching around, I found ...

angle = Math.atan2(speed.y, speed.x);

... but somehow I'm still missing something.

PS: Zero speed can be ignored, these sprites will just use whatever was the last valid direction image.

Thanks so much for any help!

A: 

You were on the right track. Normalize your speed vector (check for both components being 0 first) , call atan2 on it, and then convert the radians value you get to some sort of friendly direction enum or something that you can use to pick the right sprite.

RCIX
+2  A: 

What you suggest is exactly right! Note that the result of Math.atan2 is in radians, and you're probably more familiar with degrees; you can convert using angle_degrees = angle*(180./pi).

(Note also that you don't need to normalize as RCIX suggested, though you can if you want to. What you have, angle = Math.atan2(speed.y, speed.x);, should work just fine.)

tom10
+3  A: 

Good question! I liked tom10's answer (on the mark, +1), but wondered if it can be done without much trigonometry. Here's a solution in short, followed by an explanation.

// slope is a constant, 0.414...; calculate it just once
var slope = Math.tan(Math.PI/8);

// do this for each x,y point
var s1 = x * slope + y > 0 ? 0 : 1;
var s2 = y * slope + x > 0 ? 0 : 1;
var s3 = y * slope - x < 0 ? 0 : 1;
var s4 = x * slope - y > 0 ? 0 : 1;

var segment = 4 * s4 + 2 * (s2 ^ s4) + (s1 ^ s2 ^ s3 ^ s4);

This sets the value of segment between 0 and 7. Here's an example with 2000 random points (full source code at the end of the answer). Using the x,y values of the sprite's speed, you can use the segment value to pick up the appropriate sprite image.

alt text

Tadaa!

So how does this work? Our segment expression does look a bit cryptic.

Observation one: we want to split the circle around the point into 8 segments of equal angular dimension. 360/8 = 45 degrees per segment. Four of the 8 segments are centered on one of the two sides of the x and y axes, sliced at 45/2 = 22.5 degrees each.

alt text

Observation two: The equation of a line on a plane, a*x + b*y + c = 0, when turned into an inequality, a*x + b*y + c > 0 can be used to test on which side of the line a point is located. All our four lines cross the origin (x=0, y=0), and hence force c=0. Further, they are all at a 22.5 degrees angle from either the x or the y axis. This gets us the four line equations:

y = x * tan(22.5); y = -x * tan(22.5); x = y * tan(22.5); x = -y * tan(22.5)

Turned into inequalities we get:

x * tan(22.5) - y > 0; x * tan(22.5) + y > 0; y * tan(22.5) - x > 0; y * tan(22.5) + x > 0

Testing the inequalities for a given point lets us know on each side of each line it lies: alt text alt text

alt text alt text

Observation three: we can combine the test results to obtain the segment number pattern we want. Here's a visual breakdown:

In sequence: 4 * s4, 2 * (s2 ^ s4) and the sum 4 * s4 + 2 * (s2 ^ s4) alt text alt text alt text

(The ^ symbol is the Javascript XOR operator.)

And here is s1 ^ s2 ^ s3 ^ s4, first on its own, and then added to 4 * s4 + 2 * (s2 ^ s4) alt text alt text

Extra credit: can we tweak the calculation to use only integer arithmetic? Yes -- if x and y are known to be integers, we could multiply both sides of the inequalities by some constant (and round off), resulting in completely integer math. (This would be lost, however, on Javascript, whose numbers are always double precision floating point.):

var s1 = x * 414 + y * 1000 > 0 ? 0 : 1;
var s2 = y * 414 + x * 1000 > 0 ? 0 : 1;
var s3 = y * 414 - x * 1000 < 0 ? 0 : 1;
var s4 = x * 414 - y * 1000 > 0 ? 0 : 1;

Full source code for our sample above: (just drop it in a new html file, and open in any browser)

<html>
    <head>
        <style type="text/css">
            .dot { position: absolute; font: 10px Arial }
            .d0 { color: #FF0000; }
            .d1 { color: #FFBF00; }
            .d2 { color: #7fcc00; }
            .d3 { color: #00FF7F; }
            .d4 { color: #00FFFF; }
            .d5 { color: #5555FF; }
            .d6 { color: #aF00FF; }
            .d7 { color: #FF00BF; }
        </style>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"&gt;&lt;/script&gt;
        <script type="text/javascript">
            $(function() {
                var $canvas = $("#canvas");
                var canvasSize = 300;
                var count = 2000;
                var slope = Math.tan(Math.PI/8);

                $canvas.css({ width: canvasSize, height: canvasSize });
                for (var i = 0; i < count; ++i) {

                    // generate a random point
                    var x = Math.random() - 0.5;
                    var y = Math.random() - 0.5;

                    // draw our point
                    var $point = $("<div class='dot'></div>")
                        .css({
                            left: Math.floor((x + 0.5) * canvasSize) - 3,
                            top:  Math.floor((y + 0.5) * canvasSize) - 6 })
                        .appendTo($canvas);

                    // figure out in what segment our point lies
                    var s1 = x * slope + y > 0 ? 0 : 1;
                    var s2 = y * slope + x > 0 ? 0 : 1;
                    var s3 = y * slope - x < 0 ? 0 : 1;
                    var s4 = x * slope - y > 0 ? 0 : 1;
                    var segment = 4 * s4 + 2 * (s2 ^ s4) + (s1 ^ s2 ^ s3 ^ s4);

                    // modify the point's html content and color
                    // (via its CSS class) to indicate its segment
                    $point
                        .text(segment)
                        .addClass("d" + segment);
                }
            });
        </script>
    </head>
    <body>
        <div id="canvas" style="position: absolute; border: 1px solid blue">
        </div>
    </body>
</html>
Oren Trutner
Just to say I'm blown away by the quality and depth of this answer.
Philipp Lenssen
I want to mark two answers as correct, but StackOverflow doesn't allow me!
Philipp Lenssen