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

/*
 * This demonstration program implements a test of the node switch
 * callback: three objects representing different levels of detail
 * are constructed and placed into the scene graph under a "switch
 * node" (a node containing a switch callback). At render time,
 * the switch callback will select one of the three objects to use
 * for rendering, depending upon the distance from the viewer to
 * the object.  1/8/2001, wes.
 */

/*
 * $Id: switchtest.c,v 1.12 2005/06/08 16:21:46 wes Exp $
 * $Revision: 1.12 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: switchtest.c,v $
 * Revision 1.12  2005/06/08 16:21:46  wes
 * Code cleanups to eliminate compiler warnings.
 *
 * Revision 1.11  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.10  2003/04/12 21:03:47  wes
 * Fixed switchtest ranges based upon emperical observation.
 *
 * 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/06/17 00:43:02  wes
 * Updated copyright line.
 *
 * Revision 1.6  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.5  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.4  2001/05/26 14:27:36  wes
 * Brightened background fill color.
 *
 * Revision 1.3  2001/03/31 18:48:31  wes
 * Tweaked range values used in model switching.
 *
 * Revision 1.2  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.1  2001/01/14 15:55:30  wes
 * Initial entry.
 *
 */

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

static char MyRootName[]={"MyRoot"};
static RMnode *MyRoot;
int img_width=400,img_height=300;
float switchRanges[2];

void
usage(char *av[])
{
    char buf[256];
    sprintf(buf," usage: %s [-w img_width] [-h img_height] \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],"-h") == 0)
        {
	    i++;
	    sscanf(av[i],"%d",&img_height);
	}
	else
	{
	    usage(av);
	    exit(-1);
	}
	i++;
    }
}

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();
    
    rmDefaultCamera3D(c);		/* assign it some default values. */
    
    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);
	
    }

    /* add the camera to "my root's" scene parms. */
    rmNodeSetSceneCamera3D(camNode,c);

    switchRanges[0] = 105.0F;
    switchRanges[1] = 125.0F; /* from emperical observation */

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

    /* set background color at MyRoot */
    {
	RMcolor4D c={0.25, 0.25, 0.25, 1.0};
	rmNodeSetSceneBackgroundColor(MyRoot, &c);
    }
}

int
mySwitchCallback(const RMnode *n,
		 const RMstate *s)
{
    /* compute the distance from the viewer to the center of the
     object. the viewer is, by definition, at 0,0,0. we have to
     transform the node's center point through the composite
     transformation matrix, then vector magnitude of the resulting
     vector is the distance. */
    float v[4], d[4];
    RMvertex3D c;
    float dist;

    rmNodeGetCenter(n,&c);

    v[0] = c.x;
    v[1] = c.y;
    v[2] = c.z;
    v[3] = 1.F;
    
    {
	const RMmatrix *mv;

	/*
	 * transform the node's center point through the combined
	 * modelView matrix. This will produce a new point that is
	 * in eye coordinates. By definition, the viewer's location
	 * in eye coords is at (0,0,0).
	 */
	
	mv = rmStateGetModelViewMatrix(s);
	rmPointMatrixTransform((const RMvertex3D *)v, mv, (RMvertex3D *)d);
    }

    c.x = d[0];
    c.y = d[1];
    c.z = d[2];

    /*
     * In eye coords, the distance from the viewer to the node's center
     * is simply the vector magnitude of the transformed center point.
     */

    dist = rmVertex3DMag(&c);

    /*
     * select one of the 3 children (3 is known a priori, and is
     * app specific) based upon pre-computed range data.
     */
    if (dist < switchRanges[0])
	return(0);
    else if (dist < switchRanges[1])
	return(1);
    else
	return(2);
}

void
createSwitchGroup(RMnode *addToNode,
		  RMvertex3D *center)
{
    RMnode *hiRes, *medRes, *lowRes, *switchNode;
    RMprimitive *p;
    float radius = 10.0;
    RMcolor4D color={0.4, 0.4, 0.7, 1.0};
    
    hiRes = rmNodeNew("hiRes", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    medRes = rmNodeNew("medRes", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    lowRes = rmNodeNew("lowRes", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    switchNode = rmNodeNew("switch", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);

    rmNodeAddChild(addToNode, switchNode);
    rmNodeAddChild(switchNode, hiRes);
    rmNodeAddChild(switchNode, medRes);
    rmNodeAddChild(switchNode, lowRes);

    p = rmPrimitiveNew(RM_SPHERES);
    rmPrimitiveSetVertex3D(p, 1, center, RM_COPY_DATA, NULL);
    rmPrimitiveSetRadii(p, 1, &radius, RM_COPY_DATA, NULL);
    rmPrimitiveSetColor4D(p, 1, &color, RM_COPY_DATA, NULL);
    rmPrimitiveSetModelFlag(p, RM_SPHERES_512);
    rmNodeAddPrimitive(hiRes, p);
    rmNodeComputeBoundingBox(hiRes);
    
    p = rmPrimitiveNew(RM_SPHERES);
    rmPrimitiveSetVertex3D(p, 1, center, RM_COPY_DATA, NULL);
    rmPrimitiveSetRadii(p, 1, &radius, RM_COPY_DATA, NULL);
    rmPrimitiveSetColor4D(p, 1, &color, RM_COPY_DATA, NULL);
    rmPrimitiveSetModelFlag(p, RM_SPHERES_32);
    rmNodeAddPrimitive(medRes, p);
    rmNodeComputeBoundingBox(medRes);
    
    p = rmPrimitiveNew(RM_SPHERES);
    rmPrimitiveSetVertex3D(p, 1, center, RM_COPY_DATA, NULL);
    rmPrimitiveSetRadii(p, 1, &radius, RM_COPY_DATA, NULL);
    rmPrimitiveSetColor4D(p, 1, &color, RM_COPY_DATA, NULL);
    rmPrimitiveSetModelFlag(p, RM_SPHERES_8);
    rmNodeAddPrimitive(lowRes, p);
    rmNodeComputeBoundingBox(lowRes);

    rmNodeSetSwitchCallback(switchNode, mySwitchCallback);
    rmNodeUnionAllBoxes(switchNode);
    rmNodeComputeCenterFromBoundingBox(switchNode);
}
    
void
myBuildObjs(void)
{
    RMvertex3D point1={-20.0, 0.0, 0.0};
    RMvertex3D point2={0.0, 0.0, -20.0};
    RMvertex3D point3={20.0, 0.0, -40.0};
    
    MyRoot = rmNodeNew(MyRootName,RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeAddChild(rmRootNode(),MyRoot);

    createSwitchGroup(MyRoot, &point1);
    createSwitchGroup(MyRoot, &point2);
    createSwitchGroup(MyRoot, &point3);
    
    rmNodeUnionAllBoxes(MyRoot);
    rmNodeComputeCenterFromBoundingBox(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 warning */
    ix = iy = 0;
}


void
myinitfunc(RMpipe *p,
	   RMnode *n)
{
    myBuildObjs();
    my_set_scene(n, rmPipeGetChannelFormat(p));

    /*
     * 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);
    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
myrenderfunc(RMpipe *p,
	     RMnode *n)
{
    rmFrame(p, n);
}

#ifdef RM_WIN
int WINAPI WinMain (HINSTANCE hInstance,
		    HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
{
    MSG      msg; 
    HWND     hWnd; 
    void *fptr;
    RMenum channelStereoFormat;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_WGL;
    RMpipe *myPipe = NULL;
    
    parse_args(__argc,__argv);
#else
int
main(int ac,
     char *av[])
{
    RMenum channelStereoFormat;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_GLX;
    RMpipe *myPipe = NULL;
    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. 
     */
    channelStereoFormat = RM_MONO_CHANNEL;

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

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

    rmPipeSetProcessingMode(myPipe, 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(myPipe,
			       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(myPipe,hWnd, img_width, img_height);

       /* uncomment this next line if you want the object to rotate
	  while the user is idle. */
	/* rmauxSetIdleFunc(my_idle_func, hWnd); */
    }
#endif
#ifdef RM_X
    {
	Window w;
	w = rmauxCreateXWindow(myPipe,
			       (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(myPipe,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); 

    /* 
     * 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.
     */
    rmPipeMakeCurrent(myPipe);
    rmauxSetRenderFunc(myrenderfunc);

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

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