/*
 * Copyright (C) 1997-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: orderTest.c,v 1.6 2005/06/08 17:08:54 wes Exp $
 * $Revision: 1.6 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: orderTest.c,v $
 * Revision 1.6  2005/06/08 17:08:54  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.5  2004/01/17 03:17:32  wes
 * Removed C++ style comment delimeters, which work on on all versions of
 * gcc, and replaced them with C style comment delimeters so that the
 * SGI compiler will work.
 *
 * Revision 1.4  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.3  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.2  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.1  2002/08/29 22:22:06  wes
 * Initial entry.
 *
 */

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

/*
 * usage: orderTest [-w imgWidth] [-h imgHeight] [-n numPoints (default=100)] [-s (display framerate stats)]
 */

static char MyRootName[]={"MyRoot"};
static RMnode *MyRoot;
static int img_width=400,img_height=300;
static int num_points=200;
static int do_pointsize=1;
static float pointsize=20.0;
RMenum static_stereo_format;
int doStats=0;
int doOrder = 1;

static int *staticIndices = NULL;
static RMvertex3D *staticVerts = NULL;

void
usage(char *av[])
{
    char buf[256];
    sprintf(buf," usage: %s [-w img_width] [-h img_height] [-n num_points] [-p size (point size in pixels) ] [-noOrder (turns off per-frame node sorting)] [-s (turn on compute/report of frame rate stats)]\n",av[0]);
    rmError(buf);
}

void
parse_args(int ac,
	   char *av[])
{
    int i;

    i = 1;
    while (i < ac)
    {
	if (strcmp(av[i],"-w") == 0)
	{
	    i++;
	    sscanf(av[i],"%d",&img_width);
	}
	else if (strcmp(av[i],"-noOrder") == 0)
	    doOrder = 0;
	else if (strcmp(av[i],"-h") == 0)
        {
	    i++;
	    sscanf(av[i],"%d",&img_height);
	}
	else if (strcmp(av[i],"-s") == 0)
	{
	    i++;
	    doStats = 1;
	}
	else if (strcmp(av[i],"-n") == 0)
	{
	    i++;
	    sscanf(av[i],"%d",&num_points);
	}
	else if (strcmp(av[i],"-p") == 0)
	{
	    i++;
	    do_pointsize = 1;
	    sscanf(av[i],"%f",&pointsize);
	}
	else
	{
	    usage(av);
	    exit(-1);
	}
	i++;
    }
}

void
my_set_scene(RMnode *camNode,
	     RMenum stereo_format)
{
    /*
     * here, we compute the camera parameters such that all of
     * the geometry is visible.
     */
      
    RMcamera3D *c=rmCamera3DNew();

    /* assign a default view */
    rmDefaultCamera3D(c);

    /* adjust the default view so that all geom is visible */
    rmCamera3DComputeViewFromGeometry(c,MyRoot,img_width,img_height);

    if (stereo_format != RM_MONO_CHANNEL)
    {
	rmCamera3DSetStereo(c,RM_TRUE);
	rmCamera3DSetEyeSeparation(c,2.5F);
	rmCamera3DSetFocalDistance (c,0.707F);

    }
    rmNodeSetSceneCamera3D(camNode, c); 
    rmCamera3DDelete(c);

    /* use RM's default lighting model */
    rmDefaultLighting(camNode);
}

#define DIVISOR 1./32767.

void
ToVertex(int r1,
	 int r2,
	 RMvertex3D *v,
	 RMvertex3D *n)
{
    double azimuth, elevation;
    double ca,sa,se,ce;

    /* input rand numbers in range 0..32767 */

    /* convert to range 0..1 */
    azimuth = (double)r1 * DIVISOR;
    elevation = (double)r2 * DIVISOR;

    /* convert azimuth to range 0..2pi */
    azimuth *= RM_PI * 2.0;
    
    /* convert elevation to range -pi/2..pi/2 */
    elevation = (elevation - 0.5) * RM_PI;

    /* covert  */

    
    ca = cos(azimuth);
    sa = sin(azimuth);
    se = sin(elevation);
    ce = cos(elevation);

    v->y = se;
    v->x = ca * ce;
    v->z = sa * ce;

    n->x = v->x;
    n->y = v->y;
    n->z = v->z;
}

RMnode *
makePointNode(RMvertex3D *v,
	      RMvertex3D *n,
	      RMcolor4D *c,
	      int i)
{
    char buf[24];
    RMnode *node;
    RMprimitive *p = rmPrimitiveNew(RM_POINTS);

    sprintf(buf,"point%d",i);
    node = rmNodeNew(buf, RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
    
    rmPrimitiveSetVertex3D(p,1,v,RM_COPY_DATA,NULL);

    rmPrimitiveSetNormal3D(p,1,n, RM_COPY_DATA, NULL);
    rmPrimitiveSetColor4D(p, 1, c, RM_COPY_DATA, NULL); 

    rmNodeAddPrimitive (node, p);
    rmNodeComputeBoundingBox(node);
    return node;
}

int
myCompareFunc(const void *a,
	      const void *b)
{
    int ia, ib;
    ia = *(int *)a;
    ib = *(int *)b;

    if (staticVerts[ia].z > staticVerts[ib].z)
	return 1;
    else
	return -1;
}

int
myRenderOrderCallback(const RMnode *n,
		      const RMstate *s,
		      int *orderIndices,
		      int nChildren)
{
    int i;
    const RMmatrix *m;
    
    memcpy(staticIndices, orderIndices, sizeof(int)*nChildren);
    for (i=0;i<nChildren;i++)
	rmNodeGetCenter(n->children[i],staticVerts+i);

    /* transform points through composite transformation matrix */
    m = rmStateGetModelViewMatrix(s);
    for (i=0;i<nChildren;i++)
	rmPointMatrixTransform(staticVerts+i, m, staticVerts+i);

    /* sort in on descending z-coordinate for b2f rendering */
    qsort(staticIndices, nChildren, sizeof(int), myCompareFunc);
    
    memcpy(orderIndices, staticIndices, sizeof(int)*nChildren);
    
    return (nChildren);
}

void
my_build_objs(void)
{
    RMnode *objnode;
    RMenum rpass = RM_RENDERPASS_TRANSPARENT;  
/*    RMenum rpass = RM_RENDERPASS_OPAQUE;  */

    /* make MyRoot and all children nodes transparent */
    MyRoot = rmNodeNew(MyRootName,RM_RENDERPASS_3D, rpass);
    {
	RMnode *n = rmNodeNew("fbClear", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
	RMcolor4D bgcolor={0.2,0.2,0.3,1.0};
	/*
	 * assign a background color to take effect at "MyRoot"
	 */
	rmNodeSetSceneBackgroundColor(n,&bgcolor);
/*	rmNodeAddChild(MyRoot, n); */
	rmNodeAddChild(rmRootNode(), n);
    }
    rmNodeAddChild(rmRootNode(),MyRoot);
    
    {
	int npts = num_points;
	RMvertex3D *v,*n;
	RMcolor4D *c;
	int i;
	
	objnode = rmNodeNew("pointcloudRoot",RM_RENDERPASS_3D, rpass);

	v = rmVertex3DNew(npts);
	n = rmVertex3DNew(npts);
	c = (RMcolor4D *)malloc(sizeof(RMcolor4D)*npts);

	srand(time(NULL));
	/* generate random points on a the surface of a sphere */
	for (i=0;i<npts;i++)
	{
	    int r1,r2;
	    r1 = rand();
	    r2 = rand();
	    ToVertex(r1,r2,v+i,n+i);

	    c[i].r = c[i].g = c[i].b = 0.5;
	    c[i].r = v[i].y * 0.5 + 0.5;
	    c[i].a = 0.5F;
	}

	for (i=0;i<npts;i++)
	    rmNodeAddChild(objnode, makePointNode(v+i, n+i, c+i, i));

	rmVertex3DDelete(v);
	rmVertex3DDelete(n);

	rmNodeComputeBoundingBox(objnode);
	rmNodeComputeCenterFromBoundingBox(objnode);
	rmNodeAddChild(MyRoot, objnode);

	if (doOrder == 1)
	    rmNodeSetRenderOrderCallback(objnode, myRenderOrderCallback); 

	/* malloc space for sort buffers to avoid realloc on each frame */
	staticIndices = (int *)malloc(sizeof(int)*npts);
	staticVerts = rmVertex3DNew(npts);

	free((void *)c);

    }
    {
	RMvertex3D v[2];
	v[0].x = v[0].y = v[0].z = -1.0;
	v[1].x = v[1].y = v[1].z = 1.0;
	rmNodeSetBoundingBox(MyRoot,v,v+1);
    }

    if (do_pointsize)
    {
	rmNodeSetPointSize(MyRoot,pointsize);
	printf(" setting pointsize to %g \n", pointsize);
    }
}

void
my_idle_func(RMpipe *p,
	     int ix,
	     int iy)
{
    RMmatrix m,old;
    double d,c,s;
    rmMatrixIdentity(&m);
    d = RM_DEGREES_TO_RADIANS(1.0);
    c = cos(d);
    s = sin(d);
    m.m[0][0] = m.m[2][2] = c;
    m.m[0][2] = -s;
    m.m[2][0] = s;
    
    rmNodeGetRotateMatrix(MyRoot,&old);
    rmMatrixMultiply(&old,&m,&old);
    rmNodeSetRotateMatrix(MyRoot,&old);

    rmFrame(p, rmRootNode());

    /* foil compiler warnings */
    ix = iy = 0;
}

void
myinitfunc(RMpipe *p,
	   RMnode *n)
{
    my_build_objs();
    my_set_scene(n, rmPipeGetChannelFormat(p));
    rmauxSetGeomTransform(MyRoot,p);
    rmauxSetCamera3DTransform(n, p);

    /*
     * set handler to reset aspect ratio when the window is resized.
     */
    rmauxSetResizeFunc(p, n, rmauxDefaultResizeFunc);

    if (doStats != 0)
	rmStatsComputeDemography(n);
    
    if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE)
	rmFrame(p, n);
    
    rmFrame(p, n);
}

void
myRenderFunc(RMpipe *p,
	     RMnode *n)
{
    if (doStats != 0)
	rmStatsStartTime();
    
    rmFrame(p, n);

    if (doStats != 0)
    {
	rmStatsEndTime();
	rmStatsPrint();
    }
}
#ifdef RM_WIN
int WINAPI WinMain (HINSTANCE hInstance,
		    HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
{
    MSG      msg; 
    HWND     hWnd; 
    void *fptr;
    RMenum channelFormat;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMpipe *lone_pipe=NULL;
    RMenum targetPlatform = RM_PIPE_WGL;

    parse_args(__argc,__argv);
#else
int
main(int ac,
     char *av[])
{
    RMpipe *lone_pipe=NULL;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum channelFormat;
    RMenum targetPlatform = RM_PIPE_GLX;
    void *msg;			/* needed for rmauxEventLoop
				 win32/unix API consistency */

    parse_args(ac,av);
#endif
    /* 
     * pick a stereo format:
     * RM_MONO_CHANNEL - plain old single-view
     * RM_REDBLUE_STEREO_CHANNEL - left channel in red, right channel in cyan
     * RM_BLUERED_STEREO_CHANNEL - left in cyan, right in red
     * RM_MBUF_STEREO_CHANNEL - multibuffered stereo, requires special
     *    hardware. 
     */
    channelFormat = RM_MONO_CHANNEL; 

    /* 
     * first stage of RM initialization.
     */
    rmInit();

    /* 
     * create the rendering pipe. this step is required in both
     * Win32 and X.
     */
    lone_pipe = rmPipeNew(targetPlatform);
    
    rmPipeSetProcessingMode(lone_pipe, processingMode);

#ifdef RM_WIN
    {
        /*
	 * Win32: when a window is created, we have to tell windows the
	 * name of the "WndProc," the procedure that gets called by
	 * windows with events (the event loop) (contrast to the X model 
	 * where the name of the event loop is not part of the window). 
	 * Since we're using RMaux, we know about the event handling 
	 * procedure named "rmauxWndProc" and we provide that here. 
	 */

        fptr = (void *)(rmauxWndProc);
	hWnd = rmauxCreateW32Window(lone_pipe,
			       NULL, /* no parent window */
			       20,20,img_width,img_height,"RM for Windows",
			       hInstance,fptr);
	if (hWnd == 0)
	  exit(-1);

	/* 
	 * assign the new window handle to the rendering pipe.
	 */
	rmPipeSetWindow(lone_pipe,hWnd, img_width, img_height);
    }
#endif
    
#ifdef RM_X
    {
	Window w;

	w = rmauxCreateXWindow(lone_pipe,
			       (Window)NULL, /* parent window */
			       0,0,img_width,img_height,
			       "RM for X-Windows","icon-title",RM_TRUE);
	/* 
	 * assign the window to the rendering pipe.
	 */
	rmPipeSetWindow(lone_pipe,w,img_width,img_height);

    }
#endif

    /* 
     * specify the name of the "init" function. the "init" function is
     * mandatory in the Win32 world, and optional in the X world. 
     *
     * in Win32, we don't want to call RM services until OpenGL is
     * ready. we can be assured of readiness by using an init function
     * with RMaux. 
     *
     * in X, at this point, the window is mapped and OpenGL is ready,
     * and we could call our init function directly.
     */

    rmauxSetInitFunc(myinitfunc); 


    /* uncomment this next line if you want the object to rotate
       while the user is idle. */
    /*    rmauxSetIdleFunc(lone_pipe, my_idle_func); */
    
    /* 
     * X-ism: once the window is created and assigned to the 
     * rendering pipe, rmUsePipe makes the OpenGL rendering context
     * current for the pipe+window combination. 
     *
     * this step is required for X. in these demo programs, it is not 
     * strictly required by Win32, as the newly created context is made
     * current as part of the OpenGL initialization sequence.
     */
    rmPipeMakeCurrent(lone_pipe);	

    rmauxSetRenderFunc(myRenderFunc);
    /*
     * set key handler function so this prog will exit on "q" key.
     */
    rmauxSetKeyFunc(lone_pipe, rmauxDefaultKeyFunc);
    
    rmauxEventLoop(lone_pipe, rmRootNode(), &msg);

    rmPipeDelete(lone_pipe);
    rmFinish();

#ifdef RM_WIN
    return( msg.wParam );
#else
    return(1);
#endif

}
