12,885,784 members (43,397 online)

#### Stats

71.5K views
89 bookmarked
Posted 5 Oct 2009

# A New Perspective on Viewing

, 6 Oct 2009 CPOL
Simple yet comprehensive viewing code for OpenGL and Direct3D.
 ```// View.cpp // // Code to use the general viewing parameters to configure OpenGL and Direct3D. // // Copyright (C) 2009 John Hilton // // For an explanation see the "A New Perspective on Viewing" at www.codeproject.com. // #include "stdafx.h" // Direct3D includes #include #include #include // OpenGL includes #include "gl/gl.h" #include "gl/glu.h" #include "GeneralView.h" // Provide an inverse rotate/translate transformation void Vec3MultInverseRotTrn( float vout[3], float v3[3], float RotTrn[4][3] ) { float tmp[3] = { v3[0]-RotTrn[3][0], v3[0]-RotTrn[3][1], v3[0]-RotTrn[3][2] }; for (int i=0; i<3; i++) vout[i] = tmp[0] * RotTrn[i][0] + tmp[1] * RotTrn[i][1] + tmp[2] * RotTrn[i][2]; } void ConfigureView_OpenGL( float ViewVolume[7], float ViewToWorld[4][3] ) { // Set up the GL_PROJECTION and GL_MODELVIEW matrices which transform // from world space to clip space. Parallel and perspective views are // handled along with left or right handed spaces. // ViewToWorld must only be a rotation and translation. // WorldToClip = ModelView * Projection // = Inverse(ViewToWorld) * ViewToClipGL(ViewVolume) // ViewToClipGL = [ 1/hw 0 0 0 ] // [ 0 1/hh 0 0 ] // [ -tsx/hw -tsy/hh -(2-(zn+zf)*iez)/(zn-zf) -iez ] // [ 0 0 (zn+zf-2*zn*zf*iez)/(zn-zf) 1 ] // Abbreviations stand for // HalfWidth, HalfHeight, ZNear, ZFar // InverseEyeZ, TanSkewX, TanSkewY // Note: OpenGL's perspective view space has a different origin // to ViewToWorld's and ViewToClip's view space origin. // Use references for code readability struct TViewVolume { FLOAT hw, hh, zn, zf, iez, tsx, tsy; }; TViewVolume& vv = *(TViewVolume*) ViewVolume; float &hw = vv.hw , &hh = vv.hh , &zn = vv.zn , &zf = vv.zf; float &iez = vv.iez, &tsx = vv.tsx, &tsy = vv.tsy; glMatrixMode( GL_PROJECTION ); static const float kVerySmall = 1e-6f; if (abs(iez) < kVerySmall) { // Orthographic view // Create a matrix with the 4th column [0, 0, 0, 1] GLfloat Ortho[4][4] = { { 1/hw, 0, 0, 0 }, { 0, 1/hh, 0, 0 }, { -tsx/hw, -tsy/hh, -2/(zn-zf), 0 }, { 0, 0, (zn+zf)/(zn-zf), 1 } }; glLoadMatrixf( Ortho[0] ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); } else { // Perspective view // Create a perspective matrix with the 4th column [0, 0, -1, 0] // which has the origin at the eyepoint. float ez = 1/iez; // The eyepoint's view space Z coordinate GLfloat Persp[4][4] = { { ez/hw, 0, 0, 0 }, { 0, ez/hh, 0, 0 }, { -ez*tsx/hw, -ez*tsy/hh, -(2*ez-(zn+zf))/(zn-zf), -1 }, { 0, 0, -2*(ez*(ez-(zn+zf))+zn*zf)/(zn-zf), 0 } }; glLoadMatrixf( Persp[0] ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glTranslatef( -ez*tsx, -ez*tsy, -ez ); } // Do the inverse of the ViewToWorld rotation and translation // Transpose the rotation to invert it GLfloat WorldToViewRotate[4][4] = { { ViewToWorld[0][0], ViewToWorld[1][0], ViewToWorld[2][0], 0 }, { ViewToWorld[0][1], ViewToWorld[1][1], ViewToWorld[2][1], 0 }, { ViewToWorld[0][2], ViewToWorld[1][2], ViewToWorld[2][2], 0 }, { 0, 0, 0, 1 } }; glMultMatrixf( WorldToViewRotate[0] ); glTranslatef( -ViewToWorld[3][0], -ViewToWorld[3][1], -ViewToWorld[3][2] ); } void ConfigureView_Direct3D( FLOAT ViewVolume[7], FLOAT ViewToWorld[4][3], D3DMATRIX& Projection, D3DMATRIX& World ) { // Set up the Projection and World matrices which transform from // world space to clip space. Parallel and perspective views are // handled along with left or right handed spaces. // ViewToWorld must only be a rotation and translation. // Direct3D's View transform is the identity transform. // WorldToClip = World * View * Projection // = Inverse(ViewToWorld) * WorldToClipD3D(WorldVolume) // WorldToClipD3D = [ 1/hw 0 0 0 ] // [ 0 1/hh 0 0 ] // [ -tsx/hw -tsy/hh -(1-zf*iez)/(zn-zf) -iez ] // [ 0 0 zn*(1-zf*iez)/(zn-zf) 1 ] // Abbreviations stand for // HalfWidth, HalfHeight, ZNear, ZFar // InverseEyeZ, TanSkewX, TanSkewY // Note: Direct3D's perspective view space has a different origin // to ViewToWorld's and WorldToClip's view space origin. // Use references for code readability struct TViewVolume { FLOAT hw, hh, zn, zf, iez, tsx, tsy; }; TViewVolume& vv = *(TViewVolume*) ViewVolume; float &hw = vv.hw , &hh = vv.hh , &zn = vv.zn , &zf = vv.zf; float &iez = vv.iez, &tsx = vv.tsx, &tsy = vv.tsy; // Set World = Inverse(ViewToWorld) // = XFRM(-ViewToWorld.trn) * XFRM(Transpose(ViewToWorld.rot)) // = [ 1 0 0 0 ] * [ rot00 rot10 rot20 0 ] // [ 0 1 0 0 ] [ rot01 rot11 rot21 0 ] // [ 0 0 1 0 ] [ rot02 rot12 rot22 0 ] // [ -trn.x -trn.y -trn.z 1 ] [ 0 0 0 1 ] // where trn is the ViewToWorld.m[3][0] to m[3][2] vector and rot is the // ViewToWorld.m[0][0] to m[2][2] matrix. for (int i=0; i<3; i++) { // Transform -ViewToWorld.trn from world space to view space World.m[3][i] = -ViewToWorld[3][0] * ViewToWorld[i][0] - ViewToWorld[3][1] * ViewToWorld[i][1] - ViewToWorld[3][2] * ViewToWorld[i][2]; // Invert ViewToWorld.rot World.m[0][i] = ViewToWorld[i][0]; World.m[1][i] = ViewToWorld[i][1]; World.m[2][i] = ViewToWorld[i][2]; // Zero m[0][3] to m[2][3] World.m[i][3] = 0; } World.m[3][3] = 1; static const float kVerySmall = 1e-6f; if (abs(iez) < kVerySmall) { // Orthographic view // Create a matrix with the 4th column [0, 0, 0, 1] D3DXMATRIX Ortho( 1/hw, 0, 0, 0, 0, 1/hh, 0, 0, -tsx/hw, -tsy/hh, -1/(zn-zf), 0, 0, 0, zn/(zn-zf), 1 ); Projection = Ortho; } else { // Perspective view // Create a perspective matrix with the 4th column [0, 0, -1, 0] // which has the origin at the eyepoint. FLOAT ez = 1/iez; // The eyepoint's view space Z coordinate D3DXMATRIX Persp( ez/hw, 0, 0, 0, 0, ez/hh, 0, 0, -ez*tsx/hw, -ez*tsy/hh, -(ez-zf)/(zn-zf), -1, 0, 0, -(ez-zn)*(ez-zf)/(zn-zf), 0 ); Projection = Persp; // When tsx=tsy=0 this is equivalent to... //D3DXMATRIX Persp2; //D3DXMatrixPerspectiveFovRH( &Persp2, 2*atan2(hh,ez), hw/hh, ez-zn, ez-zf ); // Add the translation from the eyepoint to the view space origin World.m[3][0] += -ez * tsx; World.m[3][1] += -ez * tsy; World.m[3][2] += -ez; } } #pragma region // Useful user interface variables // Declare a 3D vector type template< typename T > union TVec3 { T v3[3]; struct { T x, y, z; }; }; // The following variables are the interface between the interactive user // interface and the viewing code. float g_ViewOrientation[3][3]; // orients the view in world space about // ViewCenter float g_ViewCenter[3]; // a world space point mapped to the center // of the 2D image float g_ViewSize; // minimum width or height of the view's // cross section at ViewCenter float g_ViewAngle; // either // 0 - a parallel view // 45*M_PI/180 - a perspective view float g_BoundingSphere[4]; // world space x, y, z and radius, bounds the // world space region of renderable geometry int g_ViewportWidth, g_ViewportHeight; // in pixels template< typename T > void ConfigureView( T ViewToWorld[4][3], T ViewVolume[7] ) { // Create the ViewToWorld transformation by combining g_ViewOrientation // and g_ViewCenter. for (int i=0; i<3; i++) *(TVec3*) ViewToWorld[i] = *(TVec3*) g_ViewOrientation[i]; *(TVec3*) ViewToWorld[3] = *(TVec3*) g_ViewCenter; ViewToWorld[3][0] = ViewToWorld[3][1] = ViewToWorld[3][2] = 0; // Declare TViewVolume // HalfWidth, HalfHeight, ZNear, ZFar, InverseEyeZ, TanSkewX, TanSkewY union TViewVolume { T v7[7]; struct { T hw, hh, zn, zf, iez, tsx, tsy; }; }; // Use a reference to ViewVolume[] TViewVolume& vv = *(TViewVolume*) ViewVolume; vv.tsx = vv.tsy = 0; // not using skewed views vv.hw = vv.hh = 0.5f * g_ViewSize; // Increase the wider dimension if (g_ViewportWidth >= g_ViewportHeight) vv.hw *= (float) g_ViewportWidth / g_ViewportHeight; else vv.hh *= (float) g_ViewportHeight / g_ViewportWidth; // Map the bounding sphere from world space to view space TVec3 SphereCenterView; Vec3MultInverseRotTrn( SphereCenterView.v3, g_BoundingSphere, ViewToWorld ); float& radius = g_BoundingSphere[3]; vv.zn = SphereCenterView.x + radius; vv.zf = SphereCenterView.x - radius; static const float kVerySmall = 1e-6f; if (g_ViewAngle < kVerySmall) vv.iez = 0; // parallel view else { // Perspective view // Ensure the near clipping plane is not too close to the eyepoint // and the ratio between the near and far clipping plane distances // provides adequate resolution for the z-buffer. float EyeZ = min(vv.hw,vv.hh) / tan(0.5f*g_ViewAngle); static const float kMinNearDistance = 1e-4f; if (vv.zn > EyeZ-kMinNearDistance) vv.zn = EyeZ-kMinNearDistance; static const float kMinNearFarFactor = 5e-4f; if (vv.zn > (EyeZ-vv.zf)*kMinNearFarFactor) vv.zn = (EyeZ-vv.zf)*kMinNearFarFactor; vv.iez = 1 / EyeZ; } } template void ConfigureView( float ViewToWorld[4][3], float ViewVolume[7] ); #pragma endregion ```

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

## Share

 Founder Spatial Freedom Australia
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