Function graphics in 3D


Desktop-as-a-Service Designed for Any Cloud ? Nutanix Frame

Click here for larger image

Environment: VC6, NT4 SP3

This is a simple Windows application, which creates and reproduces 3D-image (surface) of a function plot using OpenGL rendering context. Graphics data may be arbitrary (specified by the user). Data format becomes obvious if you consider (look up the codes of) the function COGView::DefaultGraphic. The application makes it convenient to inspect the results of calculation of some field (for example: surfaces of equal temperature, pressure velocity or magnetic flux density in 3D space). For instance, The View-class may be incorporated in an application which deals with the PDE-solutions. By mouse manipulations you can control the graphics position and rotation. Modeless dialog allows to control some other OpenGL image attributes.

Start with the MFC AppWizard SDI template named OG. Place the following include-directives at the end of StdAfx.h file in order to ensure the visibility of OpenGL and STL library resources.

#include < afxdlgs.h >
#include < math.h >

#include < gl/gl.h >
#include < gl/glu.h >

#include < vector >
using namespace std;

Invent the data structure

We shall need a class incapsulating the functionality of a point in 3D space (let's call it CPoint3D). The container of its objects (vector) will be used as a storage of the surface vertices. Place the following class declaration at the beginning of OGView.h file. Simplify the starting code of the View-class interface deleting some members and insert the new members in it.
class CPoint3D
   float x, y, z;
   CPoint3D () { x=y=z=0; }
   CPoint3D (float c1, float c2, float c3)
      x = c1;      z = c2;      y = c3;
   CPoint3D& operator=(const CPoint3D& pt)
      x = pt.x;   z = pt.z;   y = pt.y;
      return *this;
   CPoint3D (const CPoint3D& pt)
      *this = pt;

class COGView : public CView
   //======== New data
   long   m_BkClr;    // Background color
   HGLRC   m_hRC;     // Rendering context OpenGL
   HDC   m_hdc;       // Windows device context
   GLfloat   m_AngleX;  // Rotation angle (around X-axis)
   GLfloat m_AngleY;    // Rotation angle (around Y-axis)
   GLfloat   m_AngleView;   // Perspective angle
   GLfloat   m_fRangeX;     // Graphics dimension (along X-axis)
   GLfloat m_fRangeY;   // Graphics dimension (along Y-axis)
   GLfloat m_fRangeZ;   // Graphics dimension (along Z-axis)
   GLfloat   m_dx;      // Displacement quantum (along X-axis)
   GLfloat m_dy;        // Displacement quantum (along Y-axis)
   GLfloat   m_xTrans;  // Displacement (along X-axis)
   GLfloat m_yTrans;    // Displacement (along Y-axis)
   GLfloat m_zTrans;    // Displacement (along Z-axis)
   GLenum   m_FillMode; // Polygon filling mode
   bool   m_bCaptured;  // Mouse capture flag
   bool   m_bRightButton; // Right mouse button flag
   bool   m_bQuad;  // Flag of using GL_QUAD (instead of GL_QUAD_STRIP)
   CPoint   m_pt;   // Current mouse position
   UINT   m_xSize;  // Current client window sixe (along X-axis)
   UINT   m_zSize;  // Current client window sixe (along -axis)
   vector < CPoint3D > m_cPoints; // Graphics dimension (along X-axis)
   int   m_LightParam[11];  // Graphics dimension (along X-axis)
   CPropDlg *m_pDlg;        // Graphics dimension (along X-axis)

   //======== Public methods
   COGDoc* GetDocument() 
     { return DYNAMIC_DOWNCAST(COGDoc,m_pDocument); }
   virtual ~COGView();

   //======== New methods
   void DrawScene();       // Prepare and store the image
   void DefaultGraphic();  // Create and save the default plot
   void ReadData();   // Data-file manipulations
   bool DoRead(HANDLE hFile);   // Data-file reading
   //===== Take the data from buffer and store in m_cPoints
   void SetGraphPoints(BYTE* buff, DWORD nSize);
   void SetLightParam (short lp, int nPos); // Set lighting parameters
   void GetLightParams(int *pPos);          // Get lighting parameters
   void SetLight();            // Set the lighting
   void SetBkColor();          // Set background color
   void LimitAngles();         // Limit rotation angles

   virtual void OnDraw(CDC* pDC);
   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

Add handlers

Using ClassWizard insert in COGView responses (in other words, add message handlers) to the following Windows messages: WM_CREATE, WM_DESTROY, WM_ERASEBKGND, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE and WM_TIMER. Change the View-class constructor to initialize controlled parameters.
   //====== Rendering context does not exist yet
   m_hRC = 0;

   //====== Initial image turn
   m_AngleX = 35.f;
   m_AngleY = 20.f;

   //====== Projection matrix view angle
   m_AngleView = 45.f;

   //====== Initial bkcolor
   m_BkClr = RGB(0, 0, 96);

   // Initial mode to fill the inner polygons (quads) points
   m_FillMode = GL_FILL;

   //====== Initial plot creation

   //====== Initial image shift
   //====== One and a half object size (backward)
   m_zTrans = -1.5f*m_fRangeX;
   m_xTrans = m_yTrans = 0.f;

   //== Initial shift quantums (for rotation animation)
   m_dx = m_dy = 0.f;

   //====== Mouse is not captured
   m_bCaptured = false;
   //====== Right mouse button has not been pressed
   m_bRightButton = false;
   //====== We use quads to create the surface
   m_bQuad = true;

   //====== Initial lighting params
   m_LightParam[0] = 50;   // X position
   m_LightParam[1] = 80;   // Y position
   m_LightParam[2] = 100;  // Z position
   m_LightParam[3] = 15;   // Ambient light
   m_LightParam[4] = 70;   // Diffuse light
   m_LightParam[5] = 100;  // Specular light
   m_LightParam[6] = 100;  // Ambient material
   m_LightParam[7] = 100;  // Diffuse material
   m_LightParam[8] = 40;   // Specular material
   m_LightParam[9] = 70;   // Shininess material
   m_LightParam[10] = 0;   // Emission material

Prepare the OpenGL context

You possibly know from the multiple articles (available here at CodeGuru or at Silicon Graphics www.SGI.com) that to prepare a window for drawing with OpenGL commands you should:
  • select and set the pixel format,
  • get and store Windows device context,
  • create and store OpenGL rendering context,
  • add the window styles WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
  • refuse background erasure before each redrawing (handle WM_ERASEBKGND and just return TRUE),
  • specifically handle the WM_SIZE, WM_PAINT messages and
  • release contexts when the view is closing.
If you frequently use OpenGL you had better remember all these steps and perform them as a kind of ritual. The main events in preparing a window for OpenGL usage take place in WM_CREATE handler.
int COGView::OnCreate(LPCREATESTRUCT lpCreateStruct)
   if (CView::OnCreate(lpCreateStruct) == -1)
      return -1;

   // Structure used to describe the format
      1,         // Version
      PFD_DRAW_TO_WINDOW |   // Supports GDI
      PFD_SUPPORT_OPENGL |   // Supports OpenGL
      PFD_DOUBLEBUFFER,   // Use double buffering 
                          //     (more efficient drawing)
      PFD_TYPE_RGBA,      // No pallettes
      24,          // Number of color planes
                   // in each color buffer
      24,   0,     // for Red-component
      24,   0,     // for Green-component
      24,   0,     // for Blue-component
      24,   0,     // for Alpha-component
      0,           // Number of planes
                   // of Accumulation buffer
      0,           // for Red-component
      0,           // for Green-component
      0,           // for Blue-component
      0,           // for Alpha-component
      32,          // Depth of Z-buffer
      0,           // Depth of Stencil-buffer
      0,           // Depth of Auxiliary-buffer
      0,           // Now is ignored
      0,           // Number of planes
      0,           // Now is ignored
      0,           // Color of transparent mask
      0            // Now is ignored

   //====== Get current Windows context
   m_hdc = ::GetDC(GetSafeHwnd());

   //====== Ask to find the nearest compatible pixel-format
   int iD = ChoosePixelFormat(m_hdc, &pfd);
   if ( !iD )
      return -1;

   //====== Try to set this format
   if ( !SetPixelFormat (m_hdc, iD, &pfd) )
      return -1;

   //====== Try to create the OpenGL rendering context
   if ( !(m_hRC = wglCreateContext (m_hdc)))
      return -1;

   //====== Try to put it in action
   if ( !wglMakeCurrent (m_hdc, m_hRC))
      return -1;

   //====== Now you can issue OpenGL commands 
   //       (i.e. call gl*** functions)
   glEnable(GL_LIGHTING);      // Lighting will be used
   //====== Only one (first) source of light
   //====== Depth of the Z-buffer will be taken into account
   //====== Material colors will be taken into account

   //====== Our function to set the background

   //====== Create and store the image in a special list 
   //       of OpenGL commands
   return 0;


Each time you (or Windows) decide to redraw the view, the same chain of actions is performed and it should be given in WM_PAINT handler. Considering the peculiarities of the (admired by Microsoft) Document-View architecture you place the redrawing code not in the OnPaint but in OnDraw function.

Here we erase back-buffer (image in memory) and depth-buffer, correct the modeling matrix (which transforms the image), call the stored list of drawing commands and after (re) drawing in the memory switch front and back buffers to quickly show the result on the screen.

void COGView:: OnDraw(CDC* pDC)
   //========== Clear the background and Z-buffer
   //========== Clean the modeling matrix 
   //           (make it equal the unity matrix)

   //======= At first set the light 
   //        (otherwise it will rotate with the image)

   //====== Change the modeling matrix coefficients in order
   glTranslatef(m_xTrans,m_yTrans,m_zTrans);  // to shift
   glRotatef (m_AngleX, 1.0f, 0.0f, 0.0f );   // and to rotate
   glRotatef (m_AngleY, 0.0f, 1.0f, 0.0f );

   //====== the following vertices coordinates 
   //       (they are being multiplied by matrix)

   //====== Switch back and front buffers 
   //          (to show what happened)

The lighting

If you want to know how OpenGL calculates the color of each pixel when it processes lighting equation, you should find the book "OpenGL Programming Guide (Addison-Wesley Publishing Company)". Try to do it at www.sgi.com. Here we simply use the technique gathered there.
void COGView::SetLight()
   //====== Both surface sides are considered when calculating
   //====== each pixel color with the lighting formula


   //====== Light source position depends on the 
   //       object sizes scaled to (0,100)

   float fPos[] =
   glLightfv(GL_LIGHT0, GL_POSITION, fPos);

   //====== Ambient light intensity
   float f = m_LightParam[3]/100.f;
   float fAmbient[4] = { f, f, f, 0.f };
   glLightfv(GL_LIGHT0, GL_AMBIENT, fAmbient);

   //====== Diffuse light intensity
   f = m_LightParam[4]/100.f;   
   float fDiffuse[4] = { f, f, f, 0.f };
   glLightfv(GL_LIGHT0, GL_DIFFUSE, fDiffuse);

   //====== Specular light intensity
   f = m_LightParam[5]/100.f;
   float fSpecular[4] = { f, f, f, 0.f };
   glLightfv(GL_LIGHT0, GL_SPECULAR, fSpecular);

   //====== Surface material reflection properties for 
   //       each light component
   f = m_LightParam[6]/100.f;
   float fAmbMat[4] = { f, f, f, 0.f };
   glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, fAmbMat);

   f = m_LightParam[7]/100.f;
   float fDifMat[4] = { f, f, f, 1.f };
   glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, fDifMat);

   f = m_LightParam[8]/100.f;
   float fSpecMat[4] = { f, f, f, 0.f };
   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fSpecMat);

   //====== Material shininess
   float fShine = 128 * m_LightParam[9]/100.f;
   glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, fShine);

   //====== Material light emission property
   f = m_LightParam[10]/100.f;
   float fEmission[4] = { f, f, f, 0.f };
   glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, fEmission);
Setting the background color is easy compared to other OpenGL intricacies.
void COGView::SetBkColor()
   //====== Split the color to 3 components
   GLclampf red   = GetRValue(m_BkClr)/255.f,
          green   = GetGValue(m_BkClr)/255.f,
           blue   = GetBValue(m_BkClr)/255.f;
   //====== Set the clear (background) color
   glClearColor (red, green, blue, 0.f);

   //====== Actual background erasure

Creating and storing the image

The main job on generating the image is done in DrawScene function that creates and stores the image consisting of graphics primitives vertices' coordinates. We assume that all these coordinates are already placed in STL-container m_cPoints. The image is constructed either as a series of curved quadrangles (GL_QUADS) or as a series of connected curved quads (GL_QUAD_STRIP). Later we shall add the command to toggle between the two modes. The surface points are actually over the regular coordinate grid in (X, Z) plane. The size of this grid is stored in m_xSize and m_zSize variables. In spite of the 2-dimensional character of the grid we use linear (one-dimensional) array m_cPoints or (to be exact) container of type vector for storing the coordinates' values. This is to save efforts when executing file operations.

The selection of 4 neighboring points of one primitive being generated (GL_QUADS for example) is done with the help of 4 indices (n, i, j, k). The index n goes in succession through all the vertices in order from left to right along the X-axis (Z=0) and then the procedure is repeated (gradually increasing the Z-value). Other indices are computed relative to n-index. Note that only two indices work in connected quads mode which is enabled or disabled by the m_bQuad flag.

void COGView::DrawScene()
   //====== Create the new list of OpenGL commands
   glNewList(1, GL_COMPILE);

   //====== Set the polygon filling mode
   glPolygonMode(GL_FRONT_AND_BACK, m_FillMode);

   //====== Image grid sizes
   UINT   nx = m_xSize-1,
      nz = m_zSize-1;

   //====== Turn on the primitive connection mode 
   //                             (not connected)
   if (m_bQuad)
      glBegin (GL_QUADS);

   for (UINT z=0, i=0;  z < nz;  z++, i++)
      //====== Turn on the primitive connection mode 
      //                                  (connected)
      //====== The strip of connected quads begins here
      if (!m_bQuad)

      for (UINT x=0;  x < nx;  x++, i++)
         // i, j, k, n  4 indices of a quad
         // Counter Clockwise direction

         int   j = i + m_xSize, // Other vertices indices
            k = j+1,
            n = i+1;

         //=== Get coordinates of 4 vertices
            xi = m_cPoints[i].x,
            yi = m_cPoints[i].y,
            zi = m_cPoints[i].z,

            xj = m_cPoints[j].x,
            yj = m_cPoints[j].y,
            zj = m_cPoints[j].z,

            xk = m_cPoints[k].x,
            yk = m_cPoints[k].y,
            zk = m_cPoints[k].z,

            xn = m_cPoints[n].x,
            yn = m_cPoints[n].y,
            zn = m_cPoints[n].z,

            //=== Quad side lines vectors coordinates
            ax = xi-xn,
            ay = yi-yn,

            by = yj-yi,
            bz = zj-zi,

            //====== Normal vector coordinates
            vx = ay*bz,
            vy = -bz*ax,
            vz = ax*by,

            //====== Normal vector length
            v  = float(sqrt(vx*vx + vy*vy + vz*vz));
         //====== Scale to unity
         vx /= v;
         vy /= v;
         vz /= v;

         //====== Set the normal vector
         glNormal3f (vx,vy,vz);

         //===== Not connected quads branch
         if (m_bQuad)
            //====== Vertices are given in counter clockwise 
            //       direction order
            glColor3f (0.2f, 0.8f, 1.f);
            glVertex3f (xi, yi, zi);
            glColor3f (0.6f, 0.7f, 1.f);
            glVertex3f (xj, yj, zj);
            glColor3f (0.7f, 0.9f, 1.f);
            glVertex3f (xk, yk, zk);
            glColor3f (0.7f, 0.8f, 1.f);
            glVertex3f (xn, yn, zn);
         //===== Connected quads branch
            glColor3f (0.9f, 0.9f, 1.0f);
            glVertex3f (xi, yi, zi);
            glColor3f (0.5f, 0.8f, 1.0f);
            glVertex3f (xj, yj, zj);
      //====== Close block of GL_QUAD_STRIP commands
      if (!m_bQuad)
      //====== Close block of GL_QUADS commands
   if (m_bQuad)

   //====== Close the list of OpenGL commands
Normal vectors should be correctly calculated and specified for each primitive. If you are lazy to do this job accurately you will never get the quality of lighting and (as a result) the quality of the whole image.

File manipulations

Container m_cPoints is filled when the data is read from a file. We want to have a default surface plot and for this reason we must create a file with some sample data and read it when the application is first opened. We are going to work with the binary file and save the information in our own fomat wich can be described verbally. First the grid dimensions (m_xSize and m_zSize) are placed, then the function y = f (x, z) values are successively placed in a data file. Before writing to a file all the data are placed in a temporary BYTE (i.e. unsigned char) buffer. The fact that variables of different types are placed in a binary file complicate a bit the code but simplifies actual reading and writing which are done with only one API-function call.
void COGView::DefaultGraphic()
   //====== Coordinate grid dimensions
   m_xSize = m_zSize = 33;

   //====== Number of meshes is less than number of nodes
   UINT   nz = m_zSize - 1,
      nx = m_xSize - 1;

   // File size in bytes
   DWORD nSize = 
          m_xSize * m_zSize * sizeof(float) + 2*sizeof(UINT);

   //====== Temporary buffer
   BYTE *buff = new BYTE[nSize+1];

   //====== Point at the start of it with UINT-type pointer
   UINT *p = (UINT*)buff;

   //====== Place the two UINTs
   *p++ = m_xSize;
   *p++ = m_zSize;

   //====== Change the pointer type to continue with the 
   //       floating numbers
   float *pf = (float*)p;

   //=== Default formula coefficients
   double   fi = atan(1.)*6,
      kx = fi/nx,
      kz = fi/nz;

   //====== For all the grid nodes calculate
   //=== calculate and place default function values 
   //                in the same buffer
   for (UINT i=0;  i < m_zSize;  i++)
      for (UINT j=0;  j < m_xSize;  j++)
         //====== Sample function
         *pf++ = 
             float (sin(kz*(i-nz/2.)) * sin(kx*(j-nx/2.)));

   //=== We want to know the real number of bytes written
   //    to the file
   DWORD nBytes;

   //=== Create and open the default data file (sin.dat)
   HANDLE hFile = CreateFile(_T("sin.dat"), 

   //====== Write the whole buffer
   WriteFile(hFile,(LPCVOID)buff, nSize,&nBytes, 0);

   //====== Close the file

   //====== Create and fill container m_cPoints 
   //                  (using the same buffer)
   SetGraphPoints (buff, nSize);

   //====== Free temporary buffer
   delete [] buff;

Reading the data

The next function ReadData creates the file dialog which enables the user to select a data file, reads the data and creates the image. The standard file dialog uses the OFN_EXPLORER style which works right only in Windows 2000.
void COGView::ReadData()
   //=== Here we place the file path 
   TCHAR szFile[MAX_PATH] = { 0 };

   //====== File extensions filter
   TCHAR *szFilter =TEXT("Graphics Data Files (*.dat)\0")
         TEXT("All Files\0")

   //====== Query the current folder
   TCHAR szCurDir[MAX_PATH];

   //=== Struct used by the standard file dialog

   //====== Dialog parameters
   ofn.lStructSize   = sizeof(OPENFILENAME);
   //====== Window which owns the dialog
   ofn.hwndOwner = GetSafeHwnd();
   ofn.lpstrFilter   = szFilter;
   //====== The filters string index (begins with 1)
   ofn.nFilterIndex   = 1;
   ofn.lpstrFile      = szFile;
   ofn.nMaxFile      = sizeof(szFile);
   //====== Dialog caption
   ofn.lpstrTitle   = _T("Find a data file");
   ofn.nMaxFileTitle = sizeof (ofn.lpstrTitle);
   //====== Dialog style (only in Win2K)
   ofn.Flags      = OFN_EXPLORER;

   //====== Create and open the dialog (retuns 0 on failure)
   if (GetOpenFileName(&ofn))
      // Try to open the file (which must exist)
      HANDLE hFile = CreateFile(ofn.lpstrFile, GENERIC_READ,
            FILE_ATTRIBUTE_NORMAL, 0);

      //=== On failure CreateFile returns -1
      if (hFile == (HANDLE)-1)
         MessageBox(_T("Could not open this file"));

      //====== Try to read the data
      if (!DoRead(hFile))

      //====== Create and store the image

      //====== Redraw the window client region
The TEXT macros sequence is equivalent to one string

TEXT("Graphics Data Files (*.dat)\0*.dat\0All Files\0*.*\0");

due to the fact that C++ compiler glues the literals, created by the TEXT macro. The zero-code symbols separate filter substrings. The actual reading is done in DoRead member function where we query the file size, allocate the needed amount of storage and read the whole file in a storage buffer. After that we set the graphic point container m_cPoints within the function SetGraphPoints.

bool COGView::DoRead(HANDLE hFile)
   //===== Query the file size
   DWORD nSize = GetFileSize (hFile, 0);

   if (nSize == 0xFFFFFFFF)
      MessageBox(_T("Could not get file size"));
      return false;

   //===== Try to allocate the core
   BYTE *buff = new BYTE[nSize+1];

   if (!buff)
      MessageBox(_T("The data file is too big"));
      return false;

   DWORD nBytes;
   //===== Read the whole file in the buffer
   ReadFile (hFile, buff, nSize, &nBytes, 0);

   if (nSize != nBytes)
      MessageBox(_T("Error while reading data file"));
      return false;

   //===== Set the vector of vertices coordinates
   SetGraphPoints (buff, nSize);

   delete [] buff;
   return true;

void COGView::SetGraphPoints(BYTE* buff, DWORD nSize)
   //===== Here again we use the technique of changing 
   //       the the pointer types
   UINT *p = (UINT*)buff;

   //==== First read the grid dimensions
   m_xSize = *p;
   m_zSize = *++p;

   //===== Check the file size (just in case)
   if (m_xSize<2 || m_zSize < 2 || 
       m_xSize*m_zSize*sizeof(float) + 2 * sizeof(UINT) != nSize)
      MessageBox(_T("Wrong data format"));

   //===== Resize the container

   if (m_cPoints.empty())
      MessageBox(_T("Can not allocate the data"));

   //====== Change the pointer type
   float   x, z,
      *pf   = (float*)++p,
      fMinY = *pf,
      fMaxY = *pf,
      right = (m_xSize-1)/2.f,
      left  = -right,
      rear  = (m_zSize-1)/2.f,
      front = -rear, 
      range = (right + rear)/2.f;

   UINT   i, j, n;

   m_fRangeY = range;
   m_fRangeX = float(m_xSize);
   m_fRangeZ = float(m_zSize);

   //===== How far should we place the whole image
   m_zTrans = -1.5f * m_fRangeZ;

   //===== Fill the container with 3D poits
   for (z=front, i=0, n=0;  i < m_zSize;  i++, z+=1.f)
      for (x=left, j=0;  j < m_xSize;  j++, x+=1.f, n++)
         MinMax (*pf, fMinY, fMaxY);
         m_cPoints[n] = CPoint3D(x,z,*pf++);

   //===== Scale the data so that any data would be seen "nicely"
   float zoom = fMaxY > fMinY ? range/(fMaxY-fMinY) : 1.f;

   for (n=0;  n < m_xSize*m_zSize;  n++)
      m_cPoints[n].y = zoom * (m_cPoints[n].y - fMinY) - range/2.f;

Mouse manipulations

To control the orientation and rotation speed of the image with the help of mouse buttons add the following message handlers. Left button controls two rotation angles and swtches on and off the automatic rotation mode. If the user (additionally) presses the Ctrl button then he might shift (translate) the image along two (X and Y) axes. Note that horizontal mouse movement should change the rotation angle around the vertical axis and vice versa. The right button is used to control the Z-axis translation. i.e. the distance to the image. To switch the constant rotation we use some sensativity treshold.
void COGView::LimitAngles()
   while (m_AngleX < -360.f)
      m_AngleX += 360.f;
   while (m_AngleX > 360.f)
      m_AngleX -= 360.f;
   while (m_AngleY < -360.f)
      m_AngleY += 360.f;
   while (m_AngleY > 360.f)
      m_AngleY -= 360.f;

void COGView::OnLButtonDown(UINT nFlags, CPoint point)
   //====== Stop rotation

   //====== Zero the quantums
   m_dx = 0.f;
   m_dy = 0.f;

   //====== Capture the mouse messages and direct them 
   //       in our window
   //====== Remember the fact
   m_bCaptured = true;
   //====== and where it happened
   m_pt = point;

void COGView::OnRButtonDown(UINT nFlags, CPoint point)
   //====== Remember the fact
   m_bRightButton = true;

   //====== and reproduce the left button response
   OnLButtonDown(nFlags, point);

void COGView::OnLButtonUp(UINT nFlags, CPoint point)
   //====== If we captured the mouse,
   if (m_bCaptured)
      //=== query the desired quantum value
      //=== if it exeeds the sensativity threshold
      if (fabs(m_dx) > 0.5f || fabs(m_dy) > 0.5f)
         //=== Turn on the constant rotation
         //=== Turn off the constant rotation

      //====== Clear the capture flag
      m_bCaptured = false;

void COGView::OnRButtonUp(UINT nFlags, CPoint point) 
   m_bRightButton = m_bCaptured = false;

void COGView::OnMouseMove(UINT nFlags, CPoint point)
   if (m_bCaptured)
      // Desired rotation speed components
      m_dy = float(point.y - m_pt.y)/40.f;
      m_dx = float(point.x - m_pt.x)/40.f;

      //====== If Ctrl was pressed
      if (nFlags & MK_CONTROL)
         //=== we shift (translate) the image
         m_xTrans += m_dx;
         m_yTrans -= m_dy;
         //====== If the right mouse button is pressed
         if (m_bRightButton)
            //====== we shift along the z-axis
            m_zTrans += (m_dx + m_dy)/2.f;
            //====== otherwise we rotate the image
            double a = fabs(m_AngleX);
            if (90. < a && a < 270.)
               m_dx = -m_dx;
            m_AngleX += m_dy;
            m_AngleY += m_dx;
      //=== In any case we should store the coordinates
      m_pt = point;

void COGView::OnTimer(UINT nIDEvent)
   //====== Increase the angles
   m_AngleX += m_dy;
   m_AngleY += m_dx;
To control the lighting parameters we supplied the modeless dialog (see downloads) which uses one OnHScroll message handler to handle all the eleven sliders. That is also a known and rather efficient technology widely used in SDK programming. The auxiliary GetSliderNum function (and IDs[] array) are needed to eliminate the frequent and unpleasant errors encountered due to resource indices change.


Download demo project - 19 Kb
Download source - 30 Kb

This article was originally published on July 10th, 2001

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date