Click here to Skip to main content
15,885,546 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 109.6K   2.6K   90  
Simple yet comprehensive viewing code for OpenGL and Direct3D.
// Main.cpp
//
// High level code for the NewView demo
//
// Copyright (C) 2009 John Hilton
//
//      CMainImpl : IGfx<T>::ICallbacks
//          m_GfxWinOpenGL      CGfxWin<CGfxOpenGLf>   : CGfxOpenGLf, CWindowImpl
//          m_GfxWinDirect3D    CGfxWin<CGfxDirect3Df> : CGfxDirect3Df, CWindowImpl
//      CGfxOpenGLf   : IGfx<float>
//      CGfxDirect3Df : IGfx<float>
//      CGfxOpenGLImpl<T>   : CGfxSubclass<T>
//      CGfxDirect3DImpl<T> : CGfxSubclass<T>
//      CGfxSubclass<T> : CWindowImpl

#include "stdafx.h"
#include "Main.h"
#include "Gfx.h"
#include "Interact.h"
#define _USE_MATH_DEFINES
#include "math.h"

#pragma region SpatialMath
template< typename T >
union TVec3
{
    T v3[3];
    struct { T x, y, z; };
};

template< typename T >
union TRotTrn
{
    T v12[12];
    T m4x3[4][3];
    struct {
        T m3x3[3][3];
        union { T v3[3]; struct { T x, y, z; }; };
    };
};

template< typename T >
TVec3<T> operator/( TVec3<T>& vec, TRotTrn<T>& RotTrn )
{
    // Translate vec by -trn
    TVec3<T> vA = { vec.x-RotTrn.x, vec.y-RotTrn.y, vec.z-RotTrn.z };

    // Rotate vA by Inverse(rot)
    TVec3<T> vB;
    for (int i=0; i<3; i++)
        vB.v3[i] = vA.x * RotTrn.m3x3[i][0]
                 + vA.y * RotTrn.m3x3[i][1]
                 + vA.z * RotTrn.m3x3[i][2];
    return vB;
}
typedef TVec3<float> TVec3f;
typedef TRotTrn<float> TRotTrnf;

#pragma endregion

static DWORD Cube[] = {
    // Front, back, bottom, top, left and right faces
    //GFXCOLOR(1.0,0.5,0.9), GFXTRIANGLESTRIP(2), GFXV(0,0,1), GFXV(0,1,1), GFXV(1,0,1), GFXV(1,1,1),
    GFXCOLOR(0.5,0.9,0.8), GFXTRIANGLESTRIP(2), GFXV(0,0,0), GFXV(1,0,0), GFXV(0,1,0), GFXV(1,1,0),
    GFXCOLOR(0.7,0.4,0.2), GFXTRIANGLESTRIP(2), GFXV(0,0,0), GFXV(0,0,1), GFXV(1,0,0), GFXV(1,0,1),
    GFXCOLOR(0.3,0.8,0.5), GFXTRIANGLESTRIP(2), GFXV(0,1,0), GFXV(1,1,0), GFXV(0,1,1), GFXV(1,1,1),
    GFXCOLOR(1.0,1.0,1.0), GFXTRIANGLESTRIP(2), GFXV(0,0,0), GFXV(0,1,0), GFXV(0,0,1), GFXV(0,1,1),
    GFXCOLOR(0.9,0.2,0.4), GFXTRIANGLESTRIP(2), GFXV(1,0,0), GFXV(1,0,1), GFXV(1,1,0), GFXV(1,1,1)
};
static int Cubelength = sizeof(Cube)/sizeof(*Cube);

struct IGfxWinCallbacks {
    virtual void OnLbuttonDown( HWND hWnd, UINT nFlags, POINTS Points, IGfx<float>& gfx ) = 0;
    virtual void OnMouseMove( HWND hWnd, UINT nFlags, POINTS Points, IGfx<float>& gfx ) = 0;
};

// CGfxWin - combines CWindowImpl<> with an IGfx derived object
template< typename TGfx >
class CGfxWin : public TGfx, public CWindowImpl<CGfxWin<TGfx>>
{
public:
    IGfxWinCallbacks* m_pGfxWinCallbacks;

    CGfxWin( typename TGfx::ICallbacks& GfxCallbacks, typename IGfxWinCallbacks& GfxWinCallbacks )
        : TGfx(GfxCallbacks)
        , m_pGfxWinCallbacks(&GfxWinCallbacks)
    {
    }
    void Create( HWND hWndParent, RECT& rect )
    {
        CWindowImpl<CGfxWin>::Create( hWndParent, rect );
        TGfx::Attach(m_hWnd);
    }
    BEGIN_MSG_MAP(CGfxWin)
        MESSAGE_HANDLER( WM_MOUSEMOVE, OnMouseMove );
        MESSAGE_HANDLER( WM_ERASEBKGND, OnEraseBkgnd );
        MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLbuttonDown );
        MESSAGE_HANDLER( WM_LBUTTONUP, OnLbuttonUp );
    END_MSG_MAP()

    LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        uMsg; wParam; lParam; bHandled;
        // do nothing - don't draw the background
        return 0L;
    }
    LRESULT OnLbuttonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        uMsg; bHandled;

        UINT nFlags = (UINT) wParam;
        POINTS MousePos = MAKEPOINTS(lParam);
        m_pGfxWinCallbacks->OnLbuttonDown( m_hWnd, nFlags, MousePos, *this );
        SetCapture();
        return 0L;
    }
    LRESULT OnLbuttonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        uMsg; wParam; lParam; bHandled;

        ReleaseCapture();
        return 0L;
    }
    LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        uMsg; bHandled;

        UINT nFlags = (UINT) wParam;
        // Ignore the mouse move unless the left mouse button is down
        if (~nFlags & MK_LBUTTON)
            return 0L;
        POINTS MousePos = MAKEPOINTS(lParam);
        m_pGfxWinCallbacks->OnMouseMove( m_hWnd, nFlags, MousePos, *this );
        return 0L;
    }
};

class CMainImpl
    : public IGfx<float>::ICallbacks
    , public IGfxWinCallbacks
    , public CWindowImpl<CMainImpl> // intercept WM_SIZE
    , public CInteract
{
public:
    CGfxWin<CGfxOpenGLf>    m_GfxWinOpenGL;
    CGfxWin<CGfxDirect3Df>  m_GfxWinDirect3D;
    float m_ViewAngle;  // radians
    float m_HalfViewSize;
    float m_ViewVolume[7];
    float m_ViewToWorld[4][3];

    BEGIN_MSG_MAP(CMainImpl)
        MESSAGE_HANDLER( WM_SIZE, OnSize );
        MESSAGE_HANDLER( WM_CHAR, OnChar );
    END_MSG_MAP()

    CMainImpl( HWND hWnd )    // constructor
        : CInteract()
        , m_GfxWinOpenGL( *this, *this )
        , m_GfxWinDirect3D( *this, *this )
    {
        ResetView();

        VERIFY(SubclassWindow( hWnd ));

        // Get the client rectangle
        RECT rectClient;
        GetClientRect( &rectClient );

        // Split it vertically in two
        LONG width = rectClient.right;
        LONG height = rectClient.bottom;
        LONG mid = width / 2;

        // Create the OpenGL window and attach it to the CGfxOpenGL object
        RECT rectOpenGL = { 0, 0, mid, height };
        m_GfxWinOpenGL.Create( hWnd, rectOpenGL );

        // Create the Direct3D window and attach it to the CGfxDirect3D object
        RECT rectDirect3D = { mid, 0, width, height };
        m_GfxWinDirect3D.Create( hWnd, rectDirect3D );
    }

    ~CMainImpl()
    {
        if (m_hWnd) UnsubclassWindow();
    }

    LRESULT OnSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        wParam; nMsg; bHandled; lParam;

        //int cx = LOWORD(lParam);
        int cy = HIWORD(lParam);
        if (!m_hWnd || !cy) return 0L;

        // Get the client rectangle
        RECT rectClient;
        GetClientRect( &rectClient );

        // Split it vertically in two
        LONG width = rectClient.right;
        LONG height = rectClient.bottom;
        LONG mid = width / 2;

        // Create the OpenGL window and attach it to the CGfxOpenGL object
        RECT rectOpenGL = { 0, 0, mid, height };
        m_GfxWinOpenGL.MoveWindow( &rectOpenGL );

        // Create the Direct3D window and attach it to the CGfxDirect3D object
        RECT rectDirect3D = { mid, 0, width, height };
        m_GfxWinDirect3D.MoveWindow( &rectDirect3D );

        bHandled = FALSE;   // pass on to default handler
        return 0L;
    }

    LRESULT OnChar(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        nMsg; lParam; bHandled;

        switch (wParam)
        {
        case ' ': ResetView(); break;
        case 'P':
        case 'p': TogglePerspective(); break;
        }
        return 0L;
    }
    
    void ResetView()
    {
        m_ViewAngle = 45 * (float)M_PI/180;
        m_HalfViewSize = 10;
        static const float identity[4][3] = {{1,0,0},{0,1,0},{0,0,1},{0,0,0}};
        struct Tv12 { float v12[12]; };
        *(Tv12*) m_ViewToWorld = *(Tv12*) identity;
        if (IsWindow())
            Invalidate(FALSE);
    }

    void TogglePerspective()
    {
        if (m_ViewAngle)
            m_ViewAngle = 0;
        else
            m_ViewAngle = 45 * (float)M_PI/180;
        Invalidate(FALSE);
    }

    virtual void OnPaint( IGfx<float>& gfx )
    {
        float ViewVolume[7];
        CalcViewVolume( ViewVolume, gfx );

        gfx.ConfigureView( ViewVolume, m_ViewToWorld );
        gfx.Push();
        gfx.Translate( -4, 0, 0 );
        gfx.Scale( 4 );
        gfx.Translate( -0.5, -0.5, -0.5 );
        gfx.Render( Cube, Cubelength );
        gfx.Pop();
        gfx.Translate( 4, 0, 0 );
        gfx.Scale( 4 );
        gfx.Translate( -0.5, -0.5, -0.5 );
        gfx.Render( Cube, Cubelength );
    }

    void CalcViewVolume( float ViewVolume[7], IGfx<float>& gfx )
    {
        TViewVolumef& vv = *(TViewVolumef*) ViewVolume;
        vv.hw = vv.hh = m_HalfViewSize;
        vv.zn = 10;
        vv.zf = -10;
        vv.iez = tan(0.5f*m_ViewAngle)/m_HalfViewSize;
        vv.tsx = vv.tsy = 0;

        // Match the aspect ratio by extending hw or hh
        // Get the client rectangle
        RECT rectClient;
        if (&gfx == &m_GfxWinOpenGL)
            m_GfxWinOpenGL.GetClientRect( &rectClient );
        else
            m_GfxWinDirect3D.GetClientRect( &rectClient );

        float width = (float) rectClient.right;
        float height = (float) rectClient.bottom;
        // Scale the width or height up to match the aspect ratio
        if (width >= height)
            vv.hw *= width / height;
        else
            vv.hh *= height / width;
    }

    virtual void IGfx<float>::ICallbacks::OnFinalMessage( HWND hWnd )
    {
        hWnd;
        // do nothing
    }
    virtual void IGfxWinCallbacks::OnLbuttonDown( HWND hWnd, UINT nFlags, POINTS MousePos, IGfx<float>& gfx )
    {
        hWnd; nFlags; gfx;
        CInteract::OnLbuttonDown( MousePos );
    }
    virtual void IGfxWinCallbacks::OnMouseMove( HWND hWnd, UINT nFlags, POINTS MousePos, IGfx<float>& gfx )
    {
        TViewVolumef ViewVolume;
        CalcViewVolume( ViewVolume.v7, gfx );
        BOOL Redraw = CInteract::OnMouseMove( hWnd, nFlags, MousePos, ViewVolume.v7, m_ViewToWorld );
        m_HalfViewSize = min( ViewVolume.hw, ViewVolume.hh );
        if (Redraw)
            InvalidateRect( NULL, FALSE );
    }
};

CMain::CMain( HWND hWnd )
{
    m_pImpl = new CMainImpl( hWnd );
    ASSERT(m_pImpl);
    MessageBox( NULL,
        _T("The left window is rendered with OpenGL and the right with Direct3D.\r\n")
        _T("Use the left mouse button (LMB) to pan, LMB+SHIFT to zoom/spinZ and\r\n")
        _T("LMB+CTRL to spinXY.\r\n")
        _T("Press SPACE to reset the view and P to toggle perspective.\r\n"), _T("NewView"), MB_OK );
}

CMain::~CMain()
{
    if (m_pImpl) delete m_pImpl;
}

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