www.pudn.com > c++25.rar > OpenGLWnd.cpp
// OpenGLWnd.cpp : implementation file
//
#include "stdafx.h"
#include "OpenGLTest.h"
#include "OpenGLWnd.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define MAX_LISTS 20
// used to identify a MCD video driver (partial OGL acceleration)
#define INSTALLABLE_DRIVER_TYPE_MASK (PFD_GENERIC_ACCELERATED|PFD_GENERIC_FORMAT)
/////////////////////////////////////////////////////////////////////////////
// Global Functions/variables
// These functions are used by CGLTesselator class
struct GarbListItem{
GLdouble *pvert;
GarbListItem* next;};
GarbListItem* m_garbagelist=NULL;
void AddGarbage(GLdouble * ptr)
{
ASSERT(ptr!=NULL);
// allocate mem for new list item
GarbListItem* temp=new GarbListItem;
// store pointer
temp->pvert=ptr;
// add at head of list
temp->next=m_garbagelist;
m_garbagelist=temp;
}
void DeleteGarbage()
{
if(m_garbagelist!=NULL)
{
GarbListItem* punt=m_garbagelist;
GarbListItem* temp=m_garbagelist;
// scan the list
while(punt!=NULL)
{
// delete vertex
delete[] punt->pvert;
punt=punt->next;
// delete list item
delete temp;
temp=punt;
};
m_garbagelist=NULL;
};
}
void CALLBACK BeginCallback(GLenum type)
{
// issue corresponding GL call
glBegin(type);
}
void CALLBACK ErrorCallback(GLenum errorCode)
{
const GLubyte *estring;
CString mexstr;
// get the error descritption from OGL
estring = gluErrorString(errorCode);
// prepare and show a message box
mexstr.Format("Tessellation/Quadric Error: %s\n", estring);
AfxMessageBox(mexstr,MB_OK | MB_ICONEXCLAMATION);
// replicate mex to debug trace
TRACE("Tessellation Error: %s\n", estring);
}
void CALLBACK EndCallback()
{
// issue corresponding GL call
glEnd();
}
void CALLBACK VertexCallback(GLvoid *vertex)
{
// issue corresponding GL call (double is used to get max precision)
glVertex3dv( (const double *)vertex );
}
void CALLBACK CombineCallback(GLdouble coords[3], GLdouble *data[4], GLfloat weight[4], GLdouble **dataOut )
{
// allocate memory for a new vertex
GLdouble *vertex;
vertex = new GLdouble[3];
// store reported vertex
vertex[0] = coords[0];
vertex[1] = coords[1];
vertex[2] = coords[2];
// return vertex to OGL
*dataOut = vertex;
// add vertex pointer to garbage collection routines
AddGarbage(vertex);
}
// these are used to construct an equilibrated 256 color palette
static unsigned char _threeto8[8] =
{
0, 0111>>1, 0222>>1, 0333>>1, 0444>>1, 0555>>1, 0666>>1, 0377
};
static unsigned char _twoto8[4] =
{
0, 0x55, 0xaa, 0xff
};
static unsigned char _oneto8[2] =
{
0, 255
};
static int defaultOverride[13] =
{
0, 3, 24, 27, 64, 67, 88, 173, 181, 236, 247, 164, 91
};
// Windows Default Palette
static PALETTEENTRY defaultPalEntry[20] =
{
{ 0, 0, 0, 0 },
{ 0x80,0, 0, 0 },
{ 0, 0x80,0, 0 },
{ 0x80,0x80,0, 0 },
{ 0, 0, 0x80, 0 },
{ 0x80,0, 0x80, 0 },
{ 0, 0x80,0x80, 0 },
{ 0xC0,0xC0,0xC0, 0 },
{ 192, 220, 192, 0 },
{ 166, 202, 240, 0 },
{ 255, 251, 240, 0 },
{ 160, 160, 164, 0 },
{ 0x80,0x80,0x80, 0 },
{ 0xFF,0, 0, 0 },
{ 0, 0xFF,0, 0 },
{ 0xFF,0xFF,0, 0 },
{ 0, 0, 0xFF, 0 },
{ 0xFF,0, 0xFF, 0 },
{ 0, 0xFF,0xFF, 0 },
{ 0xFF,0xFF,0xFF, 0 }
};
/////////////////////////////////////////////////////////////////////////////
// COpenGLWnd
COpenGLWnd::COpenGLWnd() :
m_dAspectRatio(1.0),
m_bInsideDispList(FALSE), m_bExternDispListCall(FALSE),
m_bExternGLCall(FALSE)
{
// define a default cursor
m_hMouseCursor=AfxGetApp()->LoadStandardCursor(IDC_SIZEALL);
// set the disp list vector to all zeros
for (int c=0;cGetSafeHdc()==wglGetCurrentDC() ))
{
// ...if not specify the target DeviceContext of the subsequent OGL calls
wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);
// set a warning for EndDispList
m_bExternDispListCall=TRUE;
};
// create a handle to the disp list (actually an integer)
m_DispListVector[c]=glGenLists(1);
// set a semaphore
m_bInsideDispList=TRUE;
// start the disp list: all subsequent OGL calls will be redirected to the list
glNewList(m_DispListVector[c],GL_COMPILE);
};
}
void COpenGLWnd::EndStockListDef()
{
// close the disp list
glEndList();
// unset the semaphore
m_bInsideDispList=FALSE;
// if beginDispList set the warn free the target DeviceContext
if(m_bExternDispListCall) wglMakeCurrent(NULL,NULL);
}
void COpenGLWnd::DrawStockDispLists()
{
// check if we are already inside a drawing session
if(m_hRC==wglGetCurrentContext() && m_pCDC->GetSafeHdc()==wglGetCurrentDC() )
{
// draw directly all display lists
for (int c=0;cGetSafeHdc(), m_hRC);
// draw all display lists
for (int c=0;cGetSafeHdc()==wglGetCurrentDC() )
{
// delete active display lists
for (int c=0;cGetSafeHdc(), m_hRC);
// delete active display lists
for (int c=0;cGetSafeHdc(), ::GetPixelFormat(m_pCDC->GetSafeHdc()),sizeof(PIXELFORMATDESCRIPTOR), &pfd );
// specify the target DeviceContext of the subsequent OGL calls
wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);
switch(type)
{
// Derive driver information
case ACCELERATION: if( 0==(INSTALLABLE_DRIVER_TYPE_MASK & pfd.dwFlags) ) str="Fully Accelerated (ICD)"; // fully in hardware (fastest)
else if (INSTALLABLE_DRIVER_TYPE_MASK==(INSTALLABLE_DRIVER_TYPE_MASK & pfd.dwFlags) ) str="Partially Accelerated (MCD)"; // partially in hardware (pretty fast, maybe..)
else str="Not Accelerated (Software)"; // software
break;
// get the company name responsible for this implementation
case VENDOR:str=(char*)::glGetString(GL_VENDOR);
if ( ::glGetError()!=GL_NO_ERROR) str.Format("Not Available");// failed!
break;
// get the renderer name; this is specific of an hardware configuration
case RENDERER:str=(char*)::glGetString(GL_RENDERER);
if ( ::glGetError()!=GL_NO_ERROR) str.Format("Not Available");// failed!
break;
// get the version
case VERSION:str=(char*)::glGetString(GL_VERSION);
if ( ::glGetError()!=GL_NO_ERROR) str.Format("Not Available");// failed!
break;
// return a space separated list of extensions
case EXTENSIONS: str=(char*)::glGetString(GL_EXTENSIONS);
if ( ::glGetError()!=GL_NO_ERROR) str.Format("Not Available");// failed!
break;
};
// free the target DeviceContext (window) and return the result
wglMakeCurrent(NULL,NULL);
return str;
}
void COpenGLWnd::SetMouseCursor(HCURSOR mcursor)
{
// set the specified cursor (only if it is a valid one)
if(mcursor!=NULL) m_hMouseCursor=mcursor;
}
void COpenGLWnd::BeginGLCommands()
{
// check if we are inside a drawing session or not....
if(!( m_hRC==wglGetCurrentContext() && m_pCDC->GetSafeHdc()==wglGetCurrentDC() ))
{
// ...if not specify the target DeviceContext of the subsequent OGL calls
wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);
// set a warning for EndGLCommands
m_bExternGLCall=TRUE;
};
}
void COpenGLWnd::EndGLCommands()
{
// if BeginGLCommands set the warn free the target DeviceContext
if(m_bExternGLCall) wglMakeCurrent(NULL,NULL);
}
void COpenGLWnd::OnCreateGL()
{
// perform hidden line/surface removal (enabling Z-Buffer)
glEnable(GL_DEPTH_TEST);
// set background color to black
glClearColor(0.f,0.f,0.f,1.0f );
// set clear Z-Buffer value
glClearDepth(1.0f);
}
void COpenGLWnd::OnSizeGL(int cx, int cy)
{
// set correspondence between window and OGL viewport
glViewport(0,0,cx,cy);
// update the camera
glPushMatrix();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(40.0,m_dAspectRatio,0.1f, 10.0f);
glTranslatef(0.0f,0.0f,-4.f);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
void COpenGLWnd::OnDrawGL()
{
// draw carthesian axes
glBegin(GL_LINES);
// red x axis
glColor3f(1.f,0.f,0.f);
glVertex3f(0.0f,0.0f,0.0f);
glVertex3f(1.0f,0.0f,0.0f);
glVertex3f(1.0f,0.0f,0.0f);
glVertex3f(0.9f,0.1f,0.0f);
glVertex3f(1.0f,0.0f,0.0f);
glVertex3f(0.9f,-0.1f,0.0f);
// green y axis
glColor3f(0.f,1.f,0.f);
glVertex3f(0.0f,0.0f,0.0f);
glVertex3f(0.0f,1.0f,0.0f);
glVertex3f(0.0f,1.0f,0.0f);
glVertex3f(0.1f,0.9f,0.0f);
glVertex3f(0.0f,1.0f,0.0f);
glVertex3f(-0.1f,0.9f,0.0f);
// blue z axis
glColor3f(0.f,0.f,1.f);
glVertex3f(0.0f,0.0f,0.0f);
glVertex3f(0.0f,0.0f,1.0f);
glVertex3f(0.0f,0.0f,1.0f);
glVertex3f(0.0f,0.1f,0.9f);
glVertex3f(0.0f,0.0f,1.0f);
glVertex3f(0.0f,-0.1f,0.9f);
glEnd();
}
void COpenGLWnd::VideoMode(ColorsNumber &c,ZAccuracy &z,BOOL &dbuf)
{
// set default videomode
c=MILLIONS;
z=NORMAL;
dbuf=TRUE;
}
unsigned char COpenGLWnd::ComponentFromIndex(int i, UINT nbits, UINT shift)
{
unsigned char val;
val = (unsigned char) (i >> shift);
switch (nbits)
{
case 1:
val &= 0x1;
return _oneto8[val];
case 2:
val &= 0x3;
return _twoto8[val];
case 3:
val &= 0x7;
return _threeto8[val];
default:
return 0;
}
}
void COpenGLWnd::CreateRGBPalette()
{
PIXELFORMATDESCRIPTOR pfd;
LOGPALETTE *pPal;
int n, i;
// get the initially choosen video mode
n = ::GetPixelFormat(m_pCDC->GetSafeHdc());
::DescribePixelFormat(m_pCDC->GetSafeHdc(), n, sizeof(pfd), &pfd);
// if is an indexed one...
if (pfd.dwFlags & PFD_NEED_PALETTE)
{
// ... construct an equilibrated palette (3 red bits, 3 green bits, 2 blue bits)
// NOTE: this code is integrally taken from MFC example Cube
n = 1 << pfd.cColorBits;
pPal = (PLOGPALETTE) new char[sizeof(LOGPALETTE) + n * sizeof(PALETTEENTRY)];
ASSERT(pPal != NULL);
pPal->palVersion = 0x300;
pPal->palNumEntries = n;
for (i=0; ipalPalEntry[i].peRed=ComponentFromIndex(i, pfd.cRedBits, pfd.cRedShift);
pPal->palPalEntry[i].peGreen=ComponentFromIndex(i, pfd.cGreenBits, pfd.cGreenShift);
pPal->palPalEntry[i].peBlue=ComponentFromIndex(i, pfd.cBlueBits, pfd.cBlueShift);
pPal->palPalEntry[i].peFlags=0;
}
// fix up the palette to include the default Windows palette
if ((pfd.cColorBits == 8) &&
(pfd.cRedBits == 3) && (pfd.cRedShift == 0) &&
(pfd.cGreenBits == 3) && (pfd.cGreenShift == 3) &&
(pfd.cBlueBits == 2) && (pfd.cBlueShift == 6)
)
{
for (i = 1 ; i <= 12 ; i++)
pPal->palPalEntry[defaultOverride[i]] = defaultPalEntry[i];
}
m_CurrentPalette.CreatePalette(pPal);
delete pPal;
// set the palette
m_pOldPalette=m_pCDC->SelectPalette(&m_CurrentPalette, FALSE);
m_pCDC->RealizePalette();
}
}
BOOL COpenGLWnd::SetupPixelFormat()
{
// define default desired video mode (pixel format)
static PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd
1, // version number
PFD_DRAW_TO_WINDOW | // support window
PFD_SUPPORT_OPENGL | // support OpenGL
PFD_DOUBLEBUFFER, // double buffered
PFD_TYPE_RGBA, // RGBA type
24, // 24-bit color depth
0, 0, 0, 0, 0, 0, // color bits ignored
0, // no alpha buffer
0, // shift bit ignored
0, // no accumulation buffer
0, 0, 0, 0, // accum bits ignored
16, // 32-bit z-buffer
0, // no stencil buffer
0, // no auxiliary buffer
PFD_MAIN_PLANE, // main layer
0, // reserved
0, 0, 0 // layer masks ignored
};
// let the user change some parameters if he wants
BOOL bDoublBuf;
ColorsNumber cnum;
ZAccuracy zdepth;
VideoMode(cnum,zdepth,bDoublBuf);
//set the changes
if(bDoublBuf) pfd.dwFlags=PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |PFD_DOUBLEBUFFER;
else pfd.dwFlags=PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
switch(cnum)
{
case INDEXED: pfd.cColorBits=8;
case THOUSANDS: pfd.cColorBits=16;
case MILLIONS: pfd.cColorBits=24;
case MILLIONS_WITH_TRANSPARENCY: pfd.cColorBits=32;
};
switch(zdepth)
{
case NORMAL: pfd.cDepthBits=16;
case ACCURATE: pfd.cDepthBits=32;
};
// ask the system for such video mode
ASSERT(m_pCDC != NULL);
int pixelformat;
if ( (pixelformat = ChoosePixelFormat(m_pCDC->GetSafeHdc(), &pfd)) == 0 )
{
AfxMessageBox("ChoosePixelFormat failed");
return FALSE;
}
// try to set this video mode
if (SetPixelFormat(m_pCDC->GetSafeHdc(), pixelformat, &pfd) == FALSE)
{
// the requested video mode is not available so get a default one
pixelformat = 1;
if (DescribePixelFormat(m_pCDC->GetSafeHdc(), pixelformat, sizeof(PIXELFORMATDESCRIPTOR), &pfd)==0)
{
// neither the requested nor the default are available: fail
AfxMessageBox("SetPixelFormat failed (no OpenGL compatible video mode)");
return FALSE;
}
}
return TRUE;
}
int COpenGLWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWndEx::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
// OpenGL rendering context creation
PIXELFORMATDESCRIPTOR pfd;
int n;
// initialize the private member
m_pCDC= new CClientDC(this);
// choose the requested video mode
if (!SetupPixelFormat()) return 0;
// ask the system if the video mode is supported
n=::GetPixelFormat(m_pCDC->GetSafeHdc());
::DescribePixelFormat(m_pCDC->GetSafeHdc(),n,sizeof(pfd),&pfd);
// create a palette if the requested video mode has 256 colors (indexed mode)
CreateRGBPalette();
// link the Win Device Context with the OGL Rendering Context
m_hRC = wglCreateContext(m_pCDC->GetSafeHdc());
// specify the target DeviceContext (window) of the subsequent OGL calls
wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);
// performs default setting of rendering mode,etc..
OnCreateGL();
// free the target DeviceContext (window)
wglMakeCurrent(NULL,NULL);
return 0;
}
void COpenGLWnd::OnDestroy()
{
CWndEx::OnDestroy();
// TODO: Add your message handler code here
// specify the target DeviceContext (window) of the subsequent OGL calls
wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);
// remove all display lists
for (int c=0;cSelectPalette(&palDefault, FALSE);
// destroy Win Device Context
if(m_pCDC) delete m_pCDC;
}
void COpenGLWnd::OnSize(UINT nType, int cx, int cy)
{
CWndEx::OnSize(nType, cx, cy);
// TODO: Add your message handler code here
// when called with a nonzero window:
if ( 0 < cx && 0 < cy )
{
// update the rect and the aspect ratio
m_ClientRect.right = cx;
m_ClientRect.bottom = cy;
m_dAspectRatio=double(cx)/double(cy);
// specify the target DeviceContext of the subsequent OGL calls
wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);
// call the virtual sizing procedure (to be overridden by user)
OnSizeGL(cx,cy);
// free the target DeviceContext (window)
wglMakeCurrent(NULL,NULL);
// force redraw
Invalidate(TRUE);
};
}
BOOL COpenGLWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
// TODO: Add your message handler code here and/or call default
ASSERT(m_hMouseCursor!=NULL);
::SetCursor(m_hMouseCursor);
return TRUE;
// return CWndEx::OnSetCursor(pWnd, nHitTest, message);
}
void COpenGLWnd::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
// Do not call CWndEx::OnPaint() for painting messages
CDC* pDC = &dc;
// prepare a semaphore
static BOOL bBusy = FALSE;
// use the semaphore to enter this critic section
if(bBusy) return;
bBusy = TRUE;
// specify the target DeviceContext of the subsequent OGL calls
wglMakeCurrent(m_pCDC->GetSafeHdc(), m_hRC);
// clear background
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// call the virtual drawing procedure (to be overridden by user)
OnDrawGL();
// execute OGL commands (flush the OGL graphical pipeline)
glFinish();
// if double buffering is used it's time to swap the buffers
SwapBuffers(m_pCDC->GetSafeHdc());
// turn the semaphore "green"
bBusy = FALSE;
// free the target DeviceContext (window)
wglMakeCurrent(NULL,NULL);
}
BOOL COpenGLWnd::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
// return CWndEx::OnEraseBkgnd(pDC);
return TRUE;
}
COpenGLWnd::CGLDispList::CGLDispList():
m_glListId(0), m_bIsolated(FALSE)
{
}
COpenGLWnd::CGLDispList::~CGLDispList()
{
// remove display list
glDeleteLists(m_glListId,1);
}
//////////////////////////////////////////////////////////////////////
// Member functions
void COpenGLWnd::CGLDispList::Draw()
{
// if the list is not empty...
if(m_glListId)
{
if(m_bIsolated)
{
// save current transformation matrix
glPushMatrix();
// save current OGL internal state (lighting, shading, and such)
glPushAttrib(GL_ALL_ATTRIB_BITS);
};
// draw the list
glCallList(m_glListId);
if(m_bIsolated)
{
// restore transformation matrix
glPopMatrix();
// restore OGL internal state
glPopAttrib();
};
};
}
void COpenGLWnd::CGLDispList::StartDef(BOOL bImmediateExec)
{
// set the context for GL calls (if needed)
// BeginGLCommands();
// check if another list is under construction
int cur;
glGetIntegerv(GL_LIST_INDEX,&cur);
if(cur != 0) {TRACE("Error: Nested display list definition!");ASSERT(FALSE);};
// if the list is empty firstly allocate one
if(!m_glListId) m_glListId=glGenLists(1);
// start or replace a list definition
if (bImmediateExec) glNewList(m_glListId,GL_COMPILE_AND_EXECUTE);
else glNewList(m_glListId,GL_COMPILE);
}
void COpenGLWnd::CGLDispList::EndDef()
{
// check the coupling with a preceding call to StartDef()
int cur;
glGetIntegerv(GL_LIST_INDEX,&cur);
if(cur != m_glListId) {TRACE("CGLDispList:Missing StartDef() before EndDef()\n");return;};
// close list definition
glEndList();
// free the context (if needed)
// EndGLCommands();
}
//////////////////////////////////////////////////////////////////////
//
// Implementation of COpenGLWnd::CGLTesselator class.
//
/*** DESCRIPTION
This is actually a helper class which wraps the
use of tessellation objects in OGL (see guide).
It must be used inside an GLEnabledView cause
a tesselator object must refer to a Rendering Context.
****************************************/
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
COpenGLWnd::CGLTesselator::CGLTesselator()
{
// create tessellation object
m_tessObj=gluNewTess();
// set callback functions
gluTessCallback(m_tessObj,GLU_TESS_BEGIN,(void (CALLBACK*)())&BeginCallback);
gluTessCallback(m_tessObj,GLU_TESS_VERTEX,(void (CALLBACK*)())&VertexCallback);
gluTessCallback(m_tessObj,GLU_TESS_END,(void (CALLBACK*)())&EndCallback);
gluTessCallback(m_tessObj,GLU_TESS_COMBINE,(void (CALLBACK*)())&CombineCallback);
gluTessCallback(m_tessObj,GLU_TESS_ERROR,(void (CALLBACK*)())&ErrorCallback);
}
COpenGLWnd::CGLTesselator::~CGLTesselator()
{
// remove tessellation object
gluDeleteTess(m_tessObj);
}
//////////////////////////////////////////////////////////////////////
// Member functions
void COpenGLWnd::CGLTesselator::SetWindingRule(GLdouble which)
{
// issue the equivalent GL call
gluTessProperty(m_tessObj,GLU_TESS_WINDING_RULE,which);
}
GLdouble COpenGLWnd::CGLTesselator::GetWindingRule()
{
//retrieve attribute
GLdouble temp;
gluTessProperty(m_tessObj,GLU_TESS_WINDING_RULE,temp);
// return value
return temp;
}
void COpenGLWnd::CGLTesselator::SetFilling(BOOL bFill)
{
// issue the equivalent GL calls
if(bFill) gluTessProperty(m_tessObj,GLU_TESS_BOUNDARY_ONLY,GL_FALSE);
else gluTessProperty(m_tessObj,GLU_TESS_BOUNDARY_ONLY,GL_TRUE);
}
BOOL COpenGLWnd::CGLTesselator::GetFilling()
{
//retrieve attribute
GLdouble temp;
gluTessProperty(m_tessObj,GLU_TESS_BOUNDARY_ONLY,temp);
// convert to a boolean
return (temp==GL_TRUE);
}
void COpenGLWnd::CGLTesselator::StartDef()
{
// start a polygon definition
gluTessBeginPolygon(m_tessObj,NULL);
// start a contour definition
gluTessBeginContour(m_tessObj);
}
void COpenGLWnd::CGLTesselator::EndDef()
{
// end contour and polygon definition
gluTessEndContour(m_tessObj);
gluTessEndPolygon(m_tessObj);
// free new vertices created by tessellation
::DeleteGarbage();
}
void COpenGLWnd::CGLTesselator::ContourSeparator()
{
// insert a contour separation
gluTessEndContour(m_tessObj);
gluTessBeginContour(m_tessObj);
}
void COpenGLWnd::CGLTesselator::AddVertex(GLdouble vertData[3])
{
// IMPORTANT: the 3rd parameter must be given otherwise an access
// violation will occur, moreover every vertex must have it's own memory
// location till the closing of the polygon (that is you can't pass all
// the vertices trough the same variable in a for loop).
gluTessVertex(m_tessObj,vertData,vertData);
}
void COpenGLWnd::CGLTesselator::AddVertexArray(GLdouble arr[][3], int size)
{
ASSERT(arr!=NULL);
// pass the vertices to the tessellation object
for(int ct=0;ct