Click here to Skip to main content
Click here to Skip to main content

Perfect Panning in 3D (Zoom Too)

By , 12 Oct 2009
 

Introduction

Everyone reading this article would be familiar with the ‘grab’ type of panning of 2D images, as with Adobe Acrobat®, where the mouse grabs the image and drags it around. The point under the cursor stays under the cursor. When it comes to 3D, a number of programs provide this intuitive interaction for parallel (orthographic) views, but I don’t know of any that implement it for perspective views. Some 3D programs do move objects around on a construction-type plane in 3D so the point on the plane stays under the mouse. The panning technique shown here is for more general use.

General Viewing Parameters

The demo program is derived from the demo program in A New Perspective on Viewing, which proposes a set of general viewing parameters. All interactive motion algorithms need to use viewing parameters to transform screen based mouse motion into virtual space motion. From the previous article, the seven general viewing parameters that specify the view volume in view space are:

struct TViewVolume { float hw, hh, zn, zf, iez, tsx, tsy; };
// These values are specified in view space
//    hw  - HalfWidth of cross section rectangle in z=0 plane
//    hh  - HalfHeight of cross section rectangle in z=0 plane
//    zn  - near clipping plane at z=zn
//    zf  - far clipping plane at z=zf
//    iez - inverse of the eyepoint's z-coordinate (0 for parallel views)
//    tsx - tan(SkewXAngle), typically 0
//    tsy - tan(SkewYAngle), typically 0
// SkewXAngle and SkewYAngle are the angles between the axis of the view volume
// and the view space z-axis.

A view-to-world rotate/translate transformation, which positions the view volume in world space, completes the view specification.

The Panning Algorithm

Parallel view panning is relatively simple, and needs to scale the mouse pixel motion to the virtual space. In perspective views, items closer to the eye-point pan faster across the screen than items further away. To keep the picked point under the cursor requires physical pixel motion to be mapped to the corresponding virtual plane passing through the picked point.

The z-buffer value is the picked point's screen space z-coordinate. A reverse transformation from screen (depth buffer) space to view space maps the screen space point to view space. The demo's source code shows how to read the depth buffer value in both OpenGL and Direct3D. The z-buffer value is provided as a value between 0.0 (front) and 1.0 (back). The derivation of the reverse transformation formulae are given as a comment in the OnPicked() method in Main.cpp.

// Transform the screen space z-coordinate to view space
float m33 = -(1-vv.zf*vv.iez) / (vv.zn-vv.zf);
ViewZ = (ScreenZ+m33*vv.zn) / (ScreenZ*vv.iez+m33);
MotionZ = ViewZ;    // save for repeated use

A pixel-to-view-rectangle scale factor is calculated and used to map the physical mouse 2D point to a virtual 2D point in the view space z=0 plane. The 2D point is then projected onto the z=MotionZ plane.

// Calculate the pixel-to-view-rectangle scale factor
RECT rect;
GetClientRect( hWnd, &rect );
float PixelToViewRectFactor = vv.hw * 2.0f / rect.right;

// Calculate the 2D picked point on the view space z=0 plane.
// Note: Screen +Y points down.
ViewX =   ScreenX * PixelToViewRectFactor - vv.hw;
ViewY = -(ScreenY * PixelToViewRectFactor - vv.hh);

// Now project the 2D point from the z=0 plane to the z=MotionZ plane
ViewX += -ViewX*MotionZ*vv.iez + vv.tsx*MotionZ;
ViewY += -ViewX*MotionZ*vv.iez + vv.tsy*MotionZ;
ViewZ  =  MotionZ;

rect.right is the window’s client area width in pixels. vv.hw is the HalfWidth general viewing parameter that specifies half the width of the rectangular cross section of the viewing volume at the z=0 view space plane. vv.hh is similarly the HalfHeight general viewing parameter.

Notice how the calculations handle both parallel (iez=0) and perspective views. These formulae are examples of how the general viewing parameters often don’t require code to differentiate between parallel and perspective views. See A New Perspective on Viewing for information on the general viewing parameters.

For each repetitive mouse move, the move-from and move-to 3D view space points are calculated then used to update the ViewToWorld translation.

// Calculate the view space pan vector, transform it to world space
// and subtract it from the ViewToWorld.trn
ViewToWorld.trn -= (MovedTo - MovedFrom) * ViewToWorld.rot;

ViewToWorld.trn is the translation portion of the view-to-world transformation, and is a point in world space. Likewise, ViewToWorld.rot is the 3x3 rotation portion. The delta view space translation is calculated by subtracting the vectors; then the view space delta is transformed to world space by multiplying by the ViewToWorld rotation. Finally, the world space delta is added to the ViewToWorld translation. Overloaded operators implement the vector subtraction and the vector times matrix operation in the last line.

The code that reads the z-buffer value is a little convoluted as the demo program redraws the screen then, after the redraw, the depth buffer is read and the value passed back using a callback. This technique is useful for quick response programs that clear the back buffer and z-buffer straight after a redraw to minimize the response time from reading the latest input values to showing the updated image.

Each mouse move event is converted into a delta mouse movement to allow other motion sources, such as animated movements, to work seamlessly with the mouse. The panning algorithm will permit other motion sources to move the point under the cursor away from the cursor, but the interaction still makes intuitive sense to the user.

Zoom

The zoom/spinZ interaction also keeps the picked point under the cursor. This type of interaction is similar to the 2D multi-touch screen interaction using two fingers where one finger is fixed at the center of the window. The view space origin's z-coordinate needs to be moved to match the picked point’s z-coordinate, which requires the view size to be adjusted. All the other calculations are similar to those for panning.

if (ISPERSPECTIVE(vv.iez) && ISPRESSED(GetKeyState( VK_SHIFT )))
{
    float ez = 1/vv.iez;
    HalfViewSize *= (ez-ViewZ)/ez;      // scale the z=0 view rectangle
    AVec3f Delta = CVec3f(0,0,ViewZ);   // the view space translation
    Delta = Delta * ViewToWorld.rot;    // now world space
    ViewToWorld.trn += Delta;           // move the origin
    ViewZ = 0;                          // move the picked point
    InvalidateRect( hWnd, NULL, FALSE );
}

The SpatialMath vector and matrix types and overloaded operators are used to simplify the code. See SpatialMath.h for more information.

Most users prefer zoom as a separate action to spin. For instance, a vertical mouse motion can be used to zoom the view in and out or scale an object up and down. The use of a view space z-coordinate is just as applicable to this type of zoom.

Conclusion

With the perfect panning and zooming motion algorithms appearing relatively simple, it is surprising that these algorithms aren’t already in widespread use. The simplicity derives from the use of the general viewing parameters, and it can prove difficult to implement these algorithms with other sets of viewing parameters.

Perfect panning in perspective views is an intuitive and subconscious way of adjusting the rate of virtual motion so the movement of the 2D image matches the movement of the mouse. Users will love the direct control provided by perfect panning.

History

  • October 12, 2009: Initial post.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

John Hilton
Founder Spatial Freedom
Australia Australia
Member
Software engineer, mechanical engineer, electronics engineer, inventor, manager, entrepreneur, husband, father, friend.
B.Sc. B.E.(Hons) M.Eng.Sc.
Some things I've done
- Invented the Spaceball(R)/1983 and Astroid(R)/2002 3D mice
- Patents: 3D mouse, data compression, acoustic transducer
- Wrote animation software in mid 1980s for TV commercials
- Wrote a basic CAD drawing program in 1980s
- Lived in Boston, Massachusetts for 11 years
- Architected and managed full custom ASIC chip
- Reviewed bionic eye technology for investment purposes
- Product development on CPR aid for heart attacks
- Developed an electronic sports whistle
- Was actually stranded on a deserted Pacific island
- Software: lots - embedded, device driver, applications
Some things I want to do
- Develop more cool hardware/software products
- Solve the 3D mouse software barrier to proliferate 3D mice
- Help bring 3D to the masses
- Help others

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionHow to implement Zoom All and Zoom window?memberTaiZhong1 Nov '11 - 3:50 
Zoom All and Zoom window are two important feature in 3D transformation, just wonder how to implement this with the code. Thanks!
AnswerRe: How to implement Zoom All and Zoom window?memberJohn Hilton2 Nov '11 - 20:58 
Zoom All is actually a fairly involved algorithm. Zoom window is more straightforward - see below.
 
One of my to-do items is to build up a motion toolkit based on the view volume parameters. With a standard set of viewing parameters all the various interactive motion algorithms can be written to avoid having to continually reinvent the wheel. Many interactive motion algorithms apply to all types of input devices; 2D and 3D mice, joysticks, game controllers, etc.
 
Briefly, the zoom window algorithm...
ZoomWindow( vv, v2w, vp, pt1, pt2 )
// vv - view volume... hw, hh, zn, zf, iez, tsx, tsy
// v2w - view-to-world rotate/translate
// vp - viewport in pixel coordinates... l,r,b,t
// pt1, pt2 - picked screen zoom window corners in pixel coordinates
     // determine left, right, bottom, top from pt1 and pt2
     l = min(pt1.x, pt2.x)
     r = max(pt1.x, pt2.x)
     b = min(pt1.y, pt2.y)
     t = max(pt1.y, pt2.y)
     // pan the center of the view rectangle to the center of the zoom window
     PixelToViewRectScale = vv.hw*2/(vp.r-vp.l)
     v2w.trn.x += (0.5*(l+r)-0.5*(vp.l+vp.r))*PixelToViewRectScale
     v2w.trn.y += (0.5*(b+t)-0.5*(vp.b+vp.t))*PixelToViewRectScale
     // scale the view rectangle and the eyepoint distance
     DeltaScale = (r-l) / (vp.r-vp.l)
     vp.hw *= DeltaScale
     vp.hh *= DeltaScale
     iez /= DeltaScale
 
The orthographic Zoom All involves determining the scenes view space bounding volume and adjusting the view volume accordingly. The perspective Zoom All involves determining the normal vectors (in view space) for the left, right bottom and top view volume planes then transforming the scene, or a bounding volume, into view space and dot-producting the view space points with the normal vectors to determine the signed distance along the unit normal directions. These distances are then scaled out slightly to keep the scene slightly off the window's edges then used to translate the view volume to snugly fit over these four points. The view volume origin and near and far clipping planes also need to be managed. There are other algorithms but this algorithm provides a good result.
 
Again, if you or anyone else develops the code it, or a link to the code, can be posted in this article.
GeneralRe: How to implement Zoom All and Zoom window?memberTaiZhong11 Nov '11 - 15:21 
Thanks for the reply. I will go and try.
QuestionIs there a c# version of the code?memberTaiZhong31 Oct '11 - 22:14 
Your code run perfectly and I think it would be useful if the c++ code can be ported into c# version.
AnswerRe: Is there a c# version of the code?memberJohn Hilton2 Nov '11 - 19:53 
You (or anyone else) is welcome to do the port and the code can either be added to the article as a revision or a link provided to a new article with the C# code.
QuestionQuestion about rotationmemberAllForum2 Sep '11 - 0:32 
Thanks for your code and your explanations. I do not all understand yet in particular the OnPicked function but your code works fine ! Smile | :)
As you are a specialist I would like to ask you a question because I do not find anything on the web.
Your camera enables to rotate around a model. But after rotations it is practically not possible to position exactly in front, back or top of the model.
If you look at Google SketchUp you will see that it is possible to rotate the model (it seems to not move as free as with your camera) but it is also possible to go back to a model really standing up and not like a Tower of Pisa...
The rotation is more usable in a CAD context.
Do you know how to do that (the principle)? Or what keywords to search on the net to find explanations ?
Is it possible to modify your camera to obtain a such result ?
 
Thanks in advance for your advices.
AnswerRe: Question about rotationmemberJohn Hilton2 Sep '11 - 2:55 

What you're asking for I call "Keep Upright". There don't seem to be any good sources I can point you to for motion algorithms like this since motion algorithms are fundamentally based on viewing parameters and there happen to be a myriad of viewing parameters in use in today's 3D applications. The best mathematical graphics text in my opinion was an English translation from a Hungarian mathematician but I just cannot remember the author or book details (wish I'd bought one).

A rotation matrix can be considered to be the right (+X), up(+Y) and back(+Z) unit vectors in the parent space of the right (1,0,0), up (0,1,0) and back (0,0,1) child space vectors. Consider

   (1,0,0)*WorldToView.rot=(rX,rY,rZ)

   (0,1,0)*WorldToView.rot=(uX,uY,uZ)

   (0,0,1)*WorldToView.rot=(bX,bY,bZ)

So WorldToView.rot is actually

   rX rY rZ

   uX uY uZ

   bX bY bZ

i.e. the world space's right, up and back vectors expressed in view space coordinates.

"Keep Upright" is an algorithm that spins the transformation matrix with the smallest rotation to place the world space's up vector in the view space's Y-Z plane where uX is zero.

double& uX = WorldToView.rot.m33[1][0];    // use a reference
if (abs(uX) < VERY_SMALL)    // done
   return;
if (abs(uX) > 1.0-VERY_SMALL)
{
   // extreme case - almost +-90 degrees so set identity
   WorldToView.rot = Identity.rot;
   return;
}
double Angle=asin(uX);    // imagine a triangle from (0,0,0) to (uX,uY,uZ) to (0,uY,uZ)
WorldSpaceSpinMatrix = RotArbAxis( Angle, CrossProduct( Vector(uX,0,0), WorldToView.rot.m33[1] ));
WorldToView.rot = WorldSpaceSpinMatrix * WorldToView.rot;

You'll have to flesh out the coding details but hopefully the algorithm is clear enough. The cross product may also need to be reversed. RotArbAxis is a rotation about an arbitrary axis given an angle and an axis.

Good luck.

John Hilton

GeneralRe: Question about rotationmemberAllForum16 Sep '11 - 2:30 
I would like to give you some news.
First, thanks for your quick answer.
I worked a lot since your post because I have not the prerequisites to all well understand.
I added your code in the ConfigureView_OpenGL function to modify the ModelView matrix.
It was not working. I did many tries. I finally found that I have not to store the modified matrix in the ViewToWorld matrix : I have only to give the modified WorldToViewRotate (a locale variable) to opengl.
I don't know if it is correct but in this case it is possible to go back to the initial position after rotations.
On the other hand the movements are no more easy to understand : when the object is not aligned with the axes (after one rotation around Y (up) and one around X (right)) moving the mouse horizontaly no more rotates the object around the up vector ; here is a screenshot : http://imageshack.us/f/233/rotation1.png (clik on the image to see a bigger resolution)
 
Do you think that I used your code correctly ?
 
Here is what I would like to do : http://imageshack.us/f/196/rotation2.png
Do you understand which rotations axis are used ?
Do you think that modifying your code (the SpinXY function for example) is possible to obtain a such result ?
 
I am trying to modify your rotation because I think that your code is very interesting. So I would like to use it...
 
Thanks
QuestionCan it run with OpenGL not DirectX?memberwaterharbin9 Aug '11 - 17:06 
Hello,Mr.Hilton.How can I complie it without DirectX.I only use OpenGL.But I get lots of Errors when I complie it.
AnswerRe: Can it run with OpenGL not DirectX?memberJohn Hilton11 Aug '11 - 1:45 
To use only OpenGL you will need to comment out m_GfxWinDirect3D and any other Direct3D references including the #includes. In Main.cpp eliminate the splitter window. Leave out GfxDirect3D.cpp from the build and it's likely to compile ok with only the one OpenGL window.
 
Good luck.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 12 Oct 2009
Article Copyright 2009 by John Hilton
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid