views:

125

answers:

2

I have a sprite animation, a small cannon rendered using a 3D app. I have exactly 360 frames for a 360 degree turn. Each image has a 100x100 pixel size.

So basically what I am trying todo is when I click anywhere in the page, the barrel of the cannon needs to rotate to point at the mouse cursor, sound simple maybe but I can't really get it to work very well, perhaps cause my math skills is lacking :P

What I currently have is something like this

/* This is my div with the cannon background image (360 images stitched into one) each  "image area" is 100x100px */

obj.cannon = $('#cannon');

/* Get the X/Y of the cannon loc in the dom */
var cannonX = $(obj.cannon).offset().left;
var cannonY = $(obj.cannon).offset().top;

/* Get radians using atan2 */
var radians = Math.atan2(e.pageY-cannonY, e.pageX-cannonX);

/* Convert to degrees */
var degrees = radians * (180/Math.PI);

And this is where I am, I mean since the image width is 100px and I need to move the background-position by 100px to move the cannon one degree right, because 360 images * 100px = 36000px in total. So the stitched sprite is like 36000px wide.

So

Insert weird calculation here based on the current backgroundPosition of the image-sprite and apply new backgroundPosition based on where you click with the mouse cursor, then use some sort of setTimeout(animateIt, speed); to "animate" the background position to the new position.

function animateIt(){
  if(newpos!=targetpos) { //Use < > here if we need to add or remove
      newpos+=100; //Until we arrive at the new backgroundPosition
      $(obj.cannon).css({backgroundPosition: newpos+'px' });
      setTimeout(animateIt, speed);
  }
}

Am I at all on the right track here, am I thinking correctly about this? I feel stupid, this should be a simple thing but right now I am having a brain meltdown I think =P. My problem is I don't know how to properly arrive at the "new target backgroundposition" and then animate it ++ or -- based on the current background position :/

A: 

Keep in mind that the degrees you get from atan will start pointing right for zero degrees and go clockwise from there (-90 is up, 90 is down).

Each position of your image should correspond to a specific angle. Once you have the degrees measured (it looks like you have that part right), use some type of mapping to translate your degrees to the proper image offset. I don't know what your image looks like so I can't help with that. Assuming your image starts pointing right, and goes around clockwise from there, the degrees will correspond directly the the offset for the right image. (I suggest you arrange your frames like this for ease...)

JoshD
Okay making a new render/sprite arranged like that, let's see how this turns out, thanks ;-)
quiggle
If the answer turns out to be helpful, the feedback would be appreciated :)
JoshD
Of course! Just waiting to see if this works, I think the whole mapping as you suggested is exactly what I am having problem wrapping my head around, but this should certainly help :]
quiggle
JoshD - Where are you getting this from? `atan` will never return 270 (It only does -pi/2 to pi/2 radians which is -180 to 180 degree as per my previous comment.
Peter Ajtai
Yeah I noticed, but.. I would prefer it return in negatives until -360 the way I have it set up =P not just -180 then +180
quiggle
@quiggle - You'd have to handle that yourself, and it's a slight pain due to how it jumps from +180 to -180. Basically for any degree less than 0 you can take `180 + (180 - (Math.abs(degree)))`.
Peter Ajtai
JoshD
@quiggle - Thanks Josh - So the correct angles for `atan` are: 3 o'clock = 0. Noon = 90. 9 o'clock = 180 and -180. 6 o'clock = -90. The [ **atan** ](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math/atan) method returns a numeric value between -pi/2 and pi/2 radians. also keep Math.atan2() in mind. ---- However, `atan` still will never return 270 degrees.
Peter Ajtai
@Peter, corrected in answer. Thanks.
JoshD
@quiggle - Actually Josh, if you use screen coordinates, which increase RIGHT and DOWN, the `atan` angles will go counterclockwise, since `atan` only goes clockwise in a coordinate system where X and Y increases RIGHT and UP! - So for the screen the angles are: 3oclock = 0 - 6oclock = 90 - 9oclock = 180 and -180 - and noon = -90. What a pain!
Peter Ajtai
Damn, you're right. I'll be leaving now.... :)
JoshD
+2  A: 

Well, here is a simplified working example with 10 images.

I'll post the code and jsFiddle now, and I might come back later to cover it in depth. But basically you just order your images correctly, and then you pick the segment by using (Segments - Math.floor(degree / (360 / segments))). You may have to adjust your 0 degree. For example, I made my 0 equal to what would normal by 90.

Pay attention to the fact that the screen coordinates, x and y, increase right and down. This makes the degrees of atan work clockwise instead of the usual counter clockwise in coordinate systems where x and y increase right and up.

I added in some text output that shows the degrees and image segment being shown.

jQuery handles normalizing the x and y position nicely. Just take care that your CSS setup is cross browser.

Working jsFiddle example


Here's our image: alt text


Here's our HTML:

<div id="main"><div id="img"></div></div>
<div id="info">
    <span></span><br/>
    <span></span>
</div>


CSS:

div#main {
    width:500px;
    height:500px;
    border:2px #000 solid; }
div#img {
    width:94px;
    height:119px;
    overflow:hidden;
    top:50%;
    left:50%;
    margin-left:-45px;
    margin-top:-60px;
    position:relative; 
    background-image:url('http://imgur.com/3UPki.png');
    background-position:0;}
div#info {
    position: absolute;
    bottom:0;
    left:0; }


Javascript / jQuery:

$(function() {
    var position = $("div#img").position(), 
        mouseX, mouseY, imgX, imgY, degree;
    imgX = position.left;
    imgY = position.top;
    $("#main").mousemove(function(e) {
          // degree is arctan y over x (soh,cah,toa)
        degree = Math.atan2((e.pageY - imgY),(e.pageX - imgX))*(180 / Math.PI);
        degree = (degree - 90) % 360;
          // jQuery normalizes pageX and pageY
          // transfrom from -180 to 180 ==> 0 to 360
        if (degree < 0) degree = 180 + (180 - (Math.abs(degree)));        
        rotate(degree);
        $("span:first").html("Segment: " + (9 - Math.floor(degree / 36)));
        $("span:last").html("Degree: " + Math.floor(degree));          
    }); 

    function rotate(degree) {
        var off = 9 - Math.floor(degree / 36);
        $("div#img").css("background-position",-off*94);
    }    
}); ​

Working jsFiddle example

Peter Ajtai
hehe so easy right :P thanks
quiggle
@quiggle - You're welcome ;)
Peter Ajtai
I am noticing some strange behaviour, when you get close to each 90/180/270 degree it works great but 45 degree means is less accurate, the cannon barrel is not pointing directly at the pointer, however when at these 90/180/270 values the cannon barrel always point exactly at the pointer, any idea why? Guess I'll have to compensate for some mysterious behaviour here hmm ;P
quiggle