Help needed - Zooming map issue...

Monkey Forums/Monkey Programming/Help needed - Zooming map issue...

therevills(Posted 2015) [#1]
Hi All,

I am trying to display a map that I can move around and zoom in. I've got most of it working, but when I zoom it is zooming based on the top left corner and I want it either where the mouse is or in the center of the screen.

Here is what I have so far:



Demo: https://dl.dropboxusercontent.com/u/35103024/mx/map/html5/MonkeyGame.html
Cursor keys to move the map around
Key W to Zoom In 
Key S to Zoom Out

Zip with Image and Source: https://dl.dropboxusercontent.com/u/35103024/mx/map/map.zip

I know I need to either offset the mapX/mapY variables or change them when zooming, but my brain cant work it out....

Cheers,
Steve


Pakz(Posted 2015) [#2]
Well you have the tilepos under the mouse by dividing by the tile width and height. Then you maybe can increment(jump to) by these offset from the top left position. I remember doing something like that more then ten years ago or so.


ImmutableOctet(SKNG)(Posted 2015) [#3]
For a full demo, read below.

Wait, why are you moving the map? You're supposed to be moving the "camera".

Okay, this just seems a bit... Well, backwards. You're moving the map, instead of giving the map its own coordinates, and moving the view around the map. See, your current setup kind of works with:


The thing is, that's only half right. You're already moving the map by half the screen initially, but that also screws up your containment code. Really, the idea's right, but the execution is wrong. You're not quite thinking of it as a projected world. For example, 'UpdateScale' shouldn't mess with 'MapMarker' objects, because they aren't relevant. They're a part of the world, and should have global positions. No matter the scale or position, they should stay in the same place. In this case, we use an offset, meaning they're "living on the map", but it's the same concept, only one level deeper. What you're doing is telling matrix operations to screw themselves, and maintaining the idea of viewpoint projection through 'game logic'.

Here's a fully working example. (GitHub; no resources)

The thing is, unless you go with a top-left origin, you will end up seeing the outside of the map as you zoom. Technically, you could still limit it, or scale the map more, but that's the gist of it.

---

EDIT 01: Just to confirm, the only way to have a relative origin for scaling is to apply an offset. So, the camera at the beginning needs to be "pushed over" by the initial amount, like in my version. For regular games, there's easy ways to get around this.

EDIT 02: Moved the demo to GitHub.


therevills(Posted 2015) [#4]
Thanks Guys :)

@ImmutableOctet, really appreciate you writing code and showing me that. I would like to do it without using the matrix/translate commands if possible.


ImmutableOctet(SKNG)(Posted 2015) [#5]
@therevills: Any particular reason? If it's about decoupling from Mojo, this might help (Also, this).


therevills(Posted 2015) [#6]
I would like to convert it to other engines in the future :)


Samah(Posted 2015) [#7]
Any real engine you use will support matrix transformations.


therevills(Posted 2015) [#8]
Any real engine you use will support matrix transformations.


That maybe... but I would like a solution without using them.


ImmutableOctet(SKNG)(Posted 2015) [#9]
@therevills: A solution without matrices is effectively hard-coding the exact code used by matrices. The only reasons not to use them are memory and performance related; both of which are the cost of doing this kind of thing to begin with. Though, ideally, we'd have stack-allocated matrices, but that's the problem Monkey 2 is solving. Even then, the down sides to matrices are few and far between, and made even more so when you factor in optimization and vectorization (SIMD).


therevills(Posted 2015) [#10]
I know what you are saying, but my requirement doesn't let me use the matrix functions. Pretend that MX doesn't have them ;)


Samah(Posted 2015) [#11]
What requirement is that? <_<


therevills(Posted 2015) [#12]
I thought you would have learnt by now, never question the client ;)

BTW I did a presentation on TMS yesterday... I called it "TMS 101" :P


Samah(Posted 2015) [#13]
Hahhahahahaha! *wipes tears for in-house joke*


therevills(Posted 2015) [#14]
I've conceded and changed my Monkey code to use ImmutableOctet(SKNG) suggestion....

But how do I now mouse over the markers when zooming / scrolling the map?

For scrolling with the zoom at 1.0:
mapMouseX = MouseX()
mapMouseX += cameraX
mapMouseX -= hDeviceWidth
		
mapMouseY = MouseY()
mapMouseY += cameraY
mapMouseY -= hDeviceHeight
		
For Local m:MapMarker = EachIn markerList
	Local markerX:Float = (m.x - m.size / 2)
	Local markerY:Float = (m.y - m.size / 2) 					
	
	If RectsOverlap(mapMouseX, mapMouseY, 3, 3, markerX, markerY, m.size, m.size)
		m.size = 20
	Else
		m.size = 10
	End
Next


But of course that doesnt work when zooming in/out... I've tried various places where to apply the zoom amount but cant work it out.





therevills(Posted 2015) [#15]
I think I've might have cracked it:
mapMouseX = MouseX()
mapMouseX /= zoom
mapMouseX += cameraX
mapMouseX -= hDeviceWidth / zoom
		 
mapMouseY = MouseY()
mapMouseY /= zoom		
mapMouseY += cameraY
mapMouseY -= hDeviceHeight / zoom


The translation regarding the hDeviceWidth/hDeviceHeight was messing me up...


Paul - Taiphoz(Posted 2015) [#16]
this zooming stuff <wink> <wink> diddy <wink> ................ just thought for second if this is easily implemented into an existing project it could let me zoom in and out of my current project but it needs to be able to fit with diddy and all my existing offsets and collisions.


therevills(Posted 2015) [#17]
all my existing offsets and collisions.

Actually no, not if you use the matrix code - your objects dont move only your viewport does. I needed to offset the mouse since thats outside of the matrix.


ImmutableOctet(SKNG)(Posted 2015) [#18]
The mouse problem actually is a bit more complex, though. The proper way to do it is to transform the mouse coordinates into "world-space". So, this is where you should consider a formal solution like my 'matrix2d' module. Any appropriate solution will do, but in my module, you should define 'MATRIX2D_VECTOR' as 'False', if you don't plan to use my other modules.

So, to illustrate this problem, think of a map that's zoomed in. Wherever you are on the world matters. Though it uses "real units", the screen doesn't properly represent the world. Why? Because we (The viewer) are a "camera". The top-left point of the map happens to be zero in this demo, and the image isn't mid-handled. Our perspective is offset by half of our viewport. With this in mind, the top-left position of the screen would instead theoretically be our center, and it would, no matter the context, represent a static area of the world. You might think that manually accounting for these issues with the mouse's position is the right answer, but that's a bit misguided.

Think of matrices like this: The current state of the matrix represents the origin on the final "display". With this in mind, this line makes sense. That's the local context of the map. Anything done with the matrix in that area is within the space of the map. That is, it's the origin of the map's "content", which is what we want to refer to when we perform a bounds check.

The latest version reflects the ideas I'm describing. But... It's kind of the wrong way to do it. What I'm doing is, instead of performing the operations on the 'mapMatrix' object ('Scale', 'Translate', 'Rotate', etc), I take the current matrix from the render loop. This is cheaper, but since it's not encapsulated, it's also a bit of a hack. Building a system around this is ideal, but since it's just for an isolated case, I think this does a good enough job.

I do think this should be understood conceptually, though. Because I'm using a sort of projection that relies on the "realness" of positions, the desired effect is to take an obscure "real" (Not actually, just what it'd be without this) coordinate described with the bounds of a screen, then apply it realistically to the world.

Since we want the real position in the world, we need to reverse the process. That is, the inverse of the methods used to display an element. So: Screen -> World - Instead of: World -> Screen. So, we're doing the opposite. How do we do this? Simple; invert the matrix. That is, divide the elements appropriately using the matrix's "determinant". Or, you know, this.

That method will take a screen coordinate, and reverse the effects of the view-matrix. In other words: Mouse -> World. Pretty awesome, if you ask me. This is why I love matrices, and why you should use them.

To try this out for yourself, grab this file, put it in the same folder, and run the demo. (With your asset(s) in "map.data") Select markers by clicking on them. Hold your "control" key to select multiple markers. The selected markers are green, and those that aren't are red.


therevills(Posted 2015) [#19]
Cool! Thanks for the explanation Anthony.


ImmutableOctet(SKNG)(Posted 2015) [#20]
No problem. I hope this helped.