views:

72

answers:

2

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 lower 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

A: 

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.

Rehno Lindeque
Thaanks :) Basically I'm doing it in a similar way. The viewport rectangle is the display object in my case.Most things are now working.- Zoom in (scale around the current mouseposition) always works because anchorpoint is always within the boundaries- Panning at a higher zoomlevel already works nowIt gets tricky when it comes to zooming out. However I had the following idea: Given a current valid view transformation matrix, what I actually want is to step-wise approximate the identity matrix (= the original untransformed viewport). I think I can do that with linear combinations.
Michael
But then how do you keep the anchorpoint stable when you're zooming out? Anyway, if your matrix doesn't involve any rotation then interpolating to the identity is very easy. If it does have a rotational component then interpolating to the identity is a little weird... In any case the upper 2x2 block of the matrix will hold scale and the 3rd column or row (depending on your matrix library) will hold translation which can be linearly interpolated.
Rehno Lindeque
(If the matrix contains a rotation then it can't be linearly interpolated by directly interpolating the components of the matrix... Well it can, but it wouldn't be a proper linear interpolation and the aspect ratio would change in-between. What is the reason for the view matrix?)
Rehno Lindeque
Actually, sorry perhaps I'm over-thinking the answer you're looking for. Like you say, the view matrix is just a representation of the viewport. You can do exactly the operations I described in my answer on the view matrix by simply remembering that the last column (or row) represents the translation and the diagonals represent scale (or in other words the boundaries of the viewport as a distance from the center of the viewport). Simply translate the viewport into view when it overlaps a boundary and clamp the minimum scale to 1.0 and it will all work out magically. Simple :-)
Rehno Lindeque
Of course you're right. I didn't realize that having no stable point feels weird. Argh, for some reason I'm having serious troubles to understand this task. Maybe because I'm not too familiar with concepts used in 3D computer graphics. However I'd like to find a solution that is easy to understand. So I'd be fine with introducing a viewport rectangle to make things easier. Actually, I'm afraid I don't know how to imagine "transforming the viewing rectangle".Could you give me once again a step by step instruction what I'd have to do basically. Thanks a million! ;)
Michael
Can I imagine the viewing rectangle to lie on the scene, getting smaller when i zoom in and larger when I zoom out (covering the whole scene when at scale=1)? When talking about boundaries I had the bounds of the scene in mind. In other words the pixels shown in the display should never be outside scene range, pos (0,0) scene-height, scene-width.
Michael
Yes, exactly. The rectangle will just scale when you zoom. Let me add a new answer to make my meaning more clear. It's a bit hard to describe it in the comments and I think I've made one or two mistakes while describing it so informally.
Rehno Lindeque
A: 

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

  1. Apply your zoom in / zoom out code to the matrix (i.e. calculate sx,sy,tx,ty)
  2. Clamp the minimum zooming factor: s = max(s, 1.0); sx = sy = s
  3. 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)

Rehno Lindeque
Well that's a good lesson. :)I fully got step one and two. The idea of clamping the tx,ty values also makes fully sense. I'm just curious about why you are working with center coordinates rather than left (top) and right (bottom) boundaries and why they would be [-tx -ty]TSay we have a scene and display having a width of 500. Now let's look at the scene with sx = sy = 2 and tx=0, ty=0. So we would look at the top left quarter of the scene. In order to not violate the boundaries I'd clamp the value of tx between 0 and -500 (which corresponds to 0..250 in world coordinates). tbc..
Michael
If I evaluate your formula I'd get:clamp(tx, -125, 125) which I can't make use of?I'd use this formula for getting the bounds:var bound = { x: (1 - tView.sx) * display.width, y: (1 - tView.sy) * display.height};Which yields bound.x = -500 for sx = 2So are there any specific advantages of using the center point or should I go with left and right bounds. Anyways for understanding please explain me the center-point approach. :)Thanks again that was totally helpful! :)-- Michael
Michael
Wargh. That comments look awful. Read it here instead: http://pastie.org/1132551
Michael
Just committed a first working version. http://bit.ly/ch63ETDemos: http://quasipartikel.at/unveil/examples/random_bars/http://quasipartikel.at/unveil/examples/multiple_displays/
Michael
Hi Michael, I choose [-tx,-tx] to be the center of the viewport simply because I assumed [0,0] was the center of the geometry being drawn.I did this because I'm used to working with 3d graphics where it is more sensible to choose the center of the world as [0,0,0] (almost everybody does this) rather than choosing the bottom-left-front corner as [0,0,0]. To me this makes more sense.It doesn't really matter however, since the only differences was that in my case the drawing would have a bounding rectangle ([-125,-125], [125,125]) while in your case the bounding rectangle ([0,0],[500,500]).
Rehno Lindeque
Also, congrats on finishing your implementation :). The demos don't seem to work on my Firefox 3.6, I'll check them out on Firefox4.0 at home!
Rehno Lindeque
Ahh there was a little typo that made Firefox crash. http://github.com/michael/unveil/commit/b2874c753a68c4cde43ae5474ab0108955b00045 Should work now.Regarding the bounding rectangle. Clear. :) But why the intervals are ([-125, -125], [125,125]) and not ([-250, -250], [250, 250])?
Michael
Oh yes that's my mistake :) Should be 250.
Rehno Lindeque