Click here to Skip to main content
15,895,084 members
Articles / Multimedia / DirectX

A New Perspective on Viewing

Rate me:
Please Sign up or sign in to vote.
4.93/5 (44 votes)
6 Oct 2009CPOL16 min read 111.7K   2.6K   90  
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 <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

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

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.

License

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


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

Comments and Discussions