/*
 * 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: cones.c,v 1.16 2005/06/08 18:28:49 wes Exp $
 * $Revision: 1.16 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: cones.c,v $
 * Revision 1.16  2005/06/08 18:28:49  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.15  2004/01/17 03:14:46  wes
 * Added command line args [-fr NN] and [-spin] to enable constant-rate
 * rendering and auto-spin mode, respectively.
 *
 * Revision 1.14  2003/07/25 21:56:19  wes
 * Minor tweaks to fix Win32 compile problem.
 *
 * Revision 1.13  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.12  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.11  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.10  2002/09/17 14:26:19  wes
 * Changed default visual to RM_MBUF_STEREO_CHANNEL. When this fails,
 * as detected by RM_WHACKED returned from rmPipeInit(), will fall back
 * to anaglyph stereo using RM_REDBLUE_STEREO_CHANNEL.
 *
 * Revision 1.9  2002/08/19 00:25:37  wes
 * No signficant changes.
 *
 * Revision 1.8  2002/06/20 13:11:50  wes
 * Update copyright date.
 *
 * Revision 1.7  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.6  2001/06/03 19:44:05  wes
 * Add calls to new rmaux routines to handle window resize events, and
 * for keyboard event handling.
 *
 * Revision 1.5  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.4  2000/12/02 17:24:32  wes
 * Version 1.4.0-alpha-1 checkin. See the RELEASENOTES file for
 * a summary of changes. With this checkin, these demo programs
 * are no longer compatible with versions of the OpenRM API that
 * are pre-1.4.0.
 *
 * Revision 1.3  2000/08/28 01:38:18  wes
 * Updated rmaux* interfaces - rmauxEventLoop now takes two additional
 * parameters (keypress and window resize app callbacks); replaced
 * rmauxUI with rmauxSetGeomTransform and, where appropriate,
 * rmauxSetCamera3DTransform.
 *
 * Revision 1.2  2000/04/20 18:04:34  wes
 * Minor tweaks and reorg for OpenRM 1.2.1.
 *
 * Revision 1.1.1.1  2000/02/28 21:55:30  wes
 * OpenRM 1.2 Release
 *
 * Revision 1.11  2000/02/28 17:21:54  wes
 * RM 1.2, pre-OpenRM
 *
 */

/*
 * usage: cones [-fr NN] [-spin]
 * options:
 *  -fr NN sets the frameRate in maximum frames per second. If left
 *    unspecified, no frame rate is imposed, and the program is freely running.
 *  -spin  will enable auto-spin mode.
 */

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

static RMnode *MyRoot;
int img_width=400,img_height=300;

RMenum static_stereo_format = RM_MONO_CHANNEL;
static int frameRate = -1;
static RMenum spinEnable = RM_FALSE;

void
usage(char *progName)
{
    printf(" usage: %s [-fr NN] [-spin] \n \
options: \n \
  -fr NN sets the frameRate in maximum frames per second. \n \
      If left unspecified, no frame rate is imposed, and the program \n \
      is freely running. \n \
  -spin  will enable auto-spin mode. Default is no auto-spin. \n", progName);
}


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

    /*
     * assign the camera some "default" parameters.
     */
    rmDefaultCamera3D(c);
    
    /* adjust the  camera "default" parameters as a function of the
     geom contained at or below MyRoot */
    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);

    }

    /* write the 3d camera back as a scene parameter of rmRootNode() */
    rmNodeSetSceneCamera3D(camNode, c); 
    
    rmCamera3DDelete(c);		/* because a copy is made by RM */

    {
	/*
	 * assign a background color to take effect at "MyRoot"
	 */
	RMcolor4D bgcolor={0.4F, 0.4F, 0.4F, 1.0F};
	RMcolor4D unlit={1.0F, 1.0F, 1.0F, 1.0F};
	rmNodeSetSceneBackgroundColor(MyRoot, &bgcolor);
	rmNodeSetUnlitColor(MyRoot, &unlit);
    }
    
    /* use RM's default lighting model */
    rmDefaultLighting(camNode);
}

void
my_build_objs(void)
{
    MyRoot = rmNodeNew("MyRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);

    {
	/* make some cones and put  'em at x=+5,y=+5 and z=+5 */
	RMvertex3D v[6] = {{5.,0.,0.,}, /* base */
			     {8.,0.,0.}, /* top */
			     {0.,5.,0.}, /* base */
			     {0.,8.,0.}, /* top */
			     {0.,0.,5.}, /* base */
			     {0.,0.,8.}}; /* top */

	float r[3] = {1.,1.,1.}; /* radius of each cone */
	
	RMcolor3D c[3] = {{1.,0.,0.,},{0.,1.,0.},{0.,0.,1.}}; /* cone colors */
	RMnode *coneobj;
	RMprimitive *coneprim;
	int ncones = 3;

	coneobj = rmNodeNew("cones",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
	
	coneprim = rmPrimitiveNew(RM_CONES);

	/*
	 * add the vertices: each corresponds to the center point
	 * in the base of the cone.
	 */
	rmPrimitiveSetVertex3D(coneprim,ncones*2,v,RM_COPY_DATA,NULL);

	/*
	 * add colors, if desired. each rgb-triplet will be used
	 * to color the entire cone
	 */

	rmPrimitiveSetColor3D(coneprim, ncones, c, RM_COPY_DATA, NULL);
	
	/*
	 * one radius value per cone.
	 */
	rmPrimitiveSetRadii(coneprim,ncones,r,RM_COPY_DATA, NULL);
	
	rmNodeAddPrimitive(coneobj, coneprim);
	rmNodeComputeBoundingBox(coneobj);
	
	rmNodeAddChild(MyRoot,coneobj);
	
    }

    {
	/* make some cylinders */
        
	RMvertex3D v[6] = {{0.,0.,0}, {4.,0.,0.,},
			 {0.,0.,0},{0.,4.,0.},
			 {0.,0.,0},{0.,0.,4.}};
	
	float r[3] = {.3F,.3F,.3F}; /* radius of each cylinder */
	RMcolor3D c[3] = {{1.,1.,1.},{1.,1.,1.},{1.,1.,1.}}; /* cylinder colors */

	RMnode *cylinderobj;
	RMprimitive *cylinderprim;
	int ncylinders = 3;

	cylinderobj = rmNodeNew("cylinders",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
	cylinderprim = rmPrimitiveNew(RM_CYLINDERS);
	

	/*
	 * add vertex info, 2 per cylinder. each pair defines the
	 * endpoints of a cylinder.
	 */
	rmPrimitiveSetVertex3D(cylinderprim,ncylinders*2,v,RM_COPY_DATA,NULL);

	/*
	 * one color value per cylinder.
	 */

	rmPrimitiveSetColor3D(cylinderprim, ncylinders, c, RM_COPY_DATA, NULL);


	/*
	 * two radius values per cylinder, one for each end.  these
	 * radius values correspond to the vertex pair that defines
	 * the endpoints of the cylinder.
	 */

	rmPrimitiveSetRadii(cylinderprim,
			    ncylinders,
			    r,
			    RM_COPY_DATA,
			    NULL);

	rmNodeAddPrimitive(cylinderobj, cylinderprim);
	rmNodeComputeBoundingBox(cylinderobj);
	
	rmNodeAddChild(MyRoot,cylinderobj);

    }

    {
	/* stick a sphere at the base of the cylinders to make
	 things look nice */
	
	RMnode *sphereobj;
	RMprimitive *sprim;
	RMvertex3D v;
	float radius=0.3F;
	RMcolor3D c={1.,1.,1.};

	v.x = v.y = v.z = 0.0F;

	sphereobj = rmNodeNew("sphere",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
	sprim = rmPrimitiveNew(RM_SPHERES);
	
	rmPrimitiveSetVertex3D(sprim,1,&v,RM_COPY_DATA,NULL);
	rmPrimitiveSetColor3D(sprim,1,&c,RM_COPY_DATA,NULL);

	rmPrimitiveSetRadii(sprim,1,&radius, RM_COPY_DATA, NULL);
    
	rmNodeAddPrimitive(sphereobj, sprim);

	rmNodeAddChild(MyRoot,sphereobj);
    }

    rmNodeUnionAllBoxes(MyRoot); /* this does union of bounding boxes in the
				 entire tree, starting from the leaf nodes
				 and going upwards.*/
    {
        RMvertex3D bmin,bmax,center;

	rmNodeGetBoundingBox(MyRoot,&bmin,&bmax);
	center.x = bmin.x + 0.5F *(bmax.x - bmin.x);
	center.y = bmin.y + 0.5F *(bmax.y - bmin.y);
	center.z = bmin.z + 0.5F *(bmax.z - bmin.z);

	rmNodeSetCenter(MyRoot,&center);
    }

    {
        RMvertex3D bmin,bmax,center;

        rmNodeUnionAllBoxes(rmRootNode()); 

	rmNodeGetBoundingBox(rmRootNode(),&bmin,&bmax);
	center.x = bmin.x + 0.5F *(bmax.x - bmin.x);
	center.y = bmin.y + 0.5F *(bmax.y - bmin.y);
	center.z = bmin.z + 0.5F *(bmax.z - bmin.z);

	rmNodeSetCenter(rmRootNode(),&center);
    }

    /*
     * add "my root" to RM's root node. this is necessary since we're
     * doing an rmSubtreeFrame() to render everything inside of rmauxUI().
     */
    rmNodeAddChild(rmRootNode(),MyRoot); 

}

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;
    
    if (rmNodeGetRotateMatrix(MyRoot,&old) == RM_WHACKED)
	rmMatrixIdentity(&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, static_stereo_format);

    /*
     * set up the event handler to apply geometric transformations at
     * MyRoot. note that the lights and cameras are placed at rmRootNode().
     * therefore, the rotations & scaling applied at MyRoot do not affect
     * the cameras & lights since they are at a higher level in the
     * scene graph than MyRoot.
     */
    rmauxSetGeomTransform(MyRoot,p);
    rmauxSetSpinEnable(spinEnable);
    rmauxSetCamera3DTransform(n, p);

    /*
     * set handler to reset aspect ratio when the window is resized.
     */
    rmauxSetResizeFunc(p, n, rmauxDefaultResizeFunc);
    
    if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE)
	rmFrame(p, n);
    
    rmFrame(p, n);
}


void
parseArgs(int ac,
	  char *av[])
{
    int i=1;
    ac--;

    while (ac > 0)
    {
	if (strcmp(av[i],"-fr") == 0)
	{
	    i++;
	    ac--;
	    sscanf(av[i],"%d",&frameRate);
	}
	else if (strcmp(av[i],"-spin") == 0)
	{
	    spinEnable = RM_TRUE;
	}
	else
	{
	    usage(av[0]);
	    exit(-1);
	}
	i++;
	ac--;
    }
}

#ifdef RM_WIN
int WINAPI WinMain (HINSTANCE hInstance,
		    HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
{
    MSG      msg; 
    RMenum targetPlatform = RM_PIPE_WGL;
    HWND     hWnd; 
    void *fptr;
    RMpipe *lone_pipe=NULL;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    
    /* 
     * 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. 
     */
    static_stereo_format = RM_REDBLUE_STEREO_CHANNEL;    

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

    /* 
     * 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. 
     */
    static_stereo_format = RM_REDBLUE_STEREO_CHANNEL;    

    parseArgs(ac, av);
    
#endif
    
    /* 
     * 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);
    rmPipeSetFrameRate(lone_pipe, frameRate);

#ifdef RM_WIN
    rmPipeSetChannelFormat(lone_pipe, RM_REDBLUE_STEREO_CHANNEL);
    /*
     * July 2003:
     * The test we use in X-based systems for the presence/absence of
     * a stereo visual does not work under windows. We need to investigate
     * and repair the problem. For now, the following code that dynamically
     * selects a multibuffered or anaglyph stereo visual works only on X-based
     * platforms.
     */
#else
    rmPipeSetChannelFormat(lone_pipe, RM_MBUF_STEREO_CHANNEL);

    if (rmPipeCreateContext(lone_pipe) == RM_WHACKED)
    {
	rmWarning(" alloc of a mbuf visual failed, falling back to anaglyph \n");
	rmPipeSetChannelFormat(lone_pipe, RM_REDBLUE_STEREO_CHANNEL);
	
	if (rmPipeCreateContext(lone_pipe) == RM_WHACKED)
	{
	    rmError(" this is hopeless. bye!");
	    exit(-1);
	}
    }
#endif
	

#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
       by itself */
    /* rmauxSetIdleFunc(lone_pipe,my_idle_func);  */

    /* 
     * X-ism: once the window is created and assigned to the 
     * rendering pipe, rmPipeMakeCurrent 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 inside RM.
     */
    rmPipeMakeCurrent(lone_pipe);	

    /*
     * 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
}
