Viewport.Unproject positions for a RenderTarget2D scene (mouse position to world)

Translating mouse position to world position and back is something I already have working. The problem is when I change the position and dimensions of my scene on my RenderTarget. So the world position that is returned is incorrect for the mouse position, since I can move the rectangle around.

The Orange is the clear screen behind the RenderTarget. So I draw the RenderTarget texture onto a rectangle using spriteBatch, but the game world is in 3D (not spriteBatch). If I have the corners of the rectangle against the edges of the screen, it works fine, but moving them around doesn’t translate well.

Camera view and projection:

View = Matrix.CreateLookAt(cameraPosition, cameraLookAt, Up);
Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45f),
    Graphics.GraphicsDevice.Viewport.AspectRatio, 0.05f, 10000.0f);

Unproject:

Vector3 nearSource = new Vector3(mousePos.X, mousePos.Y, 0f);
Vector3 farSource = new Vector3(mousePos.X, mousePos.Y, 1f);

Vector3 nearPoint = Graphics.GraphicsDevice.Viewport.Unproject(nearSource,
     Projection, View, Matrix.Identity);
 Vector3 farPoint = Graphics.GraphicsDevice.Viewport.Unproject(farSource,
     Projection, View, Matrix.Identity);

Draw RenderTarget

 SpriteBatch.Draw(Scene.Target, Scene.DestRect, Color.White);

How might I go about translating the mouse position correctly for this?

If I understood you correctly, then you want to have your mouse cursor position relative to your game world rectangle.

It seems to be possible to translate the game world rectangle so you have an offset in the coordinates. Try reflecting this offset in the mouse cursor vector like mousePos.X + gameWorldRec.X.

It seems to be possible to scale your game world rectangle too. You need to reflect that in the projection matrix. It should be something like this:

Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45f),
    Graphics.GraphicsDevice.Viewport.AspectRatio / gameWorldScale, 0.05f, 10000.0f);

Try tinkering around in that direction and it should give you what you want to achieve.

Sorry, I didn’t explain that well. With the mouse cursor, I can click on a tile in the game world. Using unproject I translate that mouse position into the game world, so the tile I click on matches up with where I clicked. The problem is when I move that rendertarget (I want to put an in-game editor that’ll move and scale the gameplay rectangle), the mouse position in the gameplay is then way off.

Yeah, I’m thinking I’ll need to offset it like you said, and do a scaling comparison between the resolution of the game window and the gamplay rectangle.

I think you need to do the unproject/mouse-pick twice.

The first time use the View,Projection and the Viewport you had when you draw the RT as a texture.
Create a ray from nearPointA to farPointA, and a Plane from the RT quad. Intersect the ray with the plane and this will give you where the mouse hit the RT texture.
Since you are drawing the RT with Spritebatch you have to recalculate the View/Proj that Spritebatch is using internally. See SpriteEffect.cs at github,
Proj: _projection,
View: Identity,
World: probably the scale you used in Spritebatch.Draw() goes here.

The Plane should probably be positioned at the position you used in Spritebatch.Draw() and pointing backward.

Now that you have where the mouse intersect the RT, do the second unproject/mouse. Use that position to create nearSourceB , farSourceB, the View/Proj from your 3D camera, and the Viewport you had during the rendering of the RT.
nearPlaneB is your RT, where your 3D world is projected on.

Since you draw the RT first, you need to save that viewport for later,
device.SetRendertarget(myRT);
var viewportB = device.Viewport;
// draw 3d on RT
device.SetRendertarget(null);
// draw RT on screen
or create it from your RT dimensions.
var viewportB = new Viewport(0, 0, rt.Width, rt.Height);

I figured out a pretty simple solution:

public Vector2 ConvertScreenPositionToScene(Vector2 screenPos)
    {
        Vector2 resolution = ScreenManager.Instance.GraphicsManager.GetResolution();
        Rectangle rect_scene = ScreenManager.Instance.GraphicsManager.Scene.DestRect;

        if (resolution.X <= 0 || resolution.Y <= 0 || 
            rect_scene.X == 0 || rect_scene.Y == 0)
        {
            return screenPos;
        }

        Vector2 resNormalize;
        resNormalize.X = rect_scene.Width / resolution.X;
        resNormalize.Y = rect_scene.Height / resolution.Y;

        Vector2 pos_scene = new Vector2(rect_scene.X, rect_scene.Y);
        pos_scene /= resNormalize;

        screenPos /= resNormalize;
        screenPos -= pos_scene;

        return screenPos;
    }

Thanks for the quick responses guys.

I had a similar problem in my current project (w/ one surface) and simply offsetted the ViewMatrix like this:

ViewMatrix = Matrix.CreateTranslation(new Vector3(offset.X, offset.Y), 0)) *
                Matrix.CreateLookAt(
                    new Vector3(SurfaceCenter, 1), 
                    new Vector3(SurfaceCenter, 0), 
                    Vector3.Up);

Theoretically you could do this with all your surfaces and then simply unproject by using the matrices of the corresponding surfaces. This should always give you the exact positions, no matter on which surface the mouse cursor is.

But it’s great that you solved your problem in a different way :slight_smile:

1 Like