1
\$\begingroup\$

I need a bit of math help for my game.

I'm using Javascript and the Canvas tag to create a circular world, think God Finger, and Reus. The current version of my game is here: http://lazyeels-sandbox.appspot.com/

The problem:
The Canvas draws an arc (or circle) starting at 90 degrees going clockwise. See an example image.

My game splits the world in to segments (tiles). The world is 10 tiles in length I would like to calculate which tile the mouse is hovering over so the player can add buildings or adjust the height of the tile.

So I need to calculate the offset and deduct it from the mouse angle (either radians or degrees is fine). However, I can't seem to get it right. I know that in theory I should do the following:

  • Calculate the angle of the mouse.
  • Calculate the tile by dividing by the segment size (as per regular tile maps i.e. y-axis/tilewidth).
  • Remove the offset of the angle to point 0 degrees at the top of the circle rather than 90 degrees clockwise).
  • Deduct the offset to get the new tile position.

Here is the main function I'm using;

var getMouseAngle = function(mouse){
    var world_length = 10;
    var segment = (Math.PI * 2)/world_length;

    // Get the distance
    var dx = mouse.x - ((canvas.width * 0.5) - Game.camera.xScroll);
    var dy = mouse.y - ((canvas.height - Game.camera.yScroll) * Game.camera.zoom);

    // calculate the angle
    var arctan = Math.atan2(dy, dx); 

    // Reset the angle if it is a negative value
    var angle =  dy < 0 ? (Math.PI * 2) + arctan: arctan; // perhaps this is where I need to apply the offset.


    // Calculate the tile
    var orig_tile = angle/segment;


    // Calculate the offset
    var offset_tile = ((Math.PI * 3/2)/segment); // calculate offset clockwise = 270 degrees to the top of the circle (where the player sits).

    var tile = (orig_tile - offset_tile) // This is soooooo wrong

    return {
        dx: dx, 
        dy: dy, 
        degrees: (angle * 180/Math.PI).toFixed(2), 
        radian: angle.toFixed(2)
        tile: Math.floor(tile),  
    };

By comparison the player tile function was way easier:

Player.getTile = function(rotation){
    var tile = Math.floor(rotation/Game.level.segment); 

    if(rotation > 0){
        tile = Math.abs((Game.level.world.length - tile)-1);
    } else {
        tile = Math.abs(tile)-1;
    }
    if(rotation == 0){
        tile = 0;
    }
    return tile;
};

Does anyone know where I'm going wrong in the calculation, i.e. how and when should I apply the offset - on the angle before calculating the tile, or afterwards?

I haven't shown the other permutations I tried, such as deducting the offset from the original angle, all of these resulted in the wrong tile ID being returned.

I know it should be a similar process to the player function, i.e. using the word_length in the calculation.

\$\endgroup\$
9
  • \$\begingroup\$ The world is round, how is it made of tiles? Do you refer the pie slice shaped pieces as tiles? \$\endgroup\$
    – AturSams
    Commented Feb 4, 2014 at 19:58
  • \$\begingroup\$ I should say it is made of segments, but you can think of them as tiles as the world is essentially a single dimensional array with each entry represented by a tile, which could be assigned any sort of texture or object just like a 2D tile map. I agree with your comment, but for implementation reasons I like to think of them as tiles. :-) \$\endgroup\$ Commented Feb 4, 2014 at 20:02
  • \$\begingroup\$ Why is only y multiplied by zoom? \$\endgroup\$
    – AturSams
    Commented Feb 4, 2014 at 20:29
  • \$\begingroup\$ I don't see where you include the world's or cameras angle. \$\endgroup\$
    – AturSams
    Commented Feb 4, 2014 at 20:31
  • \$\begingroup\$ y is only multiplied by zoom just because the x axis is not affected since the world is always centred at half the canvas width regardless of the zoom level. \$\endgroup\$ Commented Feb 4, 2014 at 20:47

2 Answers 2

1
\$\begingroup\$

Let N be the number of slices:

(alpha + beta) * N / (2 * PI)

enter image description here

*Please note that alpha is the amount the circle is rotated Clock-Wise so if your alpha represents rotation Counter-Clock-Wise, you need to subtract it.

\$\endgroup\$
3
  • \$\begingroup\$ Many thanks Arthur for your beautiful diagram and an excellent description. I see how it works in theory and will put it in to practice and let you know if I have any further queries. Also thanks for taking a look at my question and engaging with it. Really appreciated. \$\endgroup\$ Commented Feb 4, 2014 at 20:56
  • \$\begingroup\$ @user2257705 When I move the mouse cursor to the left, the debug shows the angle is going down? Strange because the angel is suppose to start at zero on the right side (normally) and grow as you go left. \$\endgroup\$
    – AturSams
    Commented Feb 5, 2014 at 10:29
  • \$\begingroup\$ That's right. I see actually that my post didn't display the example image. !Canvas draw method. The canvas draws the arcs from the right at 90 degrees clockwise, as you see at the link to the image. So by offsetting the input of the mouse to place 0 degrees at the top of the circle where the player is (to compensate for canvas drawing issue), moving left should cause the tileID to decrease from world length to 0 and moving right should increase the tileID from 0 to world length. \$\endgroup\$ Commented Feb 5, 2014 at 13:13
0
\$\begingroup\$

@arthur-wulf-white Thanks for your help with this problem. Having this discussion has really helped. I think it works now, but this involved a bit of hacking so I'm not too happy with the code solution but here it goes.

Level.prototype.getMouseAngle = function(mouse){
    // Calculate the arctan between the mouse and the level x and y at the world center.
    var dx = mouse.x - (this.x - Game.camera.xScroll);
    var dy = mouse.y - ((this.y - Game.camera.yScroll) * Game.camera.zoom);
    var arctan = Math.atan2(dy, dx); 

    // Reset angle to 0 on full rotation
    if(dy < 0){
        angle = (Math.PI * 2) + arctan;
    } else { 
        angle = arctan;
    }

    // Deduct world rotation
    angle -= this.rotation;

    // Get original tile position offset tile position and calculate final tile.
    // N.B segment = (Math.PI * 2)/this.world.length.

    var orig_tile = angle/this.segment;
    var offset_tile = this.offset/this.segment;
    var final_tile = Math.floor(offset_tile - orig_tile);

    // Adjust tile according to offset.
    var tile = final_tile;
    if (tile < 0){
        tile = (tile + this.world.length);
    } 
    if(tile > 0){
        tile = Math.abs((this.world.length-1) - tile);
    }
    if(this.world.length + final_tile == this.world.length){
        tile = this.world.length - 1;
    }

    return {dx: dx, dy: dy, degrees: (angle * 180/Math.PI).toFixed(2), tile: Math.floor(tile), radian: angle.toFixed(2)}; 
};

If you see a better solution then I'm all ears, in the mean time thanks again for all your effort spent solving my problem, I will give you a big credit if my game ever becomes something. ;-)

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .