#include <rm/rm.h>
#include <rm/rmaux.h>
#include "barrier.h"
#include "procmode.h"

/*
 * Copyright (C) 2000-2005, R3vis Corporation.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; 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/gpl.html.
 *
 * Contributor(s):
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rm2screen.c,v 1.14 2005/06/08 16:34:18 wes Exp $
 * $Revision: 1.14 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: rm2screen.c,v $
 * Revision 1.14  2005/06/08 16:34:18  wes
 * Code cleanup to eliminate compiler warnings. (pthread_setconcurrency remains)
 *
 * Revision 1.13  2005/05/16 01:17:24  wes
 * Removed call to XInitThreads(). It seems to cause problems these days
 * on 2.6 kernels.
 *
 * Revision 1.12  2004/01/17 03:18:20  wes
 * Updated local SwapBuffers callback so that it matches the prototype
 * for user-assigned SwapBuffers callbacks.
 *
 * Revision 1.11  2003/10/15 05:50:42  wes
 * Replaced deprecated rmxPipeGetContext() with rmPipeGetContext().
 *
 * Revision 1.10  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.9  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.8  2003/01/16 22:22:45  wes
 * Updated all source files to reflect new organization of header files: all
 * headers that were formerly located in include/rmaux, include/rmv and
 * include/rmi are now located in include/rm.
 *
 * Revision 1.7  2002/08/19 00:25:23  wes
 * Updated barrier_* calls and type references to local version so as to
 * avoid name collision with similar structs and calls inside OpenRM.
 *
 * Revision 1.6  2002/06/17 00:42:07  wes
 * Updated copyright line.
 *
 * Revision 1.5  2001/07/15 22:33:19  wes
 * Added rmPipeDelete to the end of all demo progs. For those that use
 * an initfunc, added a new RMnode * parm (which is unused, except for rm2screen).
 *
 * Revision 1.4  2001/06/04 00:58:53  wes
 * Added call to new rmaux key handler callback.
 *
 * Revision 1.3  2001/03/31 16:55:18  wes
 * Added procmode.h, which defines an RMpipe processing mode used in
 * most demonstration programs. The default processing mode is
 * RM_PIPE_MULTISTAGE_VIEW_PARALLEL.
 *
 * Revision 1.2  2000/12/03 23:30:23  wes
 * *** empty log message ***
 *
 * Revision 1.1  2000/12/02 17:23:02  wes
 * Initial entry.
 *
 */



/*
 * #define PARALLEL 0 means everything (X, glX, OpenGL) is serialized
 * #define PARALLEL 1 means everything happens in parallel (unreliable
 *  on some platforms)
 */
#define PARALLEL 0

/*
 * use -DSOLARIS on the compile line, or uncomment the following line to
 * activate solaris-specific calls. you shouldn't have to uncomment
 * the following line on solaris boxes - the configure script in
 * rmdemo should generate a -DSOLARIS when run on a SunOS box.
 */

/*#define SOLARIS 1 */

#if 1
int window1[] = {0,0,500,500};	/* geom: x,y, w,h */
int window2[] = {500,0,500,500}; /* geom: x,y, w,h */
char display1[]={":0.0"};
char display2[]={":0.0"};
#else
int window1[] = {0,0,1280,2048};	/* geom: x,y, w,h */
int window2[] = {0,0,1280,2048}; /* geom: x,y, w,h */
char display1[]={":2.1"};
char display2[]={":2.2"};
#endif


double frust1[] = {-0.5, 0., -0.25, 0.25, 0.1, 10.0};/* frustum l, r, t, b, n, f */
double frust2[] = {0., 0.5, -0.25, 0.25, 0.1, 10.0};/* frustum l, r, t, b, n, f */
#if 0
double frust1[] = {-0.5, 0., -0.5, 0.5, 0.1, 10.0};/* frustum l, r, t, b, n, f */
double frust2[] = {0., 0.5, -0.5, 0.5, 0.1, 10.0};/* frustum l, r, t, b, n, f */
#endif
pthread_mutex_t *globalMutex=NULL;
int globalExit = 0;
my_barrier_t serialRenderBarrier;

typedef struct
{
    int windowGeom[4];		/* input window geom */
    int myID;			/* thread "index" */
    RMnode *modelRoot;		/* the stuff to render */
    pthread_mutex_t *mutex;
    double frustum[6];
    char *display;
} thrArgs;

#if PARALLEL
RMenum
mySwapbuffersFunc(const RMpipe *p)
{
    glXSwapBuffers(p->xdisplay, p->xdrawable);
    glFlush();
    return RM_CHILL;
}
#endif

void
setFrustum(double *f)
{

#define FRUST_SCALE .3
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(f[0]*FRUST_SCALE, f[1]*FRUST_SCALE,
	      f[2]*FRUST_SCALE, f[3]*FRUST_SCALE, f[4], f[5]);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(0., 0., 3.,
	      0.0, 0.0, -1.0,
	      0.0, 1.0, 0.0);
}

void *
threadFunc(void *args)
{
    thrArgs *ta;
    RMpipe *myPipe=NULL;
    Window w;
    char windowTitle[32];
    pthread_mutex_t *mutex;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */

    ta = (thrArgs *)args;
    mutex = ta->mutex;

#if !PARALLEL
    pthread_mutex_lock(mutex);
#endif

    myPipe = rmPipeNew(RM_PIPE_GLX);
    
/*    status = rmPipeInit(ta->display, RM_MONO_CHANNEL, &myPipe); */

    /*
     * override default processing mode - always use MULTISTAGE serial
     * in the worker thread functions.
     */
    processingMode = RM_PIPE_MULTISTAGE;
    rmPipeSetProcessingMode(myPipe, processingMode); 
    
#if PARALLEL
    rmPipeSetSwapBuffersFunc(myPipe, mySwapbuffersFunc);
#endif

    sprintf(windowTitle,"Window%d", ta->myID);
    
    w = rmauxCreateXWindow(myPipe,
			   (Window)NULL, /* parent window */
			   ta->windowGeom[0],
			   ta->windowGeom[1],
			   ta->windowGeom[2],
			   ta->windowGeom[3],
			   windowTitle, windowTitle, RM_FALSE);
    if (w == 0)
    {
	fprintf(stderr," error creating window \n");
	exit(-1);
    }
    rmPipeSetWindow(myPipe,w,window1[2],window1[3]);
    
    XMapWindow(myPipe->xdisplay, myPipe->xdrawable);
    XSync(myPipe->xdisplay,False);
    
    rmPipeMakeCurrent(myPipe);
    rmPipeSetInitMatrixStackMode(myPipe, RM_FALSE);

    setFrustum(ta->frustum);
    rmFrame(myPipe,ta->modelRoot);

#if !PARALLEL
    pthread_mutex_unlock(mutex);
#endif
    sched_yield(); 

    while (globalExit == 0)
    {
#if !PARALLEL
	pthread_mutex_lock(mutex);
	glXMakeCurrent(rmxPipeGetDisplay(myPipe),
		       rmPipeGetWindow(myPipe),
		       rmPipeGetContext(myPipe));
#endif
	
	setFrustum(ta->frustum);
	rmFrame(myPipe, ta->modelRoot);
	glFlush();
	
#if !PARALLEL
	pthread_mutex_unlock(mutex);
#endif
	my_barrier_wait(&serialRenderBarrier);
	sched_yield();
    }
    
    rmPipeDelete(myPipe);
    return(NULL);
}

void
buildCamera(RMnode **navView)
{
    /*
     * create 3 RMnodes, each of which contains a 3D camera.
     */
    RMnode *n;
    RMcamera3D *c;
    RMvertex3D eye = {0.0, 0.0, 3.0};
    RMvertex3D up = {0.0, 1.0, 0.0};
    RMvertex3D navAt= {0.0, 0.0, 0.0} ;
    float hither=0.1, yon=5.0;

    *navView = n = rmNodeNew("navView", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    c = rmCamera3DNew();
    
    rmCamera3DSetEye(c,&eye);
    rmCamera3DSetAt(c,&navAt);
    rmCamera3DSetUpVector(c,&up);
    rmCamera3DSetHither(c,hither);
    rmCamera3DSetYon(c,yon);
    rmCamera3DSetAspectRatio(c,1.0F);
    rmCamera3DSetProjection(c,RM_PROJECTION_PERSPECTIVE); 
    rmCamera3DSetFOV(c,60.0F);

    rmNodeSetSceneCamera3D(n,c);

    rmCamera3DDelete(c);
}

void
myLighting(RMnode *addToNode)
{
    /* set up two lights, one red and one blue */
    RMlight   *l0, *l1;
    RMcolor4D red = {1.0, 0.1, 0.1, 1.0};
    RMcolor4D blue= {0.1, 0.1, 1.0, 1.0};
    
    RMcolor4D  specular = {0.5, 0.5, 0.5, 1.0};
    
    RMvertex3D redDirection = {-3.0, 2.0, 1.0};
    RMvertex3D blueDirection = {3.0, 2.0, 1.0}; 

    l0 = rmLightNew();
    if (l0 == NULL)
	return;

    rmLightSetType(l0, RM_LIGHT_DIRECTIONAL); 
    rmLightSetColor(l0, NULL, &red, &specular); 
    rmLightSetXYZ (l0, &redDirection);

    l1 = rmLightNew();
    if (l1 == NULL)
	return;
    
    rmLightSetType(l1, RM_LIGHT_DIRECTIONAL); 
    rmLightSetColor(l1, NULL, &blue, &specular);
    rmLightSetXYZ(l1, &blueDirection);

    rmNodeSetSceneLight(addToNode, RM_LIGHT0, l0);
    rmNodeSetSceneLight(addToNode, RM_LIGHT1, l1);
    
    /*
     * when the lights are added as scene parameters, RM makes a copy
     * of them, and we don't need our RMlight objects anymore, so we
     * need to delete them.
     */

    rmLightDelete(l0);
    rmLightDelete(l1); 
    
    /* set up the light model/lighting environment */
    {
	RMcolor4D     defAmbient =  {0.5, 0.5, 0.5, 1.0};
	RMlightModel *lm = rmLightModelNew();

	if (lm == NULL)
	    return;
	
	rmLightModelSetAmbient(lm, &defAmbient);
	rmLightModelSetTwoSided (lm, RM_FALSE);
	rmLightModelSetLocalViewer(lm, RM_FALSE);
	rmNodeSetSceneLightModel(addToNode, lm);

	rmLightModelDelete (lm);
    }
}

void
buildInitialSceneGraph(RMnode **navView,
		       RMnode **leftView,
		       RMnode **rightView,
		       RMnode **model)
{
    RMnode *boxNode;
    RMprimitive *boxPrim;
    RMvertex3D boxCorners[2] = {{-1.0F, -1.0F, -1.0},{1.0F, 1.0F, 1.0F}};
    RMcolor4D white={0.9, 0.9, 0.9, 1.0};
    RMcolor4D gray={0.2, 0.2, 0.2, 1.0};
    RMcolor4D gray1={0.2, 0.25, 0.2, 1.0};
    RMcolor4D gray2={0.25, 0.2, 0.2, 1.0};
    
    buildCamera(navView);

    *leftView = rmNodeNew("leftView", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    *rightView = rmNodeNew("rightView", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);

    rmNodeSetSceneBackgroundColor(*navView, &gray);
    myLighting(*navView);    
    rmNodeSetUnlitColor(*navView, &white);

    rmNodeSetSceneBackgroundColor(*leftView, &gray1);
    myLighting(*leftView);    
    rmNodeSetUnlitColor(*leftView, &white);
    
    rmNodeSetSceneBackgroundColor(*rightView, &gray2);
    myLighting(*rightView);    
    rmNodeSetUnlitColor(*rightView, &white);

    *model = boxNode = rmNodeNew("boxNode",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    boxPrim = rmPrimitiveNew(RM_BOX3D);
    rmPrimitiveSetVertex3D(boxPrim, 2, boxCorners, RM_COPY_DATA, NULL);
    rmNodeAddPrimitive(boxNode, boxPrim);
    rmNodeComputeBoundingBox(boxNode);
    rmNodeComputeCenterFromBoundingBox(boxNode);

    rmNodeAddChild(*navView, boxNode);
    rmNodeAddChild(*leftView, boxNode);
    rmNodeAddChild(*rightView, boxNode);

}

void
myRenderFunc(RMpipe *p,
	     RMnode *n)
{

    rmFrame(p, n);		/* render on the navPipe */
    
    pthread_mutex_unlock(globalMutex); /* release slave rmPipe renderers */
    my_barrier_wait(&serialRenderBarrier); /* global barrier */
    pthread_mutex_lock(globalMutex);
}

void
myIdleFunc(RMpipe *p,
	   int ix,
	   int iy)
{
#if !PARALLEL
    pthread_mutex_unlock(globalMutex);
#endif
    sched_yield(); 
    usleep(100);
#if !PARALLEL
    pthread_mutex_lock(globalMutex);
#endif

    /* foil compiler warning */
    ix = iy = 0;
    p = NULL;
}

void
myInitFunc(RMpipe *p,
	   RMnode *toDraw)
{
    
    rmFrame(p, toDraw);
    myRenderFunc(p, toDraw);
}


int
main(int ac,
     char *av[])
{
    pthread_attr_t attr;
    pthread_t thrID1, thrID2;
    void *returnVal;
    thrArgs *threadArgs1, *threadArgs2;
    RMnode *leftView=NULL, *rightView=NULL, *navView=NULL, *model=NULL;
    int status;
    pthread_mutex_t *mutex;
    RMpipe *navPipe=NULL;
    Window navWindow;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */

    /* foil compiler warnings */
    ac = 0;
    av = NULL;
    
    /*    status = XInitThreads();  */
    
#ifdef SOLARIS
    glXInitThreadsSUN();
#else
    pthread_setconcurrency(12);
#endif
    
    rmInit();

    buildInitialSceneGraph(&navView, &leftView, &rightView,&model);

/*    rmPipeInit(getenv("DISPLAY"), RM_MONO_CHANNEL, &navPipe); */
/*    rmPipeInit(":0.0", RM_MONO_CHANNEL, &navPipe); */
    navPipe = rmPipeNew(RM_PIPE_GLX);
    
    rmPipeSetProcessingMode(navPipe, processingMode);
    
#if PARALLEL
    rmPipeSetSwapBuffersFunc(navPipe, mySwapbuffersFunc);
#else
    my_barrier_init(&serialRenderBarrier, 3);
#endif

    navWindow = rmauxCreateXWindow(navPipe, (Window)NULL,
				   500, 600, 200, 200,
				   "Navigate Window", "navWindow", RM_TRUE);
    rmPipeSetWindow(navPipe, navWindow, 200, 200);
    rmPipeMakeCurrent(navPipe);
    rmauxSetGeomTransform(model, navPipe);

    globalMutex = mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
    status = pthread_mutex_init(mutex,NULL);

#if !PARALLEL
    pthread_mutex_lock(mutex);
#endif

    threadArgs1 = (thrArgs *)malloc(sizeof(thrArgs));
    memcpy(threadArgs1->windowGeom, window1, sizeof(int)*4);
    memcpy(threadArgs1->frustum, frust1, sizeof(double)*6);
    threadArgs1->myID = 0;
    threadArgs1->modelRoot = leftView;
    threadArgs1->mutex = mutex;
    threadArgs1->display = display1;
    pthread_attr_init(&attr);
    pthread_create(&thrID1, &attr, threadFunc, (void *)threadArgs1); 

    threadArgs2 = (thrArgs *)malloc(sizeof(thrArgs));
    memcpy(threadArgs2->windowGeom, window2, sizeof(int)*4);
    memcpy(threadArgs2->frustum, frust2, sizeof(double)*6);
    threadArgs2->myID = 1;
    threadArgs2->modelRoot = rightView;
    threadArgs2->mutex = mutex;
    threadArgs2->display = display2;
    
    pthread_create(&thrID2, &attr, threadFunc, (void *)threadArgs2);
/*    rmauxSetIdleFunc(navPipe, myIdleFunc);   */
    
    rmauxSetRenderFunc(myRenderFunc);
    rmauxSetInitFunc(myInitFunc);

    /*
     * set key handler function so this prog will exit on "q" key.
     */
    rmauxSetKeyFunc(navPipe, rmauxDefaultKeyFunc);

    rmauxEventLoop(navPipe, navView, NULL);

    globalExit = 1;

    myRenderFunc(navPipe, navView);
    pthread_mutex_unlock(mutex);

    status = pthread_join(thrID2, &returnVal); 
    status = pthread_join(thrID1, &returnVal);

    rmPipeDelete(navPipe);

#if !PARALLEL
    my_barrier_destroy(&serialRenderBarrier);
#endif
    
    rmFinish();

    return 1;
}
