formula for game xy to image (pixel) xy - math

It's probably quite simple, but i can't find what i need on search engines... (it's like they used to know better what i was looking for)
I need to convert in-game coordinates to "coordinates" on an image so i can add ... say a pixel on the image to represent the location of the in-game coordinates.
The image is a map, the size is 2384x2044 (width x height).
The in-game 0,0 = the middle of the in-game map, this would also be the middle of the image.
So it's easy to find the xy to print a pixel on the middle of image:
2384 : 2 = 1192 and 2044 : 2 = 1022, so the xy for 0,0 in-game on the image is 1192,1022.
Now, for example, if i move up and slightly to the left in-game the coordinates become: -141.56,1108.11 - How can i calculate the correct xy for the image?
image: http://i.imgur.com/yfiwfO7.png?1

To recap, you want to scale game coordinates of -3000 to +3000 in both axes and offset them to centre them on your image; in that case the computations you want are
pixel_x = 1192 + (game_x * 1192 / 3000.0)
pixel_y = 1022 - (game_y * 1022 / 3000.0)
Note the minus on the y line to invert the direction of the offset. Your game coordinates are floating point so I've made the 3000s floating point by adding a .0 - you didn't say what language you were using so this may or may not be the correct syntax.
However you probably ought to avoid putting constants into this in case you ever want to change the size of the playfield or the image. It would be better to
set up constants in your program for the playfield dimensions
set up constants or global variables for the size of the image: you can read this from the image as you load it
pre-compute the values 1192 / 3000.0 and 1022 / 3000.0 (but using your image constants) to save one floating point operation for each scale? probably not worth it nowadays as a speed optimisation, though, and you might sacrifice a tiny bit of floating point accuracy at the end of the mantissa, but that won't matter here.

Related

How to fix zoom towards mouse routine?

I'm trying to learn how to zoom towards mouse using Orthographic projection and so far I've got this:
def dolly(self, wheel, direction, x, y, acceleration_enabled):
v = vec4(*[float(v) for v in glGetIntegerv(GL_VIEWPORT)])
w, h = v[2], v[3]
f = self.update_zoom(direction, acceleration_enabled) # [0.1, 4]
aspect = w/h
x,y = x-w/2, y-h/2
K1 = f*10
K0 = K1*aspect
self.left = K0*(-2*x/w-1)
self.right = K0*(-2*x/w+1)
self.bottom = K1*(2*y/h-1)
self.top = K1*(2*y/h+1)
x/y: mouse screen coordinates
w/h: window width/height
f: factor which goes from 0.1 to 4 when scrolling down/up
left/right/bottom/top: values used to compute the new orthographic projection
The results I'm getting are really strange but I don't know which part of the formulas I've messed up.
Could you please spot which part of my maths are wrong or just post a clear pseudocode I can try? Just for the record, I've read&tested quite a lot of versions out there on the internet but haven't found yet any place where this subject is explained properly.
Ps. You don't need to post any SO link related to this subject as I've read all of them already :)
I'm going to answer this in a general way, based on the following set of assumptions:
You use a matrix P for the (ortho) projection describing the actual mapping of your eye space view volume onto the standard view volume [-1,1]^3 OpenGL will clip against (see also assumption 2) and a matrix V for the view transformtation, that is postion and orientation of the "camera" (if there is such a thing, especially in ortho projections) and basically establishing an eye space where your view volume will be defined relative to.
I will ignore the homogeneous clip space, as you work with completely affine ortho projections only, that means NDC coordinates and clip space will be identical, and no tricks to any w coordinate are applied.
I assume default GL conventions for eye space and projection matrices, notably eye space origin is camera location and camera lookat direction is -z
The viewport is filling the window completely.
Windows Space is default OpenGL convention where the origin is at the bottom left.
Mouse coordinates are in some window-specific coordinate frame where the origin is at top left, mouse is at integer pixel coordinates.
I assume that the view volume defined by P is symmetrical: right = -left and top = -bottom, and it is also supposed to stay symmetrical after the zoom operation, therefore, to compensate for any movement, the view matrix V must be adjusted, too.
What you want to get is a zoom such that the object point under the mouse cursor does not move, so becomes the center of the scale operation. The mouse cursor itself is only 2D and a whole straight line in the 3D space will be mapped to the same pixel location. However, in an ortho projection, that line will be orthogonal to the image plane, so we don't need to bother much with the third dimension.
So what we want is to scale the current situation with P_old (defined by the ortho parameters l_old, r_old, b_old, t_old, n_old and f_old) and V_old (defined by "camera" position c_old and ortientation o_old) by a zoom factor s at mouse position (x,y) (in the space from assumption 6).
We can see a few things directly:
the near and far plane of the projection should be unaffected by the operation, so n_new = n_old and f_new = f_old.
the actual camera orientation (or lookat direction) should also be unaffected: o_new = o_old
If we zoom in by a factor of s, the actual view volume must be scaled by 1/s, since when we zoom in, a smaller part of the complete world is mapper on the screen than before (and appears bigger). So we can simply scale the frustum parameters we had:
l_new = l_old / s, r_new = r_old / s, b_new = b_old / s, t_new = t_old / s
If new only replace P_old by P_new, we get the zoom, but the world point under the mouse cursor will move (except the mouse is exactly in the center of the view). So we have to compensate for that by modifying the camera position.
Let's first put the mouse coords (x,y) into OpenGL window space (assumptions 5 and 6):
x_win = x + 0.5
y_win = height - 0.5 - y
Note that besides mirroring y, I also shift the coordinates by half a pixels. That's because in OpenGL window space, pixel centers are at half-inter coordinates, while I assume that your integer mouse coordinates are to represent the center of the pixel you click onto (will not make a big difference visually, but still)
Now let's further put the coords into Normalized Device Space (relying on assumption 4 here):
x_ndc = 2.0 * x_win / width - 1
y_ndc = 2.0 * y_win / height - 1
By assumption 2, clip and NDC coordiantes will be identical, and we can call the vector v our NDC/space mouse coordinates: v = (x_ndc, y_ndc, 0, 1)^T
We can now state our "point under mouse must not move" condition:
inverse(V_old) * inverse(P_old) * v = inverse(V_new) * inverse(P_new) * v
But let's just go into eye space and let's look at what happened:
Let a = inverse(P_old) * v be the eye space location of the point under the mouse cursor before we scaled.
Let b = inverse(P_new) * v be the eye space location of the pointer under the mouse cursor after we scaled.
Since we assumed a symmetrical view volume, we already know that for the x and y coordinates, b = (1/s) *a holds (assumption 7. if that assumption does not hold, you need to do the actual calculation for b too, which isn't hard either).
So, we can set up an 2D eye space offset vector d which describes how our point of interest was moved by the scale:
d = b - a = (1 / s) *a - a = a (1/s - 1)
To compensate for that movement, we have to move our camera inversely, so by -d.
If you keep the camera position separate as I did in assumption 1, you simply need to update the camera position c accordingly. You just have to take care about the fact that c is the world space postion, while d is an eye space offset:
c_new = c_old - inverse(V_old) * (d_x, d_y, 0, 0)^T
Not that if you do not keep the camera position as a separate variable, but keep the view matrix directly, you can simply pre-multiply the translation: V_new = translate(-d_x, -d_y, 0) * V_old
Update
What I wrote so far is correct, but I took a shortcut which is numerically a very bad idea when working with not-infinite precision data types. The error in camera position accumulates very fast if one zooms out a lot. So after #BPL implemted this, this it what he got:
The main issue seems to be that I directly calculated the offset vector d in eye space, which does not take the current view matrix V_old (and its small errors into account). So a more stable approach is to calculate all of this directly in world space:
a = inverse(P_old * V_old) * v
b = inverse(P_new * V_old) * v
d = b - a
c_new = c_old - d
(doing so makes assumption 7 not needed anymore as a by product, so it directly works in the general case of arbitrary ortho matrices).
Using this approach, the zoom operation worked as expected:

Finding a pixel location in a field of view

So I have a camera with a defined field of view a point of a place I would like to label in the image. I have both the lat and lon of the points and I know the angle between them however my equation for finding the pixel location is off. Attached is an image to help my explanation:
I can solve for each vector of the camera to the center of view and the point and the full angle of the field of view and the angle between the center of view and point.
Here is what Im currently using: [angle of the field of view(green angle)/ the angle between the vectors (the blue angle)] * 1024 (screen width)
With numbers: (14.182353/65) * 1024 = 223.426620 and on the image the pixel value should be 328...
Another way I tried it was using a bearing equation: [[the bearing of the point to the camera- the bearing of the left side of the field of view ]/field of view] * 1024
With numbers: ((97.014993-83.500000)/65) * 1024 = 212.913132 and the answer should be 328...
Can anyone think of a more accurate solution?
Try 512(1-tan(blue)/tan(green/2)), where blue is positive to the left.
If blue is to the right, you can treat it as a negative number, to get 512(1+tan(blue)/tan(green/2)).
Explanation:
Let C be the camera, d be the dot labeled 328, E be the center of the field of view, and L be the left end point of the field of view, so that you want to find dL. Then (for blue going left):
dL+dE = EL = 512
tan(green/2)=EL/CE
tan(blue)=dE/CE
Then tan(blue)/tan(green/2) = dE/EL = (512-dL)/512, and you can solve for dL.
Going right would be similar (or you can work with negative distances, and everything works out fine).

Creating seamless rotated background image

I want to repeat a background image that is rotated. Trying to make it seamless is destroying my soul.
Starting with something simple, consider each image is laid out like bricks. Creating a seamless repeating background image is pretty simple:
(the red area is the crop). You can see this working as expected at http://jsfiddle.net/mPqfB.
Now let's say I want to rotate the image by 45 degrees:
Unfortunately, the same crop no longer works, as you can see on http://jsfiddle.net/mPqfB/1.
I'm trying to figure out how to crop the image correctly so that we have a seamless repeat. There's probably some fairly trivial maths involved to do this but I can't for the life of me figure it out.
[Update]
I'm attempting to follow #oezi's calculations so to make things easier have created an image of dimensions: 100px x 50px.
Therefore:
Least Common Multiple = 100
Hypotenuse = 1002 + 1002 = 20000
Now I'm assuming this means we don't have to create an image of 20000px x 20000px. Am hoping that #oezi can clarify how he performs his resizing??
If this is a2 + b2 = c2 is equal to c = square root of (a2 + b2)
Then we can concur that our crop should be 141px?
Finally, this doesn't actually explain where we take the crop from?
[Update 2]
It does look like this is how the resize should be created. Taking a 141px x 141px crop of the image yielded the correct results - http://jsfiddle.net/EfuV2/
As far as where to crop from, it doesn't actually matter!
is the rotation is exactly 45 degrees, you'll have to find out the least common multiple of the width and height of your unrotated pattern.
in your case, that's 15100 (width 100 and height 151)
it would be much better to scale your pattern to width 100 and height 150, so the least common multiple is only 300
Take that number and some math (pythagorean theorem). Assume your number is the length of the two short arms and calculate the length of the hypotenuse - that's our result (make a square image of that size to get your pattern).
in your case, that's 21355
with resizing, it's ~ 424
Note that this is just typed straight from my head because i can't try it out practically at the moment - but i'm really sure it's correct.
edit: a fast (and messy) test got me to this:
http://i.imgur.com/rZuu9.jpg
http://jsfiddle.net/mPqfB/2/ (click the image-link first, otherwise jsfiddle doesn't show the image)
accidentally i made the pattern only be 423 in height and the rotation isn't perfect (don't have photoshop here), but it's good enough to prove that my math is correct.
The trick is to crop the pattern at points where the section being cut off matches the section remaining on the opposite side of the crop area (see example cuts in blue). It'll probably take some trial and error to get it right but you should be able to do it easily enough.

Math/Calculations for infinite/repeating world with rotation

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:
Top of visible rectangle hitting bounds circle inside a rotated square world:
Rotated square world moved to opposite vertical position so that bottom of visible rectangle now hitting bounds circle inside rotated world:
Another example of top of visible rectangle hitting bounds circle inside a rotated square world to illustrate a different scenario:
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
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:
.
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);
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.
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.

In OpenGL, How can I determine the bounds of the view at a given depth

I'm playing around with OpenGL and I've got a question that I haven't been able to find an answer to or at least haven't found the right way to ask search engines. I have a a pretty simple setup. An 800x600 viewport and a projection matrix with a 45 degree field of view and near and far planes of 1.0 and 200.0. For the sake of discussion, the modelview matrix is the identity matrix.
What I'm trying to determine is the bounds of the view at a given depth. For example, (0,0,0) is the center of the screen. And I'm looking in the -Z direction.
I want to know, if I draw geometry on a plane 100 units into the screen (0,0,-100), what are the bounds of the view? How far in the x and y direction can I draw in this plane and the geometry still be visible.
More generically, Given a plane parallel to the near and far plane (and between them), what are the visible bounds of that plane?
Also, if what I'm trying to determine has a common name or is a common operation, what's it called? That way I can track down more reading material
Your view angle is 45 degrees, you have a plane at a distance of a away from the camera, with an unkown height h. The whole thing looks like this:
Note that the angle here is half of your field of view.
Dusting off the highschool maths books, we get:
tan(angle) = h/a
Rearrange for h and subsitute the half field of view:
h = tan(FieldOfView / 2) * a;
This is how much your plane extends upwards along the Y axis.
Since screens aren't square, the width of your plane is different to the height. More exactly, the width is the aspect ratio times the height. I.e. w = h * aspectRatio
I hope this answers your question.

Resources