Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I am writing a 2D game where my game world has x axis running left to right, y axis running top to bottom, and z axis out of the screen:

enter image description here

Whilst my game world is top-down, the game is rendered on a slight tilt:

enter image description here

I'm working on projecting from world space to screen space, and vice-versa. I have the former working as follows:

var viewport = new Viewport(0, 0, this.ScreenWidth, this.ScreenHeight);
var screenPoint = viewport.Project(worldPoint.NegateY(), this.ProjectionMatrix, this.ViewMatrix, this.WorldMatrix);

The NegateY() extension method does exactly what it sounds like, since XNA's y axis runs bottom to top instead of top to bottom. The screenshot above shows this all working. Basically, I have a bunch of points in 3D space that I then render in screen space. I can modify camera properties in real time and see it animate to the new position. Obviously my actual game will use sprites rather than points and the camera position will be fixed, but I'm just trying to get all the math in place before getting to that.

Now, I am trying to convert back the other way. That is, given an x and y point in screen space above, determine the corresponding point in world space. So if I point the cursor at, say, the bottom-left of the green trapezoid, I want to get a world space reading of (0, 480). The z coordinate is irrelevant. Or, rather, the z coordinate will always be zero when mapping back to world space. Essentially, I want to implement this method signature:

public Vector2 ScreenPointToWorld(Vector2 point)

I've tried several things to get this working but am just having no luck. My latest thinking is that I need to call Viewport.Unproject twice with differing near/far z values, calculate the resultant Ray, normalize it, then calculate the intersection of the Ray with a Plane that basically represents ground-level of my world. However, I got stuck on the last step and wasn't sure whether I was over-complicating things.

Can anyone point me in the right direction on how to achieve this?

share|improve this question
add comment

1 Answer

up vote 4 down vote accepted

I think your idea is pretty much spot on! First calculate a ray for your cursor using both the near plane and the far plane as Z values for your 2D coordinates (i.e. use 0 and 1 for your Z coordinate). Here's an helper method to handle that:

public Ray GetScreenRay(Vector2 screenPosition, Viewport viewport, Matrix projectionMatrix, Matrix viewMatrix, Matrix worldMatrix)
{
    Vector3 nearPoint = viewport.Unproject(new Vector3(screenPosition, 0f), projectionMatrix, viewMatrix, worldMatrix);
    Vector3 farPoint = viewport.Unproject(new Vector3(screenPosition, 1f), projectionMatrix, viewMatrix, worldMatrix);
    return new Ray(nearPoint, Vector3.Normalize(farPoint - nearPoint));
}

Next you'll need to have a Plane instance that matches your ground. The easiest way to calculate it is to use the constructor that takes three points on the plane, and passing it any three vertices from the ground object. Example of how to calculate the ground plane:

Plane groundPlane = new Plane(ground.Vertices[0], ground.Vertices[1], ground.Vertices[2]);

And finally find the intersection point between the ray and the plane using this method. You can use the out result parameter in order to calculate the intersection coordinates like in the example below:

float? result;
mouseRay.Intersects(ref groundPlane, out result);
if(result != null)
    Vector3 worldPoint = mouseRay.Position + mouseRay.Direction * result.Value;

You might have to do something about the NegateY method too but I'm not sure where.

share|improve this answer
    
This is super helpful - thank you very much. It was the last line I was missing. I couldn't figure out what to do with the float once I had it! I think I'm gonna have to have your name in my game credits :) –  me-- Mar 25 '12 at 11:16
    
@user13414 Indeed, the documentation is a bit scarce on that method and doesn't give any example of how to use the distance to get back the intersection point. –  David Gouveia Mar 25 '12 at 11:24
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.