// 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 <d3d9.h>
#include <d3dx9.h>
#include <dxerr9.h>
// 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<T>*) ViewToWorld[i] = *(TVec3<T>*) g_ViewOrientation[i];
*(TVec3<T>*) ViewToWorld[3] = *(TVec3<T>*) 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<T> 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>( float ViewToWorld[4][3], float ViewVolume[7] );
#pragma endregion