/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmmtview.c,v 1.13 2008/11/23 15:12:48 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.13 $
 * $Log: rmmtview.c,v $
 * Revision 1.13  2008/11/23 15:12:48  wes
 * Changes to support RMnode's use of an RMarray to hold children
 *
 * Revision 1.12  2006/08/02 04:58:19  wes
 * Check for NULL mpdl in private_rmPipeDisplayListsDelete.
 *
 * Revision 1.11  2005/06/26 18:53:48  wes
 * Consolidated opcode to capture both local state push/pop and OpenGL
 * attrib stack push/pop. This move helps streamline state tracking and
 * makes it possible to better track OpenGL state.
 *
 * Revision 1.10  2005/06/06 02:04:29  wes
 * Lots of small additions to clean up compiler warnings.
 *
 * Revision 1.9  2005/03/16 16:44:42  wes
 * Added node index to certain opcodes to support better debug output.
 *
 * Revision 1.8  2005/02/24 16:17:25  wes
 * Added support for fbClears to be set at the RMpipe level. Apps can
 * still set them at the RMnode level if desired for fine-grained control.
 *
 * Revision 1.7  2005/02/19 16:22:50  wes
 * Distro sync and consolidation.
 *
 * Revision 1.6  2005/01/23 17:04:03  wes
 * Copyright update to 2005.
 *
 * Revision 1.5  2004/01/16 16:46:09  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.4  2003/04/12 21:02:00  wes
 * Undo of movement of RMpipe view-render buffer init back into rendering
 * path, and out of rmPipeNew. The init code can't be called from rmPipeNew
 * because the init code depends upon the channel format, which is usually
 * set by the app after rmPipeNew.
 *
 * Revision 1.3  2003/04/05 14:08:12  wes
 * Add code to remove inter-stage buffers, which is invoked vis a vis rmFinish().
 *
 * Revision 1.2  2003/02/02 02:07:15  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.9  2003/01/16 22:21:17  wes
 * Updated all source files to reflect new organization of header files:
 * all header files formerly located in include/rmaux, include/rmi, include/rmv
 * are now located in include/rm.
 *
 * Revision 1.8  2002/09/05 15:06:18  wes
 * tweaks to how sizes for display list indices and opcodes are
 * computed during the init phase. these now use the default page
 * size, which is probably not the correct thing. these changes were
 * made during a frenzy of realloc code testing.
 *
 * Revision 1.7  2002/08/29 22:20:32  wes
 *
 * Massive upgrade to accommodate dynamic object reallocation within
 * the component manager, and within the context cache. Use the
 * debug #define DEBUG_LEVEL DEBUG_REALLOC_TRACE to get a printf
 * whenever a realloc occurs. With this upgrade, there are no
 * OpenRM limits on the size of the scene graph. There will be external
 * limits, such as the amount of RAM and the amount of space available
 * to your OpenGL implementation.
 *
 * Revision 1.6  2002/08/17 15:11:43  wes
 * Added machinery to invoke user-defined pre- and post-traversal callbacks
 * in the view traversal.
 *
 * Revision 1.5  2002/06/17 01:00:10  wes
 * Doubled the amount of space allocated for render stage opcodes.
 * This change was motivated by bug reports from a user who has extremely
 * large scene graphs where many nodes contains transformations. The
 * remaining problem is to accurately compute the amount of opcode
 * space required for a given SG configuration. The number of opcodes required
 * for each node varies as a function of the contents of its scene
 * parameters, transformations, etc.
 *
 * Revision 1.4  2002/04/30 19:32:47  wes
 * Updated copyright dates.
 *
 * Revision 1.3  2001/10/15 00:09:03  wes
 * Removed some dead code.
 *
 * Revision 1.2  2001/06/03 20:47:28  wes
 * Removed unused vars to get rid of compile warnings.
 *
 * Revision 1.1  2001/03/31 17:12:39  wes
 * v1.4.0-alpha-2 checkin.
 *
 */

#include <rm/rm.h>
#include "rmmultis.h"
#include "rmprivat.h"

#define MODELVIEW_MATRIX_CHANGED	1
#define PROJECTION_MATRIX_CHANGED	2
#define TEXTURE_MATRIX_CHANGED		4

static void private_rmViewRecurse(RMpipe *p, RMnode *n, int (*nodeRenderPassFunc)(const RMnode *), int (*channelTraversalFunc)(const RMnode *), RMdisplayList *dl, RMstate *rState);


RMdisplayList *
private_rmPipeDisplayListNew(int initNumOpCodes,
			     int initNumIndices,
			     int initNumMatrices)
{
    RMdisplayList *t;

    t = (RMdisplayList *)malloc(sizeof(RMdisplayList));
    memset(t,0,sizeof(RMdisplayList));

    t->opCodes = (RMdisplayListOpcode *)malloc(sizeof(RMdisplayListOpcode) * initNumOpCodes);
    t->nOpCodes = 0;
    t->maxOpCodes = initNumOpCodes;

    t->indices = (int *)malloc(sizeof(int)*initNumIndices);
    t->nIndices = 0;
    t->maxIndices = initNumIndices;

    t->matrices = (RMmatrix *)malloc(sizeof(RMmatrix)*initNumMatrices);
    t->nMatrices = 0;
    t->maxMatrices = initNumMatrices;

    return(t);
}

void
private_rmPipeDisplayListDelete(RMdisplayList *t)
{
    free((void *)(t->opCodes));
    free((void *)(t->indices));
    free((void *)(t->matrices));
    free((void *)t);
}

/* PRIVATE */
/*
 * Generate structures that are used to pass information between the
 * view-stage and render-stage traversals. These structures are NOT
 * OpenGL display lists. They are "real" display lists in the
 * traditional use of the term.
 */
void
private_rmPipeDisplayListsNew(RMpipe *p)
{
    int initNumNodes;
    int initNumMatrices = NUM_ITEMS_PER_PAGE;
    RMmultipassDisplayList *mpdl;
    
    /*
     * build initial render buffers.
     */
    
    initNumNodes = initNumMatrices = NUM_ITEMS_PER_PAGE;
    
    /* right now, we're creating only a single multipass
       display list buffer. we'll need a second one if doing stereo.
       each multipass display list consists of double-buffers for
       results of multiple rendering passes: 3D opaque, 3d
       transparent, and 2d opaque */

    mpdl = (RMmultipassDisplayList *)malloc(sizeof(RMmultipassDisplayList));

    mpdl->globals[0] = private_rmPipeDisplayListNew(initNumNodes,
						     initNumNodes,
						     initNumMatrices);

    mpdl->globals[1] = private_rmPipeDisplayListNew(initNumNodes,
						     initNumNodes,
						     initNumMatrices);

    mpdl->opaque3D[0] = private_rmPipeDisplayListNew(initNumNodes,
						     initNumNodes,
						     initNumMatrices);

    mpdl->opaque3D[1] = private_rmPipeDisplayListNew(initNumNodes,
						     initNumNodes,
						     initNumMatrices);

    mpdl->transparent3D[0] = private_rmPipeDisplayListNew(initNumNodes,
							  initNumNodes,
							  initNumMatrices);

    mpdl->transparent3D[1] = private_rmPipeDisplayListNew(initNumNodes,
							  initNumNodes,
							  initNumMatrices);

    mpdl->opaque2D[0] = private_rmPipeDisplayListNew(initNumNodes,
						     initNumNodes,
						     initNumMatrices);

    mpdl->opaque2D[1] = private_rmPipeDisplayListNew(initNumNodes,
						     initNumNodes,
						     initNumMatrices);

    if (p->channel_format != RM_MONO_CHANNEL) /* assume a stereo format */
    {
	mpdl->rightGlobals[0] = private_rmPipeDisplayListNew(initNumNodes,
							      initNumNodes,
							      initNumMatrices);
	
	mpdl->rightGlobals[1] = private_rmPipeDisplayListNew(initNumNodes,
							      initNumNodes,
							      initNumMatrices);
	
	mpdl->rightOpaque3D[0] = private_rmPipeDisplayListNew(initNumNodes,
							      initNumNodes,
							      initNumMatrices);
	
	mpdl->rightOpaque3D[1] = private_rmPipeDisplayListNew(initNumNodes,
							      initNumNodes,
							      initNumMatrices);
	
	mpdl->rightTransparent3D[0] = private_rmPipeDisplayListNew(initNumNodes,
								   initNumNodes,
								   initNumMatrices);
	
	mpdl->rightTransparent3D[1] = private_rmPipeDisplayListNew(initNumNodes,
								   initNumNodes,
								   initNumMatrices);
	
	mpdl->rightOpaque2D[0] = private_rmPipeDisplayListNew(initNumNodes,
							      initNumNodes,
							      initNumMatrices);
	
	mpdl->rightOpaque2D[1] = private_rmPipeDisplayListNew(initNumNodes,
							      initNumNodes,
							      initNumMatrices);
    }

    p->displayLists = (void *)mpdl;
}

/* PRIVATE */
void
private_rmPipeDisplayListsDelete(RMpipe *p)
{
    RMmultipassDisplayList *mpdl;
    
    mpdl = (RMmultipassDisplayList *)(p->displayLists);

    if (mpdl == NULL)
	return;

    private_rmPipeDisplayListDelete(mpdl->globals[0]);
    private_rmPipeDisplayListDelete(mpdl->globals[1]);
    private_rmPipeDisplayListDelete(mpdl->opaque3D[0]);
    private_rmPipeDisplayListDelete(mpdl->opaque3D[1]);
    private_rmPipeDisplayListDelete(mpdl->transparent3D[0]);
    private_rmPipeDisplayListDelete(mpdl->transparent3D[1]);
    private_rmPipeDisplayListDelete(mpdl->opaque2D[0]);
    private_rmPipeDisplayListDelete(mpdl->opaque2D[1]);

    if (p->channel_format != RM_MONO_CHANNEL) /* assume a stereo format */
    {
	private_rmPipeDisplayListDelete(mpdl->rightGlobals[0]);
	private_rmPipeDisplayListDelete(mpdl->rightGlobals[1]);
	
	private_rmPipeDisplayListDelete(mpdl->rightOpaque3D[0]);
	private_rmPipeDisplayListDelete(mpdl->rightOpaque3D[1]);
	
	private_rmPipeDisplayListDelete(mpdl->rightTransparent3D[0]);
	private_rmPipeDisplayListDelete(mpdl->rightTransparent3D[1]);
	private_rmPipeDisplayListDelete(mpdl->rightOpaque2D[0]);
	private_rmPipeDisplayListDelete(mpdl->rightOpaque2D[1]);
    }

    free((void *)mpdl);
    p->displayLists = NULL;
}

/* PRIVATE */
void
private_rmPipeDisplayListsInit(RMpipe *p,
			       int i) /* even/odd buffer */
{
    /* reset all counters in pipe mt buffers to zero. */
    RMdisplayList *t;
    RMmultipassDisplayList *mp;

    mp = (RMmultipassDisplayList *)(p->displayLists);

    t = mp->globals[i];
    t->nOpCodes = 0;
    t->nIndices = 0;
    t->nMatrices = 0;
    
    t = mp->opaque3D[i];
    t->nOpCodes = 0;
    t->nIndices = 0;
    t->nMatrices = 0;
    
    t = mp->transparent3D[i];
    t->nOpCodes = 0;
    t->nIndices = 0;
    t->nMatrices = 0;

    t = mp->opaque2D[i];
    t->nOpCodes = 0;
    t->nIndices = 0;
    t->nMatrices = 0;
    
    if (rmPipeGetChannelFormat(p) != RM_MONO_CHANNEL)
    {
	t = mp->rightGlobals[i];
	t->nOpCodes = 0;
	t->nIndices = 0;
	t->nMatrices = 0;
	
	t = mp->rightOpaque3D[i];
	t->nOpCodes = 0;
	t->nIndices = 0;
	t->nMatrices = 0;
	
	t = mp->rightTransparent3D[i];
	t->nOpCodes = 0;
	t->nIndices = 0;
	t->nMatrices = 0;
	
	t = mp->rightOpaque2D[i];
	t->nOpCodes = 0;
	t->nIndices = 0;
	t->nMatrices = 0;
    }
}

static int
leftChannelTraversalFunc (const RMnode *r)
{
    /* this filterfunc will return 1 if the node "r" is scheduled for
       traversal during the "left channel" OR "all channels" traversal */

    RMenum stat = private_rmNodeGetTraversalMaskChannel(r);
    
    if ((stat == RM_ALL_CHANNELS) || (stat == RM_LEFT_CHANNEL))
	return(1);
    else
	return(0);
}

static int
rightChannelTraversalFunc (const RMnode *r)
{
    /* this filterfunc will return 1 if the node "r" is scheduled for
       traversal during the "right channel" OR "all channels" traversal */

    RMenum stat = private_rmNodeGetTraversalMaskChannel(r);
    
    if ((stat == RM_ALL_CHANNELS) || (stat == RM_RIGHT_CHANNEL))
	return(1);
    else
	return(0);
}

static int
opaque3DFilterFunc (const RMnode *r)
{
    /* this filterfunc will return 1 if the node "r" has 3D locations and is op
aque */

    RMenum vdims, opacity;

    vdims = private_rmNodeGetTraversalMaskDims(r);
    opacity = private_rmNodeGetTraversalMaskOpacity(r);

    if (((vdims == RM_RENDERPASS_3D) || (vdims == RM_RENDERPASS_ALL)) &&
	((opacity == RM_RENDERPASS_OPAQUE) || (opacity == RM_RENDERPASS_ALL)))
	return(1);
    else
	return(0);
}

static int
transparent3DFilterFunc (const RMnode *r)
{
    /* this filterfunc will return 1 if the node "r" has 3D locations and is op
aque */

    RMenum vdims, opacity;

    vdims = private_rmNodeGetTraversalMaskDims(r);
    opacity = private_rmNodeGetTraversalMaskOpacity(r);

    if (((vdims == RM_RENDERPASS_3D) || (vdims == RM_RENDERPASS_ALL)) &&
	((opacity == RM_RENDERPASS_TRANSPARENT) || (opacity == RM_RENDERPASS_ALL)))
	return(1);
    else
	return(0);
}

static int
opaque2DFilterFunc (const RMnode *r)
{
    /* this filterfunc will return 1 if the node "r" has 2D locations and is op
aque */

    RMenum vdims, opacity;

    vdims = private_rmNodeGetTraversalMaskDims(r);
    opacity = private_rmNodeGetTraversalMaskOpacity(r);

    if (((vdims == RM_RENDERPASS_2D) || (vdims == RM_RENDERPASS_ALL)) &&
	((opacity == RM_RENDERPASS_OPAQUE) || (opacity == RM_RENDERPASS_ALL)))
	return(1);
    else
	return(0);
}

static void
private_rmDLloadOpcode(RMdisplayListOpcode opcode,
		       int inputIndx,
		       RMdisplayList *mt)
{
    int indx;

    indx = mt->nOpCodes;
    if (indx+1 >= mt->maxOpCodes) /* need to realloc */
    {
	int oldSize, newSize;
	oldSize = mt->maxOpCodes;
	newSize = oldSize + NUM_ITEMS_PER_PAGE;
	mt->opCodes = (RMdisplayListOpcode *)realloc(mt->opCodes, newSize * sizeof(RMdisplayListOpcode));
	mt->maxOpCodes += NUM_ITEMS_PER_PAGE;
	
#if (DEBUG_LEVEL & DEBUG_REALLOC_TRACE)
	printf("private_rmDLloadOpcode() realloc of opcode space. oldSize=%d, newSize = %d. \n", oldSize, newSize);
#endif
	
#if 0
	rmError("private_rmDLloadOpcode() error: out of opcode space! Need to write realloc code! ");
	return;
#endif
    }

    mt->opCodes[indx] = opcode;
    mt->nOpCodes += 1;

    /* note: t->nIndices should be the same as t->nOpCodes !! */
    indx = mt->nIndices;
    
    if (indx+1 >= mt->maxIndices) /* need to realloc */
    {
	int oldSize, newSize;
	oldSize = mt->maxIndices;
	newSize = oldSize + NUM_ITEMS_PER_PAGE;
	mt->indices = (int *)realloc(mt->indices, newSize * sizeof(int));
	mt->maxIndices += NUM_ITEMS_PER_PAGE;
#if (DEBUG_LEVEL & DEBUG_REALLOC_TRACE)
	printf("private_rmDLloadOpcode() realloc of index space. oldSize=%d, newSize = %d. \n", oldSize, newSize);
#endif
	
    }
    mt->indices[indx] = inputIndx;
    mt->nIndices += 1;
}

static int
private_rmDLnextMatrix(RMdisplayList *mt)
{
    if ((mt->nMatrices + 1) >= mt->maxMatrices)
    {
	int oldSize, newSize;
	
	oldSize = mt->maxMatrices;
	newSize = oldSize + NUM_ITEMS_PER_PAGE;
	mt->matrices = (RMmatrix *)realloc(mt->matrices, newSize * sizeof(RMmatrix));
	mt->maxMatrices += NUM_ITEMS_PER_PAGE;
	
#if (DEBUG_LEVEL & DEBUG_REALLOC_TRACE)
	printf("private_rmDLnextMatrix() - reallocating matrix space; oldValue = %d, newValue = %d \n", oldSize, newSize);
#endif
	
#if 0
	/* old code */
	rmError("private_rmDLnextMatrix() error: out of matrix space! Need to write realloc code! ");
	return(mt->maxMatrices - 1);
#endif
	
    }
    return(mt->nMatrices);
}

static void
private_rmDLloadMatrix(RMdisplayList *mt,
		       int indx,
		       RMmatrix *src)
{
    rmMatrixCopy(mt->matrices + indx, src);
    mt->nMatrices += 1;
}

static int
private_rmDLcollectTransformations(RMpipe *p,
				   RMnode *n,
				   RMstate *rState)
{
    RMmatrix model, view, proj;
    RMmatrix oglModelView;
    int returnStatus;
    int viewMatrixChange=0, textureMatrixChange=0, modelMatrixChange=0;
    
    /*
     * this routine will determine what changes to the matrix stack,
     * if any, are dictated by parms at this node. 
     *
     * note that RM maintains separate view, model & projection
     * matrices in the RMstate object.
     *
     * the return value from this routine is the bitwise OR of the following:
     * MODELVIEW_MATRIX_CHANGED,PROJECTION_MATRIX_CHANGED  and
     * TEXTURE_MATRIX_CHANGED. each of these bits indicates which of
     * corresponding OpenGL matrix stacks was modified by this routine. a
     * return value of zero means that no matrix stacks were modified.
     *
     * the viewport is processed here, rather than later, because the
     * camera routines have occasion to use the current viewport.
     *
     */
    if (n->scene_parms && n->scene_parms->viewport)	/* do viewport */
    /* viewport needed for pick matrix */
	private_setViewport(n, rState, RM_FALSE, RM_FALSE);

    
    if (n->scene_parms && n->scene_parms->camera3d)
    {
	RMcamera3D tmp;

	/* stereo disabled for now */
	if ((rState->which_channel != RM_ALL_CHANNELS) && (rmCamera3DGetStereo(n->scene_parms->camera3d) == RM_TRUE))
	{
	    /*
	     * for binocular stereo, we compute a new camera model for
	     * the left or right eye, then use that modified camera in
	     * the derivation of a view matrix.
	     */
	    private_computeStereoOffset(n->scene_parms->camera3d, rState->which_channel, &tmp);
	}
	else
	    tmp = *(n->scene_parms->camera3d);
	
	rmCamera3DComputeViewMatrix(&tmp, &view, &proj);
#if 0
	/* picking disabled for now in multistage renderer */
	if (renderMode == GL_SELECT)
	{
	    RMmatrix pick;
	    private_rmComputePickMatrix(rState, &pick);
	    rState->pick = pick;
	    private_drawCameraPickableQuad(perObjFunc, n);
	}
#endif

	/* nested view matrices are permissible, but weird. transformations
	 occur such that child transformations apply first. */
	rmMatrixMultiply(&view, &(rState->view), &view);

	rState->view = view;

	/* nested projection matrices give me headaches to think about - they are not permitted */
/*	rState->projection = proj;  2/15/2001 wes: llnl/vdl */
	rmMatrixMultiply(&proj, &(rState->projection), &(rState->projection)); 
	viewMatrixChange = 1;
    }   
    else if (n->scene_parms && n->scene_parms->camera2d)
    {

	rmCamera2DComputeViewMatrix(n->scene_parms->camera2d, &view);
	rState->view = view;

#if 0
	/* selection (picking) in multistage disabled for now (1/2001) */
	if (renderMode == GL_SELECT)
	{
	    RMmatrix pick;
	    private_rmComputePickMatrix(rState, &pick);
	    rState->pick = pick;
	    private_drawCameraPickableQuad(perObjFunc, n);
	}
#endif
	rmMatrixIdentity(&proj);
	rmMatrixMultiply(&proj, &(rState->projection), &(rState->projection));
	
	viewMatrixChange = 1;
    }

    if ((n->transforms != NULL) && (n->transforms->transform_mode != RM_TRANSFORM_IGNORE))
    {
	rmNodeGetCompositeModelMatrix(n, &model);

	/* texture and model matrix transformations accumulate - child transformations are applied first */
	if (n->transforms->transform_mode == RM_TRANSFORM_TEXTURE)
	{
	    rmMatrixMultiply(&model, &(rState->textureMatrix), &model);
	    rState->textureMatrix = model;
	    textureMatrixChange = 1;
	    returnStatus = TEXTURE_MATRIX_CHANGED;
	}
	else
	{
	    rmMatrixMultiply(&model, &(rState->model), &model);
	    rState->model = model;
	    modelMatrixChange = 1;
	}
    }
    
    /* now apply the matrices to the RMstate object - load the matrices into OpenGL */
    if (modelMatrixChange==1 || viewMatrixChange == 1)
    {
	RMmatrix m;
	
	returnStatus = MODELVIEW_MATRIX_CHANGED;
	rmMatrixMultiply(&(rState->model), &(rState->view), &oglModelView);
	
	if (viewMatrixChange == 1)
	{
	    rmMatrixMultiply(&(rState->pick), &(rState->projection), &m);
	    returnStatus |= PROJECTION_MATRIX_CHANGED;
	}
	rState->modelView = oglModelView;
	rmMatrixMultiply(&(rState->modelView), &(rState->projection), &(rState->composite));
    }

    p = NULL; 			/* foil compiler warning */
    return(returnStatus);
}

static void
private_rmViewRecurse(RMpipe *p,
		      RMnode *n,
		      int (*nodeRenderPassFunc)(const RMnode *),
		      int (*channelTraversalFunc)(const RMnode *),
		      RMdisplayList *mt,
		      RMstate *rState)
{
    /*
     * outline:
     *
     * filterfunc test
     * pretraversal callback
     * channel traversal test
     * if !ok
     *   return;
     *
     * cullmode:
     * if frustumcull
     * {
     *    compute/accumulate xform, if any
     *    frustum cull
     * }
     * other types of cull?
     * if !pass
     *    return;
     *
     * compute opcode for this node (eg, pushattrib, loadmatrix, etc.)
     * load matrix and or node * into pipe mt buffer
     *
     * switch callback -OR- renderOrder callback
     * process child(ren)
     *
     * undo pushattrib, if set
     *
     * post traverse
     */

    /*
     * June 2005. Consolidated two opcode push/pops into one: combined
     * the attrib and local state push/pop into a single call. The reason
     * is so that when we do a pop from the attrib stack, we can also
     * compare the attrib mask used during the push (now stored in a
     * stack in the RMpipe). If the GL_ENABLE_BIT is set, we then know
     * that the RMstateCache may be out of sync wrt the RMstate. Normally,
     * the RMstateCache has the most current settings for lighting,
     * colormaterial, etc. In some cases, some RMprimitives will
     * turn off lighting (rmText, for example), which is reflected
     * in the RMstateCache, but then when attrib/state is popped, the
     * old state might have had lighting enabled, which would then cause
     * OpenGL, the RMstate and the RMstateCache to be out of sync.
     *
     * By combining the two opcodes into one, we can address the sync problem
     * whereas we couldn't using two separate push/pops.
     */
#if 0
    GLuint mask=0;
#endif
    int i, descendChildren;
    int haveCameras, haveTransforms, haveTextProps, haveOGLStateChange;
    RMstate localState;
    int matrixStat=0;

    /* need refinement on traverse-enable flags? */
    if (rmNodeGetTraverseEnable(n) == RM_FALSE) 
	return;
    
    /* compare 3d/2d/transparent renderpass with values set in node */
    if ((*nodeRenderPassFunc)(n) == 0)
	return;

    /* compare to stereo channel settings */
    if ((channelTraversalFunc != NULL) && ((*channelTraversalFunc)(n) == 0))
	return;

    localState = *rState;

    /* invoke user defined view-stage, pre-traversal callback */
    if (n->viewPretraverseCallback != NULL)
    {
	int rstat;
	int (*afunc)(const RMnode *, const RMstate *);

	afunc = n->viewPretraverseCallback;
	rstat = (*afunc)((const RMnode *)n, (const RMstate *)rState);

	if (rstat <= 0)
	    return;
    }

    /* 8/11/02 wes */
    /* if there's a pre traversal callback for the render traversal,
       tell the render stage to invoke it */
    if (n->renderPretraverseCallback != NULL)
    	private_rmDLloadOpcode(MT_PRETRAVERSAL_FUNC, n->compListIndx, mt);


    /* anything in this node that will cause a draw state change? */
    if ((n->scene_parms != NULL) && (n->scene_parms->textProps != NULL))
	haveTextProps = 1;
    else
	haveTextProps = 0;

    haveTransforms = (n->transforms != NULL) ? 1 : 0;
    
    if ((n->scene_parms != NULL) && ((n->scene_parms->camera3d != NULL) || (n->scene_parms->camera2d != NULL)))
	haveCameras = 1;
    else
	haveCameras = 0;

    haveOGLStateChange = (private_rmNodeGetAttribMask(n) != 0) ? 1 : 0;

#if 0
    /*
     * June 2005 - want to combined OpenGL attrib push/pop along with
     * OpenRM state push/pop into a single call to help better sync
     * state tracking.
     */
    if (haveOGLStateChange || haveCameras || haveTransforms || haveTextProps)
	private_rmDLloadOpcode(MT_PUSH_STATE, n->compListIndx, mt);
#endif

    /* text props? */
    if ((n->scene_parms != NULL) && (n->scene_parms->textProps != NULL))
	private_rmDLloadOpcode(MT_PUSH_TEXTPROPS, n->compListIndx, mt);
    
    /* process transformations */
    
    if (haveTransforms || haveCameras)
    {
	int indx;

	matrixStat = private_rmDLcollectTransformations(p, n, &localState);
	
	if (matrixStat & TEXTURE_MATRIX_CHANGED)
	{
	    indx = private_rmDLnextMatrix(mt);
	    private_rmDLloadMatrix(mt, indx, &(localState.textureMatrix));
	    private_rmDLloadOpcode(MT_PUSHLOAD_TEXTURE_MATRIX,indx, mt);
	}
	
	if (matrixStat & PROJECTION_MATRIX_CHANGED)
	{
	    indx = private_rmDLnextMatrix(mt);
	    private_rmDLloadMatrix(mt, indx, &(localState.projection));
	    private_rmDLloadOpcode(MT_PUSHLOAD_PROJ_MATRIX, indx, mt);
	}
	
	if (matrixStat & MODELVIEW_MATRIX_CHANGED)
	{
	    indx = private_rmDLnextMatrix(mt);
	    private_rmDLloadMatrix(mt, indx, &(localState.modelView));
	    private_rmDLloadOpcode(MT_PUSHLOAD_MODELVIEW_MATRIX, indx, mt);
	}
    }
    
    /* cull modes - not yet implemented */

#if 0
    /* compute attribute opcode for this node */
    /*
     * June 2005 - want to combined OpenGL attrib push/pop along with
     * OpenRM state push/pop into a single call to help better sync
     * state tracking.
     */
    if ((mask = private_rmNodeGetAttribMask(n)) != 0)
	private_rmDLloadOpcode(MT_PUSHATTRIB, n->compListIndx, mt);
#endif

#if 1
    /*
     * push OpenGL attrib and RM state
     */
    /*    if (haveOGLStateChange || haveCameras || haveTransforms || haveTextProps) */
    if (haveOGLStateChange)
	private_rmDLloadOpcode(MT_PUSH_STATE_ATTRIB, n->compListIndx, mt);
#endif

    /* fb clear? */
    if (n->fbClear != 0)
	private_rmDLloadOpcode(MT_FBCLEAR, n->compListIndx, mt);

    /* process switch callback, and/or compute # of children. process children */
/*     descendChildren = (n->nchildren > 0) ? 1 : 0; 1.6.2 */
    descendChildren = (private_rmNodeGetNumChildren(n) > 0) ? 1 : 0; 
    if (descendChildren != 0)
    {
	if (n->viewSwitchCallback != NULL)
	{
	    int (*afunc)(const RMnode *, const RMstate *);
	    int indx;
	    
	    afunc = n->viewSwitchCallback;
	    indx = (*afunc)((const RMnode *)n, (const RMstate *)&localState);

	    /* range check return value */
/*	    if ((indx >= 0) || (indx < n->nchildren)) 1.6.2 */
	    if ((indx >= 0) || (indx < private_rmNodeGetNumChildren(n)))
		private_rmViewRecurse(p, private_rmNodeGetIthChild(n,indx), nodeRenderPassFunc, channelTraversalFunc, mt, &localState);
/* 		private_rmViewRecurse(p, n->children[indx], nodeRenderPassFunc, channelTraversalFunc, mt, &localState); 1.6.2 */
	    else
	    {
		char buf[256];
/*		sprintf(buf," view traversal error: the switch callback at node <%s> returned a value of %d which is out of range. The valid range is 0 <= n < %d.",n->object_info.name, indx, n->nchildren); 1.6.2 */
		sprintf(buf," view traversal error: the switch callback at node <%s> returned a value of %d which is out of range. The valid range is 0 <= n < %d.",n->object_info.name, indx, (int)private_rmNodeGetNumChildren(n));
		
		rmError(buf);
	    }
	}
	else if (n->viewRenderOrderCallback != NULL) /* render order callback */
        {
	    int i, nChildrenToRender;
	    int (*appFunc)(const RMnode *, const RMstate *, int *orderIndices, int nChildren);
/*	    int *childIndices = (int *)malloc(sizeof(int)*n->nchildren); 1.6.2 */
	    int *childIndices = (int *)malloc(sizeof(int)*private_rmNodeGetNumChildren(n));

	    /* default render or is 0..n-1 */
/*	    for (i=0;i<n->nchildren;i++) */
	    for (i=0; i<private_rmNodeGetNumChildren(n); i++)
		childIndices[i] = i;
	    
	    appFunc = n->viewRenderOrderCallback;
/*	    nChildrenToRender = (*appFunc)((const RMnode *)n, (const RMstate *)&localState, childIndices, n->nchildren); 1.6.2 */
	    nChildrenToRender = (*appFunc)((const RMnode *)n, (const RMstate *)&localState, childIndices, private_rmNodeGetNumChildren(n));

	    for (i=0;i<nChildrenToRender;i++)
	    {
/*		if ((childIndices[i] < 0) || (childIndices[i] >= n->nchildren)) 1.6.2 */
		if ((childIndices[i] < 0) || (childIndices[i] >= private_rmNodeGetNumChildren(n)))
		{
		    char buf[1024];
		    sprintf(buf,"private_rmViewRecurse() view stage traversal error: the index value returned by the app Render Order callback in position %d is equal to %d, which is out of range. Skipping this child index and processing the rest.",i, childIndices[i]);
		    rmError(buf);
		}
		else
		    private_rmViewRecurse(p, private_rmNodeGetIthChild(n,childIndices[i]), nodeRenderPassFunc, channelTraversalFunc, mt, &localState);
/* 		    private_rmViewRecurse(p, n->children[childIndices[i]], nodeRenderPassFunc, channelTraversalFunc, mt, &localState); 1.6.2 */
	    }
	}
	else			/* traverse all children */
	{
#if 0
	    /* 1.6.2 */
	    for (i=0;i<n->nchildren;i++)
		private_rmViewRecurse(p, n->children[i], nodeRenderPassFunc, channelTraversalFunc, mt, &localState);
#endif
	    for (i=0; i<private_rmNodeGetNumChildren(n); i++)
		private_rmViewRecurse(p, private_rmNodeGetIthChild(n,i), nodeRenderPassFunc, channelTraversalFunc, mt, &localState);
	}
    }
    else
    {
	/* if no children, any prims or draw-process callbacks? if so, load
	   this node for processing. */
	if (n->nprims > 0)
	    private_rmDLloadOpcode(MT_DRAW, n->compListIndx, mt);
    }

    /* 8/11/02 wes.  */
    /* invoke view-stage post traversal callback before attributes are popped */
    if (n->viewPosttraverseCallback != NULL)
    {
	int (*afunc)(const RMnode *, const RMstate *);
	int rstat;
	afunc = n->viewPosttraverseCallback;
	rstat = (*afunc)((const RMnode *)n, (const RMstate *)rState);
	/* ignore the return status of the post traversal callback */
    }

    /* 8/11/02 wes */
    /* if there's a post traversal callback for the render traversal,
       tell the render stage to invoke it */
    if (n->renderPosttraverseCallback != NULL)
    	private_rmDLloadOpcode(MT_POSTTRAVERSAL_FUNC, n->compListIndx, mt);

#if 0
    /* if we did a pushattrib, undo it */
    if (mask != 0)
	private_rmDLloadOpcode(MT_POPATTRIB, n->compListIndx, mt); 
/*	private_rmDLloadOpcode(MT_POPATTRIB, -1, mt); */
#endif

    if (matrixStat != 0)
    {
	if (matrixStat & TEXTURE_MATRIX_CHANGED)
	    private_rmDLloadOpcode(MT_POP_TEXTURE_MATRIX, -1, mt);
	if (matrixStat & PROJECTION_MATRIX_CHANGED)
	    private_rmDLloadOpcode(MT_POP_PROJ_MATRIX, -1, mt);
	if (matrixStat & MODELVIEW_MATRIX_CHANGED)
	    private_rmDLloadOpcode(MT_POP_MODELVIEW_MATRIX, -1, mt);
    }
	
    /* text props? */
    if (haveTextProps != 0)
	private_rmDLloadOpcode(MT_POP_TEXTPROPS, n->compListIndx, mt);

#if 0
    if (haveOGLStateChange || haveCameras || haveTransforms || haveTextProps)
	private_rmDLloadOpcode(MT_POP_STATE, n->compListIndx, mt);
#endif
    
    /*
     * pop OpenGL attrib and RM state
     */
    /*    if (haveOGLStateChange || haveCameras || haveTransforms || haveTextProps) */
    if (haveOGLStateChange)
	private_rmDLloadOpcode(MT_POP_STATE_ATTRIB, n->compListIndx, mt);

}

void
private_rmView (RMpipe *p,
		RMnode *n,
		int frameNumber,
		RMmatrix *initModelMatrix,
		RMmatrix *initViewMatrix,
		RMmatrix *initProjectionMatrix,
		RMmatrix *initTextureMatrix)
{
    /* for each pass of multipass rendering on RMpipe p, perform
     view pass operations, preparing results for rendering */
    RMstate rState, initState;
    RMenum channelFormat = rmPipeGetChannelFormat(p);
    RMmultipassDisplayList *mp=NULL;
    RMdisplayList *dl=NULL;
    int dlIndx;
    
    RMenum render3DOpaqueEnable;
    RMenum render3DTransparentEnable;
    RMenum render2DOpaqueEnable;

    frameNumber++; 		/* foil compiler warning */
    
#if 1
    /* 4/3/03 wes - moved this initialization step to rmPipeNew. the
       result is to remove some initialization from the rendering path,
       even though the initialization is a one-time event) */
    if (p->displayLists == NULL)
	private_rmPipeDisplayListsNew(p);
#endif

    /* select even or odd multipass display list as a function of framenum */

    dlIndx = private_rmSelectEvenOddBuffer(p->frameNumber);
    
    private_rmPipeDisplayListsInit(p, dlIndx);
    mp = (RMmultipassDisplayList *)(p->displayLists);

    private_rmStateInit(p, &initState, (RMenum) GL_RENDER,
			initModelMatrix,
			initViewMatrix,
			initProjectionMatrix,
			initTextureMatrix);
    
    rState = initState;
    
    rmPipeGetRenderPassEnable(p, &render3DOpaqueEnable, &render3DTransparentEnable, &render2DOpaqueEnable);

    /* RMpipe level fb clear? */
    if ((p->fbClearNode != NULL) && (p->fbClearNode->fbClear != NULL))
    {
	if (channelFormat == RM_MONO_CHANNEL)
	{
	    dl = mp->globals[dlIndx];
	    private_rmDLloadOpcode(MT_FBCLEAR, p->fbClearNode->compListIndx, dl);
	}
	else
	{
	    dl = mp->globals[dlIndx];
	    private_rmDLloadOpcode(MT_FBCLEAR, p->fbClearNode->compListIndx, dl);
	    dl = mp->rightGlobals[dlIndx];
	    private_rmDLloadOpcode(MT_FBCLEAR, p->fbClearNode->compListIndx, dl);
	}
    }

    /* do the 3D, opaque rendering pass */
    if (render3DOpaqueEnable == RM_TRUE)
    {
	rState.renderpass = RM_RENDERPASS_OPAQUE;

	if (channelFormat == RM_MONO_CHANNEL)
	{
	    rState.which_channel = RM_ALL_CHANNELS;
	    dl = mp->opaque3D[dlIndx];
	    private_rmViewRecurse(p, n, opaque3DFilterFunc, NULL, dl, &rState);
	}
	else
	{
	    rState.which_channel = RM_LEFT_CHANNEL;
	    dl = mp->opaque3D[dlIndx];
	    private_rmViewRecurse(p, n, opaque3DFilterFunc, leftChannelTraversalFunc, dl, &rState);
	    
	    rState.which_channel = RM_RIGHT_CHANNEL;
	    dl = mp->rightOpaque3D[dlIndx];
	    private_rmViewRecurse(p, n, opaque3DFilterFunc, rightChannelTraversalFunc, dl, &rState);
	}
    }
    
    /* do the 3D, transparent rendering pass */
    if (render3DTransparentEnable == RM_TRUE)
    {
	rState = initState;
	
	rState.renderpass = RM_RENDERPASS_TRANSPARENT;

	if (channelFormat == RM_MONO_CHANNEL)
	{
	    rState.which_channel = RM_ALL_CHANNELS;
	    dl = mp->transparent3D[dlIndx];
	    private_rmViewRecurse(p, n, transparent3DFilterFunc, NULL, dl, &rState);
	}
	else
	{
	    rState.which_channel = RM_LEFT_CHANNEL;
	    dl = mp->transparent3D[dlIndx];
	    private_rmViewRecurse(p, n, transparent3DFilterFunc, leftChannelTraversalFunc, dl, &rState);
	    
	    rState.which_channel = RM_RIGHT_CHANNEL;
	    dl = mp->rightTransparent3D[dlIndx];
	    private_rmViewRecurse(p, n, transparent3DFilterFunc, rightChannelTraversalFunc, dl, &rState);
	}
    }

    /* do the 2d rendering pass */
    if (render2DOpaqueEnable == RM_TRUE)
    {
	rState = initState;
	rState.renderpass = RM_RENDERPASS_OPAQUE;

	if (channelFormat == RM_MONO_CHANNEL)
	{
	    rState.which_channel = RM_ALL_CHANNELS;
	    dl = mp->opaque2D[dlIndx];
	    private_rmViewRecurse(p, n, opaque2DFilterFunc, NULL, dl, &rState);
	}
	else
	{
	    rState.which_channel = RM_LEFT_CHANNEL;
	    dl = mp->opaque2D[dlIndx];
	    private_rmViewRecurse(p, n, opaque2DFilterFunc, leftChannelTraversalFunc, dl, &rState);
	    
	    rState.which_channel = RM_RIGHT_CHANNEL;
	    dl = mp->rightOpaque2D[dlIndx];
	    private_rmViewRecurse(p, n, opaque2DFilterFunc, rightChannelTraversalFunc, dl, &rState);
	}
    }
}
/* EOF */
