Okay I solved the problem. Run this. I purpousefully left out three lines to help you understand the issue here:
First, I changed your code so that it prints the true mouse position at the top, and the virtual position below it.
Second, I removed the offset you were adding to the mouse position, because it was uneccessary.
Third, I cleaned up the code. :-)
So... When you look at this initally you might think there is still a problem, because the virtual coordinates say 0,0 when the mouse is in the upper left corner of the screen, and 799,499 when it's in the lower right. I did!
You may say, "But I want the virtual coordinates to be 0,0 when the mouse is at the top left corner of the VISIBLE region inside the letterbox!
But: SURPRISE! They ARE!
You're seeing the mouse drawn at the screen coordinates, because it's the hardware mouse. So it has free reign to travel the entire screen.
But if you were to draw the mouse at the virtual coordinates after the scaling, then when the real mouse is at the top left corner of the screen, the virtual mouse would be drawn at the top left corner of the visible region inside the letterboxing. The same goes for when the real mouse is in the lower right corner, the virtual mouse would be drawn in the correct spot.
Just add these lines after the drawtext commands in the above code, and everything will become clear!
So you see, what you need to do if you want to have a mouse in your app using this code is to eschew the hardware mouse cursor and simply draw your own using those virtual mouse coordinates scaled to the virtual screen size.
Ta da!
Global Mouse_X#, Mouse_Y# Global Mx#, My# Local OriginX%, OriginY% Local ScaleX#, ScaleY# Graphics 1024,768,32 ProjectionMatrix.SetLetterBox(800,500) ScaleX# = ProjectionMatrix.Width() / GraphicsWidth() ScaleY# = ProjectionMatrix.Height() / GraphicsHeight() OriginX = ProjectionMatrix.CenterX() - ProjectionMatrix.Width()/2 OriginY = ProjectionMatrix.CenterY() - ProjectionMatrix.Height()/2 SetOrigin OriginX, OriginY Repeat SetClsColor 0, 0, 63 Cls 'VirtualMouseX = (MouseX() - OriginX) * ProjX 'VirtualMouseY = (MouseY() - OriginY) * ProjY ' Transform mouse coordinates from screen space to virtual space via scaling. Mx# = MouseX() My# = MouseY() Mouse_X# = Mx#*ScaleX# Mouse_Y# = My#*ScaleY# DrawText Mx#, 16, 16*1 DrawText My#, 16, 16*2 DrawText Mouse_X#, 16, 16*4 DrawText Mouse_Y#, 16, 16*5 ProjectionMatrix.DrawLetterBox() Flip 1 Until KeyDown(KEY_ESCAPE) Or AppTerminate() ' --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' This type allows you to make games that can run at any resolution. ' --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Type ProjectionMatrix Global _Width% ' The size of the screen, in BlitzMax coordinates. Global _Height% ' Normally BlitzMax coordinates correspond 1:1 with pixels on the screen, but when you adjust the projection matrix, that relationship changes. Global _VirtualWidth% ' The size of the visible region in which gameplay takes place. The area inside the letterbox. Global _VirtualHeight% ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' This function sets the scale of the projection matrix. ' ' If you simply wish your game to stretch vertically and horizontally to match the current resolution, and fill the screen, call this function with your desired "virtual" resolution. ' Ie, if you want to build your game around an 800x600 resolution, then set Width and Height to 800,600. If the game is then run at 1920x1200, it will be squashed vertically. ' ' If you want letterboxing however, call InitLetterbox() and then DrawLetterBox() every frame just before you flip. ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function SetScale(Width%, Height%) _Width = Width _Height = Height _VirtualWidth = Width _VirtualHeight = Height ?Win32 Local D3D7Driver:TD3D7Max2DDriver = TD3D7Max2DDriver(_max2dDriver) If D3D7Driver Local Matrix#[] = [2.0/Width, 0.0, 0.0, 0.0,.. 0.0, -2.0/Height, 0.0, 0.0,.. 0.0, 0.0, 1.0, 0.0,.. -1-(1.0/Width), 1+(1.0/Height), 1.0, 1.0] D3D7Driver.device.SetTransform(D3DTS_PROJECTION, Matrix) Else ? ' If on platform other than Win32, or using OpenGL run this code. glMatrixMode(GL_PROJECTION) glLoadIdentity() glortho(0, Width, Height, 0, 0, 1) glMatrixMode(GL_MODELVIEW) glLoadIdentity() ?Win32 EndIf ? End Function ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' This function initializes the letterbox. ' ' Call it with the virtual resolution you want to use for your game after you have set the graphics mode, and then DrawLetterBox() every frame just before you flip. ' ' These functions will automatically handle both letterboxing on screens which are too tall, and pillarboxing on screens which are too wide. So if you design your game for 800x600, ' then InitLetterbox() will squash things horizontally to maintain the proper aspect ratio, and DrawLetterbox() will add black bars to the sides of the display for you. ' ' ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function SetLetterbox(Width%, Height%) Local NewWidth#, NewHeight# NewWidth# = Width NewHeight# = Float(GraphicsHeight()) / (Float(GraphicsWidth()) / Float(Width)) ' If screen is wider than the desired apsect ratio... If NewHeight# < Height ' Use pillarboxing instead of letterboxing. NewHeight# = Height NewWidth# = Float(GraphicsWidth()) / (Float(GraphicsHeight())/Float(Height)) EndIf ' Adjust the scale of the projection matrix to achieve the desired result. ProjectionMatrix.SetScale(NewWidth#, NewHeight#) ' Store the size of the visible game region. _VirtualWidth = Width _VirtualHeight = Height End Function ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' This function draws black bars over the portions of the screen which are outside the gameplay area. ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function DrawLetterbox() Local Size% RenderState.Push() If _VirtualHeight < _Height ' Draw Letterbox. Size = (_Height-_VirtualHeight) / 2 SetColor(0,0,0) DrawRect(0, 0, _Width, Size) DrawRect(0, _Height-Size, _Width, Size) Else ' Draw pillarbox. Size = (_Width-_VirtualWidth) / 2 SetColor(0,0,0) DrawRect( 0, 0, Size, _Height) DrawRect(_Width-Size, 0, Size, _Height) EndIf RenderState.Pop() ' Render push and pop simply resets the color and other display properties to whatever they were beforehand. These two functions are in the code archives. End Function ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' These functions return the virtual width and height of the usuable region of the screen, inside any letterboxing. ' ' Basically, these functions are equivalent to GraphicsWidth() and GraphicsHeight() when using a projection matrix where coordinates don't match up 1:1 to pixels ' and/or letterboxing is being used. ' ' These functions will not return useful values until either ProjectionMatrix.SetScale() is called, or ProjectonMatrix.SetLetterbox() is called! ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function Width#() Return _VirtualWidth End Function Function Height#() Return _VirtualHeight End Function ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' These functions tell you where the center of the screen is when using letterboxing. ' ' Note that they do not calculate this using the width and height of the visible area of the screen, but rather, the size of the whole screen including the letterboxed regions. ' This is important, because if you draw objects using the size of the visible region only, they will be higher on the screen than they should be, or more to the left. ' ' In my games, what I do is create a pivot called Origin, and place it in the center of the screen, and attach all my sprites to that, so the only time I need to worry about where ' the center of the screen really is is when I position that pivot initially. ' ' Then when I've done that, the top of the screen is at -ProjectionMatrix.Height#()/2, half the height I passed to SetLetterbox(), and so on. ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function CenterX#() Return _Width/2 End Function Function CenterY#() Return _Height/2 End Function End Type Type RenderState Global RenderStateList:TList = CreateList() Field Alpha# Field Blend Field ClsColor_R, ClsColor_G, ClsColor_B Field Color_R, Color_G, Color_B Field Handle_X#, Handle_Y# Field ImageFont:TImageFont Field LineWidth# Field MaskColor_R, MaskColor_G, MaskColor_B Field Origin_X#, Origin_Y# Field Rotation# Field Scale_X#, Scale_Y# Field Viewport_X, Viewport_Y, Viewport_Width, Viewport_Height ' ------------------------------------------------------------------------------------------------------------------------------------------------------- ' These methods allow you to save and restore the current render settings ' ' Each time you call the push method, the current state is placed on the stack. ' Each time you call the pop method, the last state placed on the stack is restored and removed from the stack. ' ------------------------------------------------------------------------------------------------------------------------------------------------------- Function Push() Local RS:RenderState = New RenderState RS.Alpha# = GetAlpha#() RS.Blend = GetBlend() GetClsColor(RS.ClsColor_R, RS.ClsColor_G, RS.ClsColor_B) GetColor(RS.Color_R, RS.Color_G, RS.Color_B) GetHandle(RS.Handle_X#, RS.Handle_Y#) RS.ImageFont = GetImageFont() RS.LineWidth# = GetLineWidth#() GetMaskColor(RS.MaskColor_R, RS.MaskColor_G, RS.MaskColor_B) GetOrigin(RS.Origin_X#, RS.Origin_Y#) ; SetOrigin 0,0 RS.Rotation# = GetRotation#() GetScale(RS.Scale_X#, RS.Scale_Y#) GetViewport(RS.Viewport_X, RS.Viewport_Y, RS.Viewport_Width, RS.Viewport_Height) RenderStateList.AddLast(RS) End Function Function Pop() Local RS:RenderState = RenderState(RenderStateList.RemoveLast()) SetAlpha(RS.Alpha#) SetBlend(RS.Blend) SetClsColor(RS.ClsColor_R, RS.ClsColor_G, RS.ClsColor_B) SetColor(RS.Color_R, RS.Color_G, RS.Color_B) SetHandle(RS.Handle_X#, RS.Handle_Y#) SetImageFont(RS.ImageFont) SetLineWidth(RS.LineWidth#) SetMaskColor(RS.MaskColor_R, RS.MaskColor_G, RS.MaskColor_B) SetOrigin(RS.Origin_X#, RS.Origin_Y#) SetRotation(RS.Rotation#) SetScale(RS.Scale_X#, RS.Scale_Y#) SetViewport(RS.Viewport_X, RS.Viewport_Y, RS.Viewport_Width, RS.Viewport_Height) End Function End Type
First, I changed your code so that it prints the true mouse position at the top, and the virtual position below it.
Second, I removed the offset you were adding to the mouse position, because it was uneccessary.
Third, I cleaned up the code. :-)
So... When you look at this initally you might think there is still a problem, because the virtual coordinates say 0,0 when the mouse is in the upper left corner of the screen, and 799,499 when it's in the lower right. I did!
You may say, "But I want the virtual coordinates to be 0,0 when the mouse is at the top left corner of the VISIBLE region inside the letterbox!
But: SURPRISE! They ARE!
You're seeing the mouse drawn at the screen coordinates, because it's the hardware mouse. So it has free reign to travel the entire screen.
But if you were to draw the mouse at the virtual coordinates after the scaling, then when the real mouse is at the top left corner of the screen, the virtual mouse would be drawn at the top left corner of the visible region inside the letterboxing. The same goes for when the real mouse is in the lower right corner, the virtual mouse would be drawn in the correct spot.
Just add these lines after the drawtext commands in the above code, and everything will become clear!
SetColor 255, 0, 0 DrawRect Mouse_X#, Mouse_Y#, 2, 2 SetColor 255, 255, 255
So you see, what you need to do if you want to have a mouse in your app using this code is to eschew the hardware mouse cursor and simply draw your own using those virtual mouse coordinates scaled to the virtual screen size.
Ta da!