Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
69 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Introduction
 Getting Started
 Viewport Classes
 Taking Control
 Creating the
 Document


 Source and Demo
 Printable version
 Discuss this article
 in the forums


Creating the Viewport Classes

The viewport, as a vital piece of our application, is one place in particular that requires careful planning. Running in headfirst will most likely turn up a bunch of hacks and a lot of redundant code. I hate having to type things twice twice.

A good place to start is a base class that contains all the functionality required to get up and running in our desired API. The rest of our viewport classes will inherit from this class, making a port from one API to another simple work. The sample code deals with OpenGL, but the process should be similar with other APIs.

COpenGLWnd - Our base class with the basic OpenGL setup stuff. For more information on the specifics of creating an OpenGL view with MFC check the accompanying code or the tutorial found here. Note that this class doesn’t have a message map for WM_SIZE. That will be covered later.

CPerspective - The perspective viewport. To get an MFC class with all the right trimmings we start by creating a class with the ClassWizard with CView as the parent. Following this make sure you change ALL references to CView to COpenGLWnd. At this point we’re ready to start handling WM_SIZE messages with a message map:

// Adjust the viewport after a window sizing.
void CPerspective::OnSize(UINT nType, int cx, int cy)
{
  COpenGLWnd::OnSize(nType, cx, cy);

  if ( 0 >= cx || 0 >= cy || nType == SIZE_MINIMIZED )
    return;

  // Change the perspective viewing volume to
  // reflect the new dimensions of the window.
  SetContext();
  glViewport( 0, 0, cx, cy );
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  gluPerspective(45.0f, (float)(cx)/(float)(cy), 0.01f, 1000.0f);
  glMatrixMode( GL_MODELVIEW );
}

This process should look familiar to those that use OpenGL. We simple adjust the viewing volume with the new dimensions of the screen. At this point our viewport is just a boring static window, so let’s shake it up a bit. First we need to add member values to track the camera’s position and rotation:

float m_zoom,
      m_xpos,
      m_ypos,
      m_xrot,
      m_yrot;

Next we need to catch user actions to move the camera. We’ll create a process similar to camera manipulation in Maya and many other 3D packages. If the user holds down the Ctrl key and clicks and drags with the left mouse button the camera rotates, the middle mouse button zooms, and the right mouse button pans. We accomplish this by adding a message map for the WM_MOUSEMOVE message:

Where:

int  m_lastMouseX,
     m_lastMouseY;

Are class members of CPerspective:

void CPerspective::OnMouseMove(UINT nFlags, CPoint point) 
{
  // Move the camera if control is being
  // pressed and the apropriate mouse
  // button is being held down.

  if ( nFlags & MK_CONTROL )
  {
    if ( nFlags & MK_LBUTTON )
    {
      // Left mouse button is being
      // pressed. Rotate the camera.
      if ( m_lastMouseX != -1 )
      {
        m_yrot += point.y - m_lastMouseY;
        m_xrot += point.x - m_lastMouseX;
        // Redraw the viewport.
        OnDraw( NULL );
      }
      m_lastMouseX = point.x;
      m_lastMouseY = point.y;
    }

    // etc...

    else
    {
      m_lastMouseX = -1;
      m_lastMouseY = -1;
    }
  }
  else
  {
    m_lastMouseX = -1;
    m_lastMouseY = -1;
  }

  COpenGLWnd::OnMouseMove(nFlags, point);
}

Now mouse movements under the correct conditions alter the member variables representing our camera. The final step is to implement our camera variables in our WM_DRAW message map:

void CPerspective::OnDraw(CDC* pDC)
{
  SetContext();
  glLoadIdentity();

  // Position the camera
  glTranslatef( m_xpos, -m_ypos, -m_zoom );

  // Adjust viewport for md3 models which
  // use a different coordinate system -
  // 3DSMAX format.
  glRotatef( -90.0f, 1.0f, 0.0f, 0.0f );
  glRotatef( -90.0f, 0.0f, 0.0f, 1.0f );

  // Rotate the camera
  glRotatef( m_xrot, 0.0f, 0.0f, 1.0f );
  glRotatef( m_yrot, 0.0f, 1.0f, 0.0f );
  RenderScene();
  SwapGLBuffers();
}

Huzzah! Go back to MainFrm.cpp and plug your new class into one or more of the viewport panes and put some drawing code into CPerspective::RenderScene(). I used a cube with colored faces for testing purposes. It feels good to be brilliant doesn’t it?

COrthographic - COrthographic inherits from COpenGLWnd, create it in the same manner you created CPerspective. While CPerspective was a finished viewport (more or less) COrthographic is one step away from being a finished viewport, with CFront, CSide, and CTop inheriting from it. COrthographic differs from CPerspective in the fact that as an orthogonal viewport it cannot be rotated by the user, and all positioning and zooming can be done in the WM_SIZE message map because of the nature of orthographic viewports:

Where:

float m_zoom,
      m_xpos,
      m_ypos;
int   m_lastMouseX,
      m_lastMouseY;

Are class members of COrthographic:

void COrthographic::OnSize(UINT nType, int cx, int cy) 
{
  COpenGLWnd::OnSize(nType, cx, cy);

  if ( 0 >= cx || 0 >= cy || nType == SIZE_MINIMIZED )
    return;

  // Change the orthographic viewing volume to
  // reflect the new dimensions of the window
  // and the zoom and position of the viewport.
  SetContext();
  glViewport( 0, 0, cx, cy );
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  glOrtho( (float)(cx)/(float)(cy)*-m_zoom-m_xpos,
           (float)(cx)/(float)(cy)*m_zoom-m_xpos,
           -m_zoom+m_ypos, m_zoom+m_ypos, -200.0f, 200.0f );
  glMatrixMode( GL_MODELVIEW );
}

If you have trouble understanding the reasoning behind the orthographic sizing I suggest reading up on the difference between perspective and orthogonal views to get a clear idea of exactly what’s going on here. Note that COrthographic has no member variables for rotation. Orthographic views can be rotated, but in the context of 3D editor or modeler you don’t want them to be rotated by the user. The message map for WM_MOUSEMOVE is identical to the map for CPerspective except there is no check for the left mouse button (rotation) and OnSize is called just before OnDraw to apply the changes. Check the accompanying source for more information.

CFront, CSide, CTop - All inherited from COrthographic each class differs in its WM_DRAW message map with the appropriate amount of rotation for its respective viewpoint.

Double Huzzah! Go back to MainFrm.cpp and add in your orthographic viewports. Now that we have sleek sexy viewports covering most of the screen that little control panel is starting to look pretty weak. Let’s see what we can do...





Next : Taking Control