Constrained Panning/Zooming using a ViewMatrix
I'm trying to add constraining boundaries to the Zooming and Panning behaviors I've implemented for Unveil.js (http://github.com/michael/unveil). With boundaries enabled you shouldn't be able to pan outside the scene boundaries and the zoomlevel (=scale) shouldn't ever become 开发者_如何学Golower than 1.0. As a result you shouldn't be able to pan at all if you're at zoomlevel 1.0.
You can see the un-constrained behavior at the stacks example: dejavis.org/stacks. Use the mousewheel to zoom. You're able to wipe the blocks out the screen or infinitely shrink them, which should be avoided.
The really hard problem is that zooming relative to mouse pointer also causes the viewport to move out of place. So checking the boundaries during panning is not enough. I'd have to find a smart way to zoom back to 100% when using the Mouswheel (without doing dirty jumps). Photoshop seems to have solved this problem when zooming a picture.
I've absolutely no idea how I should solve this. Very frustrating. :/
I'm using a Matrix to store the current View transformation which is manipulated repeatedly. Here's the implementation code for Zooming and Panning behaviors.
http://github.com/michael/unveil/blob/master/src/scene/behaviors.js
Thanks for any ideas. :)
Cheers,
Michael
My previous answer is a bit rough and I made some silly mistakes by being too informal. Let me try again :)
So you have a 3x3 matrix representing the view transformation that looks something like this (if no rotation is present):
[ sx 0 tx ]
viewMatrix = [ 0 sy tx ]
[ 0 0 1 ]
sx
and sy
represent the scaling factor. I.e. the zoom parameters.
tx
and ty
represent the translation. I.e. the panning parameters.
(Note: you can actually replace sx
and sy
with s
everywhere since sx = sy
in your case)
It's easy to demonstrate this by multiplying it out with a point p
in homogeneous coordinates.
[ sx 0 tx ] [ px ] [ sx * px + tx ]
[ 0 sy tx ] * [ py ] = [ sy * py + ty ]
[ 0 0 1 ] [ 1 ] [ 1 ]
Now if you're visualizing the viewport its center coordinates will be at [-tx -ty]T
.
Furthermore if the canvas size is (width, height)
then the size of the viewport will be (width/sx, height/sy)
(because the size of the viewport scales inversely against the scale of the drawn geometry).
_______________
| | ^
| . [-tx] | | height/sy
| [-ty] | |
|_______________| v
width/sx
<--------------->
When zooming in or out relative to an anchor point sx, sy, tx and ty will all change. (If you need help with this calculation, just add a comment and I'll help. I got a bit busy :-))
So now the steps are easy
- Apply your zoom in / zoom out code to the matrix (i.e. calculate sx,sy,tx,ty)
- Clamp the minimum zooming factor:
s = max(s, 1.0); sx = sy = s
If the viewport borders intersect the canvas boundaries, translate the viewport until it is completely in view. Probably something roughly like this:
tx = clamp(tx, width * (1/sx - 1)/2, width * (1 - 1/sx)/2)
ty = clamp(ty, height * (1/sy - 1)/2, height * (1 - 1/sy)/2)
(Just check my math at the end there. I did it in a hurry :P)
Why not reformulate the problem slightly (as a matter of simplicity)? Store the viewport as a rectangle and perform all zooming/panning operations by transforming that rectangle. When the rectangle crosses over a boundary then simply clamp it to that boundary.
Then simply calculate the zoom and panning parameters from the rectangle size and position.
Hope I understood the problem you were describing correctly.
(BAD) EDIT: When zooming out, perhaps you might not only want to clamp the rectangle though. If an edge of the rectangle hits a boundary you could move the "stable point" around which you're zooming (i.e. usually the mouse cursor coordinates) right onto the boundary. Then only the opposite edge will scale larger. If two edges intersect then the "stable point" will be in the corner and only the opposite corner will grow. This will keep the rectangle scaling at a consistent rate even when one or more edges are constrained to the boundaries.
EDIT: Actually the easiest is not to "clamp" the edges of the viewport, but simply translate it until the edges lie on the boundary. My previous edit is a bit silly :). This will also keep the aspect ratio fixed which I'm sure is what you intend. If opposite edges touch the boundaries then simply stop zooming.
精彩评论