I have a 2D game and I’m trying to get the world coordinated of my mouse.
I think I need to use Viewport.Unproject but I’m getting lost as to what the matrices are that I’m supposed to supply. I am translating my spritebatch with a matrix like so:
You can use the following function directly it returns a ray for your mouse coordinates in world space.
A ray of course has a position which should be usable for your purposes. As well as a direction the direction is a ray into the world from that point in the correspondingly correct direction into the world.
This works for any sort of matrix concation which is why all the matrices are passed as well.
/// <summary>
/// Near plane is typically just 0 in this function or some extremely small value. Far plane cant be more then one and so i just folded it automatically. In truth this isnt the near plane its the min clip but whatever.
/// </summary>
public Ray GetScreenVector2AsRayInto3dWorld(Vector2 screenPosition, Matrix projectionMatrix, Matrix viewMatrix, Matrix cameraWorld, float near, GraphicsDevice device)
{
//if (far > 1.0f) // this is actually a misnomer which caused me a headache this is supposed to be the max clip value not the far plane.
// throw new ArgumentException("Far Plane can't be more then 1f or this function will fail to work in many cases");
Vector3 nearScreenPoint = new Vector3(screenPosition.X, screenPosition.Y, near); // must be more then zero.
Vector3 nearWorldPoint = Unproject(nearScreenPoint, projectionMatrix, viewMatrix, Matrix.Identity, device);
Vector3 farScreenPoint = new Vector3(screenPosition.X, screenPosition.Y, 1f); // the projection matrice's far plane value.
Vector3 farWorldPoint = Unproject(farScreenPoint, projectionMatrix, viewMatrix, Matrix.Identity, device);
Vector3 worldRaysNormal = Vector3.Normalize((farWorldPoint + nearWorldPoint) - nearWorldPoint);
return new Ray(nearWorldPoint, worldRaysNormal);
}
/// <summary>
/// Note the source position internally expects a Vector3 with a z value.
/// That Z value can Not excced 1.0f or the function will error. I leave it as is for future advanced depth selection functionality which should be apparent.
/// </summary>
public Vector3 Unproject(Vector3 position, Matrix projection, Matrix view, Matrix world, GraphicsDevice gd)
{
if (position.Z > gd.Viewport.MaxDepth)
throw new Exception("Source Z must be less than MaxDepth ");
Matrix wvp = Matrix.Multiply(view, projection);
Matrix inv = Matrix.Invert(wvp);
Vector3 clipSpace = position;
clipSpace.X = (((position.X - gd.Viewport.X) / ((float)gd.Viewport.Width)) * 2f) - 1f;
clipSpace.Y = -((((position.Y - gd.Viewport.Y) / ((float)gd.Viewport.Height)) * 2f) - 1f);
clipSpace.Z = (position.Z - gd.Viewport.MinDepth) / (gd.Viewport.MaxDepth - gd.Viewport.MinDepth); // >> Oo <<
Vector3 invsrc = Vector3.Transform(clipSpace, inv);
float a = (((clipSpace.X * inv.M14) + (clipSpace.Y * inv.M24)) + (clipSpace.Z * inv.M34)) + inv.M44;
return invsrc / a;
}
I can only tell you i have tested the method i posted in 3d with a free camera and a billboarded object that changed shape when i put the mouse over it i tested it all directions and at distances as well as when objects were behind the camera.
I debugged out the problems i had with it and the notes to that still remain. The ray is basically both the position and the direction into the world and so it gives more information then just a position anyways.
This is a part of my utility class methods now.
You can look to the unproject function to see how it inverts which is actually right out of the matrix class i may have minorly altered it just to make it clearer when i was debugging a problem with my function.
Both of the methods you supplied take 3 matrices: Matrix projection, Matrix view, Matrix world. Which leads me back to my original question. Where do I get these matrices from?
It is somewhat odd that you would ask what these matrixs are and also need to have a world position for the mouse. Out of curiosity can i ask what this is for ?
Anyways to be as succinct as possible.
If you are only using spritebatch and passing it a BasicEffect basicEffect then these matrices are basicEffect.World
basicEffect.View
basicEffect.Projection;
If you are not passing a basic effect to spritebatch and using it as is then you don’t even need the above functions and can directly caluculate world coordinates by linear math.
Edit.
If you want to create your own basic Effect that matches spritebatch and then pass that to spriteBatch.Begin You can see the equivalent matrices and how to make them and pass them to spritebatch begin, in the below post were i put a full example that can be copy pasted.
This also includes a second example of how to make your own custom effect that matches spritebatch matrice setup.
Edit sorry that might not be the best example. If you look at set states it shows how to set up a basic effect though in the actual draw using spritebatch you add the basic effect to begin.
If you look at my image you will see that there is a character centred on the screen. The character is able to move around freely and the camera is set to follow her.
If you look to the right of her you will see a cross-hair. The player can use the mouse to move the cross-hair in a circle around the character. The red line is the vector.
I’m calculating the angle of the cross-hair as follows:
player.transform.Position - mouse.Position
Then I convert the vector to an angle which I then apply to the transform of the cross-hairs.
The problem is that the mouse position I’m getting is in screen coords and the player is in world coords.
I’d like to do this with a matrix transform because the camera can freely move, scale(zoom) and rotate, so I’d like accurate coords for any situation.
The matrix here looks incorrect, as the Z is translated and scaled; that would explain why M42 has a value like -60. Try this instead to guarantee nothing goes awry if the Z is modified:
Although it did mess up my sprite layerDepth. Am I correct in saying that the layerDepth must be a value between 0 and 1? When I set the scale.z to 1 all of my sprites disappeared. I found that I can either set the scale.z to a really low value (0.0000001f) or divide my layerDepth by a large value (2000000). Before implementing your fix my layerDepth was set to the sprites Y position. Is this the correct way to do this?
That’s correct; layerDepth must be between 0 and 1 if you’re using a FrontToBack or BackToFront sorting mode. If the depth is out of this range, a sprite may not render. A sprite’s Y position will be outside of that range fairly quickly, so for the depth, divide the Y by a value you don’t think it’ll ever go over.
The method above gives me inaccurate results (the ray direction seems a bit offset). I came up with the following code using the Unproject method of the GraphicsDevice.Viewport and the result is perfect. I also added the groundplane-ray-intersection stuff.