Can't properly scale down mouse coordinates to a smaller RenderTarget

I’ve been trying for ages now to properly map my mouse coordinates to my game world, which is smaller than the screen and stretched out using a RenderTarget2D. Here’s the relevant code if anyone wants to take a look: https://pastebin.com/YKpQb2pt
The relevant calculation is on line 39, with the matrix transformation.

The issue I’m having is the calculation is off by a margin, and that margin increases as the mouse moves from the upper-left of the window to the lower-right. If the window isn’t the same size as the RenderTarget for the game world, it’ll make boarders on either the left/right or top/bottom. If it’s left/right the mouse coordinates lag behind the actual mouse, and if it’s top/bottom they’re ahead. No matter what I try I can’t get it right.

Nobody’s been able to help me with this. I’m almost starting to think this is somehow impossible, but if that’s the case, how do I render my game in a larger window while being able to use a mouse? Is this really something that nobody in the history of XNA/MonoGame has ever done? Has every XNA/MonoGame game in existence been rendered in a window that’s exactly the same resolution as the game? Even tiny 600x800 games like mine that would be microscopic on modern 4K monitors? I don’t know what I’m doing wrong, and it would be miserable for me to have to give up my mouse or forcefully render my game in it’s native resolution.

Thanks for reading, I’ve asked this question around the internet about a dozen times over a few months but I hope someone has some sort of solution for me.

I didnt look at code but from experience (pos/2) - (viewport.xy/2)… or is it+…

Is there a reason you can’t use Viewport.Unproject? You’re missing the projection matrix at the least unless GetViewMatrix is returning the combined View-projection. (World * view) * projection.

I totally forgot about the Viewport, I never touched it outside of that Camera class that I didn’t write. I’ll try to modify it’s GetViewMatrix method to take into account the world, although I don’t know what I’m doing so that’ll be some trial and error.
Is there no documentation or tutorials around for this sort of thing? I mean, I sure couldn’t find any. How do other people do this?

If it’s 2D then it’s just:

  • Flip mouse Y so Y is Height - ReportedY, if necessary

  • Normalize EACH component of mouse position to actual window resolution

  • Denormalize against the render target resolution or world-coordinates per frame w/e

  • Add camera position (in same units from before) to denormalized position

      public static float Normalize(float val, float min, float max)
      {
           return (val - min) / (max - min);
       }
      public static float Denormalize(float val, float min, float max)
      {
          return val * (max - min) + min;
      }
    

If it’s 3d then you have no choice but to do it through the Viewport (or write your own method doing the same thing).

1 Like

Hello. I tried doing what you said, and using your normalize methods, and I got this

            Single xpos1 = Extra.Normalize(ms.X - r.X, 
                (Single)Globals.WindowSize.Width, 
                (Single)Globals.WindowSize.Height
            );
            Single ypos1 = Extra.Normalize(ms.Y - r.Y, 
                (Single)Globals.WindowSize.Width, 
                (Single)Globals.WindowSize.Height
            );
            Single xpos2 = Extra.Denormalize(xpos1, 
                (Single)Globals.RenderSize.Width, 
                (Single)Globals.RenderSize.Height
            );
            Single ypos2 = Extra.Denormalize(ypos1, 
                (Single)Globals.RenderSize.Width, 
                (Single)Globals.RenderSize.Height
            );
            pos = new Vector2(
                (xpos2 + (Globals.Camera.Position.X - (Single)(Globals.RenderSize.Width / 2))),
                (ypos2 + (Globals.Camera.Position.Y - (Single)(Globals.RenderSize.Height / 2)))
            );

It doesn’t seem to work, though. It also still has the issue of the real mouse moving further from the in-game mouse as it moves towards the bottom right.
I’m not sure what you mean by make the Y value Height?
I don’t think I’m properly interpreting your instructions. Is there any way you could write some pseudo-code for it?

Using the wrong values in normalization (I have no idea what “R” is either).

You normalize/denormalize X against width and Y against height. Your range is 0->width and 0->height.

Single xpos1 = Extra.Normalize(ms.X - r.X, 
    0.0f, 
    (Single)Globals.WindowSize.Width
);
Single ypos1 = Extra.Normalize(ms.Y - r.Y, 
    0.0f, 
    (Single)Globals.WindowSize.Height
);
Single xpos2 = Extra.Denormalize(xpos1, 
    0.0f, 
    (Single)Globals.RenderSize.Width
);
Single ypos2 = Extra.Denormalize(ypos1, 
    0.0f, 
    (Single)Globals.RenderSize.Height
);

I’m not sure what you mean by make the Y value Height?

Mouse coordinates are relative to the top left of the window with Y going down. Usually your world coordinates are the other way around with Y going up. That means to get a world relative Y you have to make the mouse position relative to the screen coordinate system (WindowHeight - MouseY). See the diagram here, part way down on the page - note that the diagram is for viewport coordinates.

If you wanted to get pure viewport coordinates you’d denormalize as Denormalize(x, -1, 1) and Denormalize(y, -1, 1), though depending on mouse basis (ie. flip Y or not, why may have to be Denormalize(y, 1, -1)).

1 Like

Oh, my goodness! This is actually working! Thank you so much, you saved my hide. I can’t tell you how long I spent trying to fix this myself.
It’s still slightly off, there’s a growing gap between the real cursor and in-game cursor, which is the size of the boarders filling the space in the window that the RenderTarget doesn’t cover. That’s what “R” is, it’s the RenderTarget size and the size of the boarder (X and Y are the boarder size, one is always 0 because the boarders are either up/down or left/right. It’s a Rectangle returned from the CalculateRenderTargetPosition() method). I’m not too concerned about it, I can fiddle with it some more later, try and find a place where to put the R values. I just wanted to thank you.

So you’re letterboxing the render target I assume?

That could be a bit wonky. I would guess you’d want to use the Rectangle’s Width and Height from CalculateRenderTargetPosition if that is the location of it in the window. But if you have other mouse requirements (such as you use the letter-box edge area or the letter-box edge area is just concealed area with valid world locations underneath - but only hidden, etc).

You’re probably off into specifics of your case land now.