Function graphics in 3D



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
{
public:
   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
{
protected:
   COGView();
   DECLARE_DYNCREATE(COGView)
public:
   //======== 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

   //{{AFX_VIRTUAL(COGView)
   public:
   virtual void OnDraw(CDC* pDC);
   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
   //}}AFX_VIRTUAL
protected:
   //{{AFX_MSG(COGView)
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()
};

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.
COGView::COGView()
{
   //====== 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
   DefaultGraphic();

   //====== 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
   PIXELFORMATDESCRIPTOR pfd =   
   {
      sizeof(PIXELFORMATDESCRIPTOR),
      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 )
   {
      MessageBox("ChoosePixelFormat::Error");
      return -1;
   }

   //====== Try to set this format
   if ( !SetPixelFormat (m_hdc, iD, &pfd) )
   {
      MessageBox("SetPixelFormat::Error");
      return -1;
   }

   //====== Try to create the OpenGL rendering context
   if ( !(m_hRC = wglCreateContext (m_hdc)))
   {
      MessageBox("wglCreateContext::Error");
      return -1;
   }

   //====== Try to put it in action
   if ( !wglMakeCurrent (m_hdc, m_hRC))
   {
      MessageBox("wglMakeCurrent::Error");
      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
   glEnable(GL_LIGHT0);
   //====== Depth of the Z-buffer will be taken into account
   glEnable(GL_DEPTH_TEST);
   //====== Material colors will be taken into account
   glEnable(GL_COLOR_MATERIAL);

   //====== Our function to set the background
   SetBkColor();

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

Redrawings

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
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   //========== Clean the modeling matrix 
   //           (make it equal the unity matrix)
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

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

   //====== 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)
   glCallList(1);

   //====== Switch back and front buffers 
   //          (to show what happened)
   SwapBuffers(m_hdc);
}

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

   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1);

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

   float fPos[] =
   {
      (m_LightParam[0]-50)*m_fRangeX/100,
      (m_LightParam[1]-50)*m_fRangeY/100,
      (m_LightParam[2]-50)*m_fRangeZ/100,
      1.f
   };
   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
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

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)
         glBegin(GL_QUAD_STRIP);

      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
         float
            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);
         }
         else
         //===== 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)
         glEnd();
   }
      //====== Close block of GL_QUADS commands
   if (m_bQuad)
      glEnd();

   //====== Close the list of OpenGL commands
   glEndList();
}   
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"), 
                             GENERIC_WRITE,
                             0,0,
                             CREATE_ALWAYS,
                             FILE_ATTRIBUTE_NORMAL,
                             0);

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

   //====== Close the file
   CloseHandle(hFile);

   //====== 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("*.dat\0")
         TEXT("All Files\0")
         TEXT("*.*\0");

   //====== Query the current folder
   TCHAR szCurDir[MAX_PATH];
   ::GetCurrentDirectory(MAX_PATH-1,szCurDir);

   //=== Struct used by the standard file dialog
   OPENFILENAME ofn;
   ZeroMemory(&ofn,sizeof(OPENFILENAME));

   //====== 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_SHARE_READ, 0, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL, 0);

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

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

      //====== Create and store the image
      DrawScene();

      //====== Redraw the window client region
      Invalidate(FALSE);
   }
}
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)
   {
      GetLastError();
      MessageBox(_T("Could not get file size"));
      CloseHandle(hFile);
      return false;
   }

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

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

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

   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"));
      return;
   }

   //===== Resize the container
   m_cPoints.resize(m_xSize*m_zSize);

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

   //====== 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
   KillTimer(1);

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

   //====== Capture the mouse messages and direct them 
   //       in our window
   SetCapture();
   //====== 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
         SetTimer(1,33,0);
      else
         //=== Turn off the constant rotation
         KillTimer(1);

      //====== Clear the capture flag
      m_bCaptured = false;
      ReleaseCapture();
   }
}


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

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;
      }
      else
      {
         //====== If the right mouse button is pressed
         if (m_bRightButton)
            //====== we shift along the z-axis
            m_zTrans += (m_dx + m_dy)/2.f;
         else
         {
            //====== otherwise we rotate the image
            LimitAngles();
            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;
      Invalidate(FALSE);
   }
}

void COGView::OnTimer(UINT nIDEvent)
{
   LimitAngles();
   //====== Increase the angles
   m_AngleX += m_dy;
   m_AngleY += m_dx;
   Invalidate(FALSE);
}
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.

Downloads

Download demo project - 19 Kb
Download source - 30 Kb


Comments

  • Thank

    Posted by zlj644 on 02/16/2010 10:47pm

    Thanks a lot for this useful excellent application!

    Reply
  • Initally updating the window when compiled using Visual Studio 2005

    Posted by Mike Pliam on 09/09/2008 03:38pm

    have rebuilt the MFC/OpenGL interface using Visual Studio 2005. Essentially no major coding changes were required. The revised application runs quite nicely with one exception. When first booting up the app, the view window is transparent. This is immediately corrected by doing any action that requires updating the window, for example, minimizing and then restoring, moving the window partially off the computer screen, then back again, etc. I would like to have the application automatically update the window on the initial boot. I have implemented CView::OnInitialUpdate(), and while this method is always called when first opening the application, I cannot get any of the usual methods to work to programatically, i.e., Invalidate(), etc. It was not a problem with the same code using Visual Studio 7.0 and earlier. Can anyone out there help me solve this problem? __________________

    Reply
  • Dynamic change in surface

    Posted by Legacy on 11/25/2003 12:00am

    Originally posted by: Hari

    I would like to draw a dynamic change in the surface vertex onto which I could read from a file. How I can do this?

    Reply
  • code

    Posted by Legacy on 08/15/2003 12:00am

    Originally posted by: mm


    goods!

    Reply
  • Excellent! ActiveX control ...

    Posted by Legacy on 06/24/2003 12:00am

    Originally posted by: Nikolai

    http://www.codeproject.com/useritems/NTGraph3D_ATL.asp

    Tanks a lot!!!

    Reply
  • Thanks a lot for this application.

    Posted by Legacy on 12/08/2002 12:00am

    Originally posted by: Chatain Pascal

    That exactly what I've tried to find for a long time.

    Reply
  • Using menu to load me another Opengl Window

    Posted by Legacy on 11/11/2002 12:00am

    Originally posted by: jayman_bob

    Played around with this app called OG, which I download here. Written me a little program that I want to use wiff this OG. But I want the user to call it from a menu.So I
    adds it as a OG function in OG.cpp and og.h.( It is basically my WinMain function if the little program is standalone ). Yeah I use CreateMyWindow here.

    When I close this menuopenGLWindowNew type thing. The original OG STUFF no work, no more. The toolbar she is dead. The menu OG opengl commands she is dead.( Except my MenuWindow type thing and exit,open, etc.)

    What is wrong? I would like to know how to use this following wiff in OG.
    I create struct and than I would
    SetWindowLong(g_hWnd,GWL_USERDATA, (LONG)&tHestruct).
    Than I must goes
    tHestruct = (myStruct*)(GetWindowLong(g_hWnd,GWL_USERDATA));
    than I do this
    wglMakeCurrent( tHestruct.tHe_hDC, tHestruct.tHe_hRC );
    But is no good.
    This is problems I get:
    1) How I get g_hWnd for Og app?
    2) How I do my assignment operator definition is sposed to be defined?.

    Me not no how to do this? Me no want to use childWinnows.

    " My head hurts it must be going to snow"

    • faint

      Posted by Calois on 08/09/2004 04:16am

      so ma fan

      Reply
    Reply
  • Importing in Borland

    Posted by Legacy on 07/29/2002 12:00am

    Originally posted by: slamdunk

    Hi,
    great application first of all.
    I would like to write your code in Borland C++
    coz that's my platform.
    I can run the program but i just see the blue background.
    I've used the FormPaint event handler as OnPaint, could it be he problem?
    Cheers

    Marco

    Reply
  • Resource Problem (Fix)

    Posted by Legacy on 07/26/2002 12:00am

    Originally posted by: Rodri

    I would like to thank you for the effort you have put into writting the article "Function graphics in 3D" in CodeGuru. It helped me a lot to write a 3D viewer for my 2D spectra data. 
    
    I have found a small problem regarding resources management. I think the OpenGL rendering context should be removed before destroying the view. I found this problem in my application because I implemented the 3D viewer in a dialog box and each time the dialog box was invoked, the memory requirements grew up significantly. I solved it by adding this code:

    void C3DView::OnDestroy()
    {

    // specify the target DeviceContext (window) of the subsequent OGL calls
    wglMakeCurrent(m_hdc, m_hRC);

    // release definitely OGL Rendering Context
    if (m_hRC!=NULL) ::wglDeleteContext(m_hRC);

    C3DView::OnDestroy();
    }

    I would also like to ask you if you know why I can't implement alpha-blending into the viewer. I added a menu option in order to enable alpha blending but it doesn't work properly. This is the code I'm using in response to the menu command:

    void C3DView::OnAlphaBlend()
    {
    if(::glIsEnabled(GL_BLEND))
    ::glDisable(GL_BLEND);
    else
    ::glEnable(GL_BLEND);
    InvalidateRect(NULL, FALSE);

    }

    Thanks again for your nice article and code.
    Best regards,
    Rodri

    Reply
  • How to put different images in different sub-windows

    Posted by Legacy on 05/01/2002 12:00am

    Originally posted by: yang

    I tried to put different images in different sub-windows, 
    
    but failed. Here was what I tried.

    Four sub-windows were directly inherited from CView (MFC),
    , say CView1:CView, CView2:CView, CView3:CView,
    CView4:CView, as shown in following.
    ---------------------------------------------
    | || |
    | window 1 || window 2 |
    | || |
    |===========================================|
    | || |
    | window 3 || window 4 |
    | || |
    ---------------------------------------------
    For example, I want to put
    (1) sin(x)*sin(y) in window 1
    (2) cos(x)*sin(y) in window 2
    (3) cos(x)*cos(y) in window 3
    (4) sin(x)*tan(y) in window 4

    Unfortunately, only the current data was drawn in the
    current sub-window, and all previous images were erased,
    although seperate codes were written in different CView's
    (CView1, CView2, CView3, CView4).

    I'm afraid OpenGL shared a same buffer for all CView's.
    Could you tell me how to put data on different sub-windows?
    I mean I just want to put (1) in CView1, (2) in CView2, (3)
    in CView3, and (4) in CView4. Thanks a lot.

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Due to internal controls and regulations, the amount of long term archival data is increasing every year. Since magnetic tape does not need to be periodically operated or connected to a power source, there will be no data loss because of performance degradation due to the drive actuator. Read this white paper to learn about a series of tests that determined magnetic tape is a reliable long-term storage solution for up to 30 years.

Most Popular Programming Stories

More for Developers

RSS Feeds