/******************************Module*Header*******************************\
* Module Name: state.cxx
*
* STATE
*
* Copyright 1995 - 1998 Microsoft Corporation
*
\**************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>
#include "sspipes.h"
#include "dialog.h"
#include "state.h"
#include "pipe.h"
#include "npipe.h"
#include "fpipe.h"
#include "eval.h"
// default texture resource(s)
#define DEF_TEX_COUNT 1
TEX_RES gTexRes[DEF_TEX_COUNT] = {
{ TEX_BMP, IDB_DEFTEX }
};
static void InitTexParams();
/******************************Public*Routine******************************\
* STATE constructor
*
* - global state init
* - translates variables set from the dialog boxes
*
\**************************************************************************/
STATE::STATE( BOOL bFlexMode, BOOL bMultiPipes )
{
// various state values
resetStatus = RESET_STARTUP_BIT;
// Put initial hglrc in drawThreads[0]
// This RC is also used for dlists and texture objects that are shared
// by other RC's
shareRC = wglGetCurrentContext();
drawThreads[0].SetRCDC( shareRC, wglGetCurrentDC() );
bTexture = FALSE;
if( ulSurfStyle == SURFSTYLE_TEX ) {
if( LoadTextureFiles( gTexFile, gnTextures, &gTexRes[0] ) )
bTexture = TRUE;
}
else if( ulSurfStyle == SURFSTYLE_WIREFRAME ) {
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
}
// Initialize GL state for the initial RC (sets texture state, so
// (must come after LoadTextureFiles())
GLInit();
// set 'reference' radius value
radius = 1.0f;
// convert tesselation from fTesselFact(0.0-2.0) to tessLevel(0-MAX_TESS)
int tessLevel = (int) (fTesselFact * (MAX_TESS+1) / 2.0001f);
nSlices = (tessLevel+2) * 4;
// Allocate basic NODE_ARRAY
// NODE_ARRAY size is determined in Reshape (based on window size)
nodes = new NODE_ARRAY;
// Set drawing mode, and initialize accordingly. For now, either all normal
// or all flex pipes are drawn, but they could be combined later.
// Can assume here that if there's any possibility that normal pipes
// will be drawn, NORMAL_STATE will be initialized so that dlists are
// built
// Again, since have either NORMAL or FLEX, set maxPipesPerFrame,
// maxDrawThreads
if( bMultiPipes )
maxDrawThreads = MAX_DRAW_THREADS;
else
maxDrawThreads = 1;
nDrawThreads = 0; // no active threads yet
nPipesDrawn = 0;
// maxPipesPerFrame is set in Reset()
if( bFlexMode ) {
drawMode = DRAW_FLEX;
pFState = new FLEX_STATE( this );
pNState = NULL;
} else {
drawMode = DRAW_NORMAL;
pNState = new NORMAL_STATE( this );
pFState = NULL;
}
// initialize materials
if( bTexture )
ss_InitTexMaterials();
else
ss_InitTeaMaterials();
// default draw scheme
drawScheme = FRAME_SCHEME_RANDOM;
}
/******************************Public*Routine******************************\
* STATE destructor
*
\**************************************************************************/
STATE::~STATE( )
{
if( pNState )
delete pNState;
if( pFState )
delete pFState;
if( nodes )
delete nodes;
if( bTexture ) {
for( int i = 0; i < nTextures; i ++ ) {
ss_DeleteTexture( &texture[i] );
}
}
// Delete any RC's - should be done by ~THREAD, but since common lib
// deletes shareRC, have to do it here
DRAW_THREAD *pdt = &drawThreads[0];
for( int i = 0; i < MAX_DRAW_THREADS; i ++, pdt++ ) {
if( pdt->hglrc && (pdt->hglrc != shareRC) ) {
wglDeleteContext( pdt->hglrc );
}
}
}
/******************************Public*Routine******************************\
* CalcTexRepFactors
*
\**************************************************************************/
void
STATE::CalcTexRepFactors()
{
ISIZE winSize;
POINT2D texFact;
ss_GetScreenSize( &winSize );
// Figure out repetition factor of texture, based on bitmap size and
// screen size.
//
// We arbitrarily decide to repeat textures that are smaller than
// 1/8th of screen width or height.
for( int i = 0; i < nTextures; i++ ) {
texRep[i].x = texRep[i].y = 1;
if( (texFact.x = winSize.width / texture[i].width / 8.0f) >= 1.0f)
texRep[i].x = (int) (texFact.x+0.5f);
if( (texFact.y = winSize.height / texture[i].height / 8.0f) >= 1.0f)
texRep[i].y = (int) (texFact.y+0.5f);
}
// ! If display list based normal pipes, texture repetition is embedded
// in the dlists and can't be changed. So use the smallest rep factors.
if( pNState ) {
//put smallest rep factors in texRep[0]; (mf:this is ok for now, as
// flex pipes and normal pipes don't coexist)
for( i = 1; i < nTextures; i++ ) {
if( texRep[i].x < texRep[0].x )
texRep[0].x = texRep[i].x;
if( texRep[i].y < texRep[0].y )
texRep[0].y = texRep[i].y;
}
}
}
/******************************Public*Routine******************************\
* LoadTextureFiles
*
* - Load user texture files. If texturing on but no user textures, or
* problems loading them, load default texture resource
*
\**************************************************************************/
BOOL
STATE::LoadTextureFiles( TEXFILE *pTexFile, int nTexFiles, TEX_RES *pTexRes )
{
// Set pixel store state
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Try to load the bmp or rgb file
// i counts successfully loaded textures
for( int i = 0; nTexFiles; nTexFiles-- ) {
if( ss_LoadTextureFile( &pTexFile[i], &texture[i] ) ) {
// If texture object extension, set tex params here for each object
if( ss_TextureObjectsEnabled() )
InitTexParams();
i++; // count another valid texture
}
}
// set number of valid textures in state
nTextures = i;
if( nTextures == 0 ) {
// No user textures, or none loaded successfully
// Load default resource texture(s)
nTextures = DEF_TEX_COUNT;
for( i = 0; i < nTextures; i++, pTexRes++ ) {
if( !ss_LoadTextureResource( pTexRes, &texture[i] ) ) {
// shouldn't happen
return FALSE;
}
}
}
CalcTexRepFactors();
return TRUE;
}
/******************************Public*Routine******************************\
* GLInit
*
* - Sets up GL state
* - Called once for every context (rc)
*
\**************************************************************************/
void
STATE::GLInit()
{
static float ambient[] = {0.1f, 0.1f, 0.1f, 1.0f};
static float diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
static float position[] = {90.0f, 90.0f, 150.0f, 0.0f};
static float lmodel_ambient[] = {1.0f, 1.0f, 1.0f, 1.0f};
static float lmodel_ambientTex[] = {0.6f, 0.6f, 0.6f, 0.0f};
static float back_mat_diffuse[] = {0.0f, 0.0f, 1.0f};
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glFrontFace(GL_CCW);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
glEnable( GL_AUTO_NORMAL ); // needed for GL_MAP2_VERTEX (tea)
if( bTexture )
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambientTex);
else
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
// Set texture modes
if( bTexture ) {
glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
InitTexParams();
}
}
/**************************************************************************\
* InitTexParams
*
* Set texture parameters, globally, or per object if texture object extension
*
\**************************************************************************/
static void
InitTexParams()
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
switch( ulTexQuality ) {
case TEXQUAL_HIGH:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
break;
case TEXQUAL_DEFAULT:
default:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
break;
}
}
/**************************************************************************\
* Repaint
*
* This is called when a WM_PAINT msg has been sent to the window. The paint
* will overwrite the frame buffer, screwing up the scene if pipes is in single
* buffer mode. We set resetStatus accordingly to clear things up on next
* draw.
*
\**************************************************************************/
void
STATE::Repaint( LPRECT pRect, void *data)
{
resetStatus |= RESET_REPAINT_BIT;
}
/**************************************************************************\
* Reshape
* - called on resize, expose
* - always called on app startup
* - set new window size for VIEW object, and set resetStatus for validation
* at draw time
*
\**************************************************************************/
void
STATE::Reshape(int width, int height, void *data)
{
if( view.SetWinSize( width, height ) )
resetStatus |= RESET_RESIZE_BIT;
}
/**************************************************************************\
* ResetView
*
* Called on FrameReset resulting from change in viewing paramters (e.g. from
* a Resize event).
\**************************************************************************/
void
STATE::ResetView()
{
IPOINT3D numNodes;
// Have VIEW calculate the node array size based on view params
view.CalcNodeArraySize( &numNodes );
// Resize the node array
nodes->Resize( &numNodes );
// Set GL viewing parameters for each active RC
DRAW_THREAD *pThread = drawThreads;
for( int i = 0; i < MAX_DRAW_THREADS; i ++, pThread++ ) {
if( pThread->HasRC() ) {
pThread->MakeRCCurrent();
view.SetGLView();
}
}
}
/**************************************************************************\
* FrameReset
*
* Start a new frame of pipes
*
* The resetStatus parameter indicates what triggered the Reset.
*
\**************************************************************************/
static int PickRandomTexture( int i, int nTextures );
void
STATE::FrameReset()
{
int i;
float xRot, zRot;
PIPE *pNewPipe;
#ifdef DO_TIMING
Timer( TIMER_STOP );
#endif
SS_DBGINFO( "Pipes STATE::FrameReset:\n" );
// Kill off any active pipes ! (so they can shut down ok)
DRAW_THREAD *pThread = drawThreads;
for( i = 0; i < nDrawThreads; i ++, pThread++ ) {
pThread->KillPipe();
}
nDrawThreads = 0;
// Clear the screen
Clear();
// Check for window resize status
if( resetStatus & RESET_RESIZE_BIT ) {
ResetView();
}
// Reset the node states to empty
nodes->Reset();
// Call any pipe-specific state resets, and get any recommended
// pipesPerFrame counts
if( pNState ) {
pNState->Reset();
}
if( pFState ) {
pFState->Reset();
xRot = ss_fRand(-5.0f, 5.0f);
zRot = ss_fRand(-5.0f, 5.0f);
}
maxPipesPerFrame = CalcMaxPipesPerFrame();
// Set new number of drawing threads
if( maxDrawThreads > 1 ) {
// Set maximum # of pipes per frame
maxPipesPerFrame = (int) (maxPipesPerFrame * 1.5);
// Set # of draw threads
nDrawThreads = SS_MIN( maxPipesPerFrame, ss_iRand2( 2, maxDrawThreads ) );
// Set chase mode if applicable, every now and then
BOOL bUseChase = pNState || (pFState && pFState->OKToUseChase());
if( bUseChase && (!ss_iRand(5)) ) {
drawScheme = FRAME_SCHEME_CHASE;
}
} else {
nDrawThreads = 1;
}
nPipesDrawn = 0;
// for now, either all NORMAL or all FLEX for each frame
pThread = drawThreads;
for( i = 0; i < nDrawThreads; i ++, pThread++ ) {
// Create hglrc if necessary, and init it
if( !pThread->HasRC() ) {
HDC hdc = wglGetCurrentDC();
pThread->SetRCDC( wglCreateContext( hdc ), hdc );
// also need to init each RC
pThread->MakeRCCurrent();
// Do GL Init for this new RC
GLInit();
// Set viewing params
view.SetGLView();
// Give this rc access to any dlists
wglShareLists( shareRC, pThread->GetRC() );
}
else
pThread->MakeRCCurrent();
// Set up the modeling view
glLoadIdentity();
glTranslatef(0.0f, 0.0f, view.zTrans);
// Rotate Scene
glRotatef( view.yRot, 0.0f, 1.0f, 0.0f );
// create approppriate pipe for this thread slot
switch( drawMode ) {
case DRAW_NORMAL:
pNewPipe = (PIPE *) new NORMAL_PIPE(this);
break;
case DRAW_FLEX:
// There are several kinds of FLEX pipes - have FLEX_STATE
// decide which one to create
pNewPipe = pFState->NewPipe( this );
// rotate a bit around x and z as well
// If combining NORMAL and FLEX, same rotations must be
// applied to both
glRotatef( xRot, 1.0f, 0.0f, 0.0f );
glRotatef( zRot, 0.0f, 0.0f, 1.0f );
break;
}
pThread->SetPipe( pNewPipe );
if( drawScheme == FRAME_SCHEME_CHASE ) {
if( i == 0 ) {
// this will be the lead pipe
pLeadPipe = pNewPipe;
pNewPipe->SetChooseDirectionMethod( CHOOSE_DIR_RANDOM_WEIGHTED );
} else {
pNewPipe->SetChooseDirectionMethod( CHOOSE_DIR_CHASE );
}
}
// If texturing, pick a random texture for this thread
if( bTexture ) {
int index = PickRandomTexture( i, nTextures );
pThread->SetTexture( &texture[index] );
// Flex pipes need to be informed of the texture, so they
// can dynamically calculate various texture params
if( pFState )
((FLEX_PIPE *) pNewPipe)->SetTexParams( &texture[index],
&texRep[index] );
}
// Launch the pipe (assumed: always more nodes than pipes starting, so
// StartPipe cannot fail)
// ! All pipe setup needs to be done before we call StartPipe, as this
// is where the pipe starts drawing
pThread->StartPipe();
if( (i == 0) && (drawScheme == FRAME_SCHEME_CHASE) )
pNewPipe->SetChooseStartPosMethod( CHOOSE_STARTPOS_FURTHEST );
nPipesDrawn++;
}
// Increment scene rotation for normal reset case
if( resetStatus & RESET_NORMAL_BIT )
view.IncrementSceneRotation();
// clear reset status
resetStatus = 0;
#ifdef DO_TIMING
Timer( TIMER_START );
#endif
}
/**************************************************************************\
* CalcMaxPipesPerFrame
*
\**************************************************************************/
int
STATE::CalcMaxPipesPerFrame()
{
int nCount=0, fCount=0;
if( pFState )
fCount = pFState->GetMaxPipesPerFrame();
if( pNState )
nCount = bTexture ? NORMAL_TEX_PIPE_COUNT : NORMAL_PIPE_COUNT;
return SS_MAX( nCount, fCount );
}
/**************************************************************************\
* PickRandomTexture
*
* Pick a random texture index from a list. Remove entry from list as it
* is picked. Once all have been picked, or starting a new frame, reset.
*
* ! Routine not reentrant, should only be called by the main thread
* dispatcher (FrameReset)
\**************************************************************************/
static int
PickRandomTexture( int iThread, int nTextures )
{
if( nTextures == 0 )
return 0;
static int pickSet[MAX_TEXTURES] = {0};
static int nPicked = 0;
int i, index;
if( iThread == 0 )
// new frame - force reset
nPicked = nTextures;
// reset condition
if( ++nPicked > nTextures ) {
for( i = 0; i < nTextures; i ++ ) pickSet[i] = 0;
nPicked = 1; // cuz
}
// Pick a random texture index
index = ss_iRand( nTextures );
while( pickSet[index] ) {
// this index has alread been taken, try the next one
if( ++index >= nTextures )
index = 0;
}
// Hopefully, the above loop will exit :). This means that we have
// found a texIndex that is available
pickSet[index] = 1; // mark as taken
return index;
}
/**************************************************************************\
* Clear
*
* Clear the screen. Depending on resetStatus, use normal clear or
* fancy transitional clear.
\**************************************************************************/
void
STATE::Clear()
{
// clear the screen - any rc will do
glClear(GL_DEPTH_BUFFER_BIT);
if( resetStatus & RESET_RESIZE_BIT ) {
// new window size - recalibrate the transitional clear
// Calibration is set after a window resize, so window is already black
ddClear.CalibrateClear( view.winSize.width, view.winSize.height, 2.0f );
} else if( resetStatus & RESET_NORMAL_BIT )
// do the normal transitional clear
ddClear.Clear( view.winSize.width, view.winSize.height );
else {
// do a fast one-shot clear
glClear( GL_COLOR_BUFFER_BIT );
}
}
/**************************************************************************\
* DrawValidate
*
* Validation done before every Draw
*
* For now, this just involves checking resetStatus
*
\**************************************************************************/
void
STATE::DrawValidate()
{
if( ! resetStatus )
return;
FrameReset();
}
/**************************************************************************\
* Draw
*
* - Top-level pipe drawing routine
* - Each pipe thread keeps drawing new pipes until we reach maximum number
* of pipes per frame - then each thread gets killed as soon as it gets
* stuck. Once number of drawing threads reaches 0, we start a new
* frame
*
\**************************************************************************/
void
STATE::Draw(void *data)
{
int nKilledThreads = 0;
BOOL bChooseNewLead = FALSE;
// Validate the draw state
DrawValidate();
// Check each pipe's status
DRAW_THREAD *pThread = drawThreads;
for( int i = 0; i < nDrawThreads; i++, pThread++ ) {
if( pThread->pPipe->IsStuck() ) {
if( ++nPipesDrawn > maxPipesPerFrame ) {
// Reaching pipe saturation - kill this pipe thread
if( (drawScheme == FRAME_SCHEME_CHASE) &&
(pThread->pPipe == pLeadPipe) )
bChooseNewLead = TRUE;
pThread->KillPipe();
nKilledThreads++;
} else {
// Start up another pipe
if( ! pThread->StartPipe() )
// we won't be able to draw any more pipes this frame
// (probably out of nodes)
maxPipesPerFrame = nPipesDrawn;
}
}
}
// Whenever one or more pipes are killed, compact the thread list
if( nKilledThreads ) {
CompactThreadList();
nDrawThreads -= nKilledThreads;
}
if( nDrawThreads == 0 ) {
// This frame is finished - mark for reset on next Draw
resetStatus |= RESET_NORMAL_BIT;
return;
}
if( bChooseNewLead ) {
// We're in 'chase mode' and need to pick a new lead pipe
ChooseNewLeadPipe();
}
// Draw each pipe
for( i = 0, pThread = drawThreads; i < nDrawThreads; i++, pThread++ ) {
pThread->DrawPipe();
#ifdef DO_TIMING
pipeCount++;
#endif
}
glFlush();
}
/**************************************************************************\
*
* CompactThreadList
*
* - Compact the thread list according to number of pipe threads killed
* - The pipes have been killed, but the RC's in each slot are still valid
* and reusable. So we swap up entries with valid pipes. This means that
* the ordering of the RC's in the thread list will change during the life
* of the program. This should be OK.
*
\**************************************************************************/
#define SWAP_SLOT( a, b ) \
DRAW_THREAD pTemp; \
pTemp = *(a); \
*(a) = *(b); \
*(b) = pTemp;
void
STATE::CompactThreadList()
{
if( nDrawThreads <= 1 )
// If only one active thread, it must be in slot 0 from previous
// compactions - so nothing to do
return;
int iEmpty = 0;
DRAW_THREAD *pThread = drawThreads;
for( int i = 0; i < nDrawThreads; i ++, pThread++ ) {
if( pThread->pPipe ) {
if( iEmpty < i ) {
// swap active pipe thread and empty slot
SWAP_SLOT( &(drawThreads[iEmpty]), pThread );
}
iEmpty++;
}
}
}
/**************************************************************************\
*
* ChooseNewLeadPipe
*
* Choose a new lead pipe for chase mode.
*
\**************************************************************************/
void
STATE::ChooseNewLeadPipe()
{
// Pick one of the active pipes at random to become the new lead
int iLead = ss_iRand( nDrawThreads );
pLeadPipe = drawThreads[iLead].pPipe;
pLeadPipe->SetChooseStartPosMethod( CHOOSE_STARTPOS_FURTHEST );
pLeadPipe->SetChooseDirectionMethod( CHOOSE_DIR_RANDOM_WEIGHTED );
}
/******************************Public*Routine******************************\
* Finish
*
* - Called when GL window being closed
*
\**************************************************************************/
void
STATE::Finish( void *data )
{
delete (STATE *) data;
}
/**************************************************************************\
* DRAW_THREAD constructor
*
\**************************************************************************/
DRAW_THREAD::DRAW_THREAD()
{
hdc = 0;
hglrc = 0;
pPipe = NULL;
htex = (HTEXTURE) -1;
}
/**************************************************************************\
* DRAW_THREAD destructor
*
* Delete any GL contexts
*
* - can't Delete shareRC, as this is done by common lib, so had to move
* this up to ~STATE
*
\**************************************************************************/
DRAW_THREAD::~DRAW_THREAD()
{
}
/**************************************************************************\
* MakeRCCurrent
*
\**************************************************************************/
void
DRAW_THREAD::MakeRCCurrent()
{
if( hglrc != wglGetCurrentContext() )
wglMakeCurrent( hdc, hglrc );
}
/**************************************************************************\
* SetRCDC
*
\**************************************************************************/
void
DRAW_THREAD::SetRCDC( HGLRC rc, HDC Hdc )
{
hglrc = rc;
hdc = Hdc;
}
/**************************************************************************\
* SetPipe
*
\**************************************************************************/
void
DRAW_THREAD::SetPipe( PIPE *pipe )
{
pPipe = pipe;
}
/**************************************************************************\
* HasRC
*
\**************************************************************************/
BOOL
DRAW_THREAD::HasRC()
{
return( hglrc != 0 );
}
/**************************************************************************\
* GetRC
*
\**************************************************************************/
HGLRC
DRAW_THREAD::GetRC()
{
return hglrc;
}
/**************************************************************************\
* SetTexture
*
* - Set a texture for a thread
* - Cache the texture index for performance
\**************************************************************************/
void
DRAW_THREAD::SetTexture( HTEXTURE hnewtex )
{
if( hnewtex != htex )
{
htex = hnewtex;
ss_SetTexture( htex );
}
}
/**************************************************************************\
* DrawPipe
*
* - Draw pipe in thread slot, according to its type
*
\**************************************************************************/
void
DRAW_THREAD::DrawPipe()
{
MakeRCCurrent();
switch( pPipe->type ) {
case TYPE_NORMAL:
( (NORMAL_PIPE *) pPipe )->Draw();
break;
case TYPE_FLEX_REGULAR:
( (REGULAR_FLEX_PIPE *) pPipe )->Draw();
break;
case TYPE_FLEX_TURNING:
( (TURNING_FLEX_PIPE *) pPipe )->Draw();
break;
}
glFlush();
}
/**************************************************************************\
* StartPipe
*
* Starts up pipe of the approppriate type. If can't find an empty node
* for the pipe to start on, returns FALSE;
*
\**************************************************************************/
BOOL
DRAW_THREAD::StartPipe()
{
MakeRCCurrent();
// call pipe-type specific Start function
switch( pPipe->type ) {
case TYPE_NORMAL:
( (NORMAL_PIPE *) pPipe )->Start();
break;
case TYPE_FLEX_REGULAR:
( (REGULAR_FLEX_PIPE *) pPipe )->Start();
break;
case TYPE_FLEX_TURNING:
( (TURNING_FLEX_PIPE *) pPipe )->Start();
break;
}
glFlush();
// check status
if( pPipe->NowhereToRun() )
return FALSE;
else
return TRUE;
}
/**************************************************************************\
* KillPipe
*
\**************************************************************************/
void
DRAW_THREAD::KillPipe()
{
switch( pPipe->type ) {
case TYPE_NORMAL:
delete (NORMAL_PIPE *) pPipe;
break;
case TYPE_FLEX_REGULAR:
delete (REGULAR_FLEX_PIPE *) pPipe;
break;
case TYPE_FLEX_TURNING:
delete (TURNING_FLEX_PIPE *) pPipe;
break;
}
pPipe = NULL;
}