Math/Calculations for infinite/repeating world with rotation
Asked Answered
M

3

9

How do I make a infinite/repeating world that handles rotation, just like in this game:

http://bloodfromastone.co.uk/retaliation.html

I have coded my rotating moving world by having a hierarchy like this:

Scene
  - mainLayer (CCLayer)
     - rotationLayer(CCNode)
        - positionLayer(CCNode)

The rotationLayer and positionLayer have the same size (4000x4000 px right now).

I rotate the whole world by rotating the rotationLayer, and I move the whole world by moving the positionLayer, so that the player always stays centered on the device screen and it is the world that moves and rotates.

Now I would like to make it so that if the player reaches the bounds of the world (the world is moved so that the worlds bounds gets in to contact with the device screen bounds), then the world is "wrapped" to the opposite bounds so that the world is infinite. If the world did not rotate that would be easy, but now that it does I have no idea how to do this. I am a fool at math and in thinking mathematically, so I need some help here.

Now I do not think I need any cocos2d-iphone related help here. What I need is some way to calculate if my player is outside the bounds of the world, and then some way to calculate what new position I must give the world to wrap the world.

I think I have to calculate a radius for a circle that will be my foundry inside the square world, that no matter what angle the square world is in, will ensure that the visible rectangle (the screen) will always be inside the bounds of the world square. And then I need a way to calculate if the visible rectangle bounds are outside the bounds circle, and if so I need a way to calculate the new opposite position in the bounds circle to move the world to. So to illustrate I have added 5 images.

Visible rectangle well inside bounds circle inside a rotated square world:
Visible rectangle well inside bounds circle inside a rotated square world

Top of visible rectangle hitting bounds circle inside a rotated square world:
enter image description here

Rotated square world moved to opposite vertical position so that bottom of visible rectangle now hitting bounds circle inside rotated world:
enter image description here

Another example of top of visible rectangle hitting bounds circle inside a rotated square world to illustrate a different scenario:
enter image description here

And again rotated square world moved to opposite vertical position so that bottom of visible rectangle now hitting bounds circle inside rotated world:
And again rotated square world moved to opposite vertical position so that bottom of visible rectangle now hitting bounds circle inside rotated world

Moving the positionLayer in a non-rotated situation is the math that I did figure out, as I said I can figure this one out as long as the world does not get rotate, but it does. The world/CCNode (positionLayer) that gets moved/positioned is inside a world/CCNode (rotationLayer) that gets rotated. The anchor point for the rotationLayer that rotates is on the center of screen always, but as the positionLayer that gets moved is inside the rotating rotationLayer it gets rotated around the rotationLayer's anchor point. And then I am lost... When I e.g. move the positionLayer down enough so that its top border hits the top of the screen I need to wrap that positionLayer as JohnPS describes but not so simple, I need it to wrap in a vector based on the rotation of the rotationLayer CCNode. This I do not know how to do.

Thank you
Søren

Mateusz answered 11/11, 2011 at 13:30 Comment(0)
G
7

Like John said, the easiest thing to do is to build a torus world. Imagine that your ship is a point on the surface of the donut and it can only move on the surface. Say you are located at the point where the two circles (red and purple in the picture) intersect:

Circles on a torus.

If you follow those circles you'll end up where you started. Also, notice that, no matter how you move on the surface, there is no way you're going to reach an "edge". The surface of the torus has no such thing, which is why it's useful to use as an infinite 2D world. The other reason it's useful is because the equations are quite simple. You specify where on the torus you are by two angles: the angle you travel from the "origin" on the purple circle to find the red circle and the angle you travel on the red circle to find the point you are interested in. Both those angles wrap at 360 degrees. Let's call the two angles theta and phi. They are your ship's coordinates in the world, and what you change when you change velocities, etc. You basically use them as your x and y, except you have to make sure to always use the modulus when you change them (your world will only be 360 degrees in each direction, it will then wrap around).

Suppose now that your ship is at coordinates (theta_ship,phi_ship) and has orientation gamma_ship. You want to draw a square window with the ship at its center and length/width equal to some percentage n of the whole world (say you only want to see a quarter of the world at a time, then you'd set n = sqrt(1/4) = 1/2 and have the length and width of the window set to n*2*pi = pi). To do this you need a function that takes a point represented in the screen coordinates (x and y) and spits out a point in the world coordinates (theta and phi). For example, if you asked it what part of the world corresponds to (0,0) it should return back the coordinates of the ship (theta_ship,phi_ship). If the orientation of the ship is zero (x and y will be aligned with theta and phi) then some coordinate (x_0,y_0) will correspond to (theta_ship+k*x_0, phi_ship+k*y_0), where k is some scaling factor related to how much of the world one can see in a screen and the boundaries on x and y. The rotation by gamma_ship introduces a little bit of trig, detailed in the function below. See the picture for exact definitions of the quantities. !Blue is the screen coordinate system, red is the world coordinate system and the configuration variables (the things that describe where in the world the ship is). The object represented in world coordinates is green.

The coordinate transformation function might look something like this:

# takes a screen coordinate and returns a world coordinate
function screen2world(x,y)
    # this is the angle between the (x,y) vector and the center of the screen           
    alpha = atan2(x,y); 
    radius = sqrt(x^2 + y^2); # and the distance to the center of the screen

    # this takes into account the rotation of the ship with respect to the torus coords
    beta = alpha - pi/2 + gamma_ship;

    # find the coordinates
    theta = theta_ship + n*radius*cos(beta)/(2*pi);
    phi = phi_ship + n*radius*sin(beta)/(2*pi));

    # return the answer, making sure it is between 0 and 2pi
    return (theta%(2*pi),phi%(2*pi))

and that's pretty much it, I think. The math is just some relatively easy trig, you should make a little drawing to convince yourself that it's right. Alternatively you can get the same answer in a somewhat more automated fashion by using rotations matrices and their bigger brother, rigid body transformations (the special Euclidian group SE(2)). For the latter, I suggest reading the first few chapters of Murray, Li, Sastry, which is free online.

If you want to do the opposite (go from world coordinates to screen coordinates) you'd have to do more or less the same thing, but in reverse:

beta = atan2(phi-phi_ship, theta-theta_ship);
radius = 2*pi*(theta-theta_ship)/(n*cos(beta));
alpha = beta + pi/2 - gamma_ship;
x = radius*cos(alpha);
y = radius*sin(alpha);
Grunter answered 18/11, 2011 at 21:42 Comment(4)
Thank you Vlad for the long answer. Now lets assume I am very very bad at math (I really am), could you then break this down a little more for me? Could you maybe make a complete pseudo code example where you start out having just the "real" x and y (anchor point is in center) coordinates and width/height of the rotating moving world? You answer, how simple it may be, is to complicated for me. E.g. what does the first method you describe return (a array?) and how is that result used in the second code part?Mateusz
the World() function is superfluous, I will remove it. My example shows how you loop over the whole scene and draw it one pixel at a time. That is, if you give me the coordinate in x and y, it will transform that into theta and phi which are to be used in any function that deals with the world: those are the coordinates that the world is represented in. I am going to change the example so that it shows a simple coordinate transformation instead of a complete screen rendering.Grunter
Thank you again Vlad, I am sorry for being a fool here but I still do not get it. So I put in my x and y and the rotation of the ship (gamma_ship, is that just degrees in screen rotation?) and it spits out the x and y coordinates on the torus right? Then what? How do I use these coordinates, will they always be correct so I can simply convert them back to screen coordinates?Mateusz
How you use the coordinate transformations depends on exactly what you're trying to achieve. If you want to know what you should draw at a pixel described by x and y on the screen, then you calculate theta and phi corresponding to that point and look at that coordinate on the torus. if you want, say, to draw a line between two points in the world you'd use the opposite coordinate transformation: (theta1,phi1)->(x1,y1) and (theta2,phi2)->(x2,y2). You then draw a line on the screen between (x1,y1) and (x2,y2), but it will represent a line (circle, whathaveyou) on he torus.Grunter
M
2

You need to define what you want "opposite bounds" to mean. For 2-dimensional examples see Fundamental polygon. There are 4 ways that you can map the sides of a square to the other sides, and you get a sphere, real projective plane, Klein bottle, or torus. The classic arcade game Asteroids actually has a torus playing surface.

The idea is you need glue each of your boundary points to some other boundary point that will make sense and be consistent.

If your world is truly 3-dimensional (not just 3-D on a 2-D surface map), then I think your task becomes considerably more difficult to determine how you want to glue your edges together--your edges are now surfaces embedded in the 3-D world.

Edit:

Say you have a 2-D map and want to wrap around like in Asteroids.

If the map is 1000x1000 units, x=0 is the left border of the map, x=999 the right border, and you are looking to the right and see 20 units ahead. Then at x=995 you want to see up to 1015, but this is off the right side of the map, so 1015 should become 15.

If you are at x=5 and look to the left 20 units, then you see x=-15 which you really want to be 985.

To get these numbers (always between 0 and 999) when you are looking past the border of your map you need to use the modulo operator.

new_x = x % 1000; // in many programming languages

When x is negative each programming language handles the result of x % 1000 differently. It can even be implementation defined. i.e. it will not always be positive (between 0 and 999), so using this would be safer:

new_x = (x + 1000) % 1000; // result 0 to 999, when x >= -1000

So every time you move or change view you need to recompute the coordinates of your position and coordinates of anything in your view. You apply this operation to get back a coordinate on the map for both x and y coordinates.

Multiplicity answered 11/11, 2011 at 19:49 Comment(4)
My world is 2d and very similar to asteroids. Do you have some more hints for a math-fool to figure this one out?Mateusz
Sorry, I'm not familiar enough with cocos2d and CCNode to understand how to do it using that.Multiplicity
Can you explain it in math and maybe even pseudo code? That would help me a lot.Mateusz
Seems like the CCNodes have the ability to apply transformations of your layers to translate between the coordinates on your map and the relative screen coordinates. I do not know how to do this. You may be able to subclass the appropriate CCNode and override some function to return the wrapped coordinates. But, like I said, I really do not know how to do this or if it's actually possible. There must be other people that have wanted to do what you want, so maybe you can find something else on the Internet.Multiplicity
C
0

I'm new to Cocos2d, but I think I can give it a try on helping you with the geometry calculation issue, since, as you said, it's not a framework question.

I'd start off by setting the anchor point of every layer you're using in the visual center of them all.

Then let's agree on the assumption that the first part to touch the edge will always be a corner.

In case you just want to check IF it's inside the circle, just check if all the four edges are inside the circle.

In case you want to know which edge is touching the circumference of the circle, just check for the one that is the furthest from point x=0 y=0, since the anchor will be at the center.

If you have a reason for not putting the anchor in the middle, you can use the same logic, just as long as you include half of the width of each object on everything.

Capsular answered 18/11, 2011 at 10:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.