/*
 * 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: jballs.c,v 1.15 2005/06/08 17:39:23 wes Exp $
 * $Revision: 1.15 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: jballs.c,v $
 * Revision 1.15  2005/06/08 17:39:23  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.14  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.13  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.12  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.11  2002/06/17 00:38:48  wes
 * updated copyright lines.
 *
 * Revision 1.10  2001/10/15 00:22:52  wes
 * Removed dead code.
 *
 * Revision 1.9  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.8  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.7  2001/03/31 18:48:43  wes
 * Turned off per-frame printf's.
 *
 * Revision 1.6  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.5  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.4  2000/08/31 02:14:46  wes
 * Added rmauxSetGeomTransform and rmauxSetCamera3DTransform.
 *
 * 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/16 14:43:27  wes
 * Changed "1" to RM_TRUE for variable "managed" passed to rmauxCreateXWindow.
 *
 * Revision 1.1.1.1  2000/02/28 21:55:30  wes
 * OpenRM 1.2 Release
 *
 * Revision 1.6  2000/02/28 17:21:55  wes
 * RM 1.2, pre-OpenRM
 *
 */

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

static RMpipe *lone_pipe=NULL;
static RMnode *MyRoot, *movinggroup, *stationarygroup;
int img_width=800,img_height=600;
RMenum static_stereo_format;

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();

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

    {
	/*
	 * rather than use the default lighting, we're going to
	 * roll our own here.
	 */
	RMlight *top;
	RMcolor4D speccolor;

	top = rmLightNew();
	
	speccolor.r = speccolor.g = speccolor.b = .7;
	speccolor.a = 1.0;
	rmLightSetColor(top,NULL, NULL, &speccolor);
	rmNodeSetSceneLight(camNode, RM_LIGHT0, top);
	rmLightDelete(top);

	/* use default light model stuff from OpenGL */
    }

}

void
my_sphere(RMnode *addto,
	  float cx,
	  float cy,
	  float cz,
	  float radius,
	  RMcolor3D *color)
{
    RMprimitive *sprim;
    RMvertex3D v;

    v.x = cx;
    v.y = cy;
    v.z = cz;
    
    sprim = rmPrimitiveNew(RM_SPHERES);
    rmPrimitiveSetVertex3D(sprim,1,&v,RM_COPY_DATA,NULL);

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

    if (color != NULL)
	rmPrimitiveSetColor3D(sprim, 1, color, RM_COPY_DATA, NULL);

    if (color == NULL)
	rmPrimitiveSetModelFlag(sprim,RM_SPHERES_512);
    else
	rmPrimitiveSetModelFlag(sprim,RM_SPHERES_128); 
    
    rmNodeAddPrimitive(addto, sprim);
}


void
my_build_objs(void)
{
    RMcolor3D color;
    
    RMnode *floor;

    /*
     *
     */

    /* the root for all our stuff */
    MyRoot = rmNodeNew("MyRoot",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);

    /* the root for the stationary objects */
    stationarygroup = rmNodeNew("stationary",RM_RENDERPASS_3D, RM_RENDERPASS_ALL);

    /* build floor */
    {
	int floor_res = 20;	/* a 20x20 grid */
	RMprimitive *p;
	RMvertex3D v[4] =
	    {{-3.0, -2.0, 3.0},{3.0, -2.0, 3.0}, 
	     {3.0, -2.0, -3.0},{-3.0,-2.0,-3.0}};
	RMvertex3D n={0.0F, 1.0F, 0.0F};

	RMvertex3D *vlist;
	RMvertex3D *nlist;
	int iu,iv,indx;
	float x,dx,z,dz,y;	/* y stays constant */

	vlist = rmVertex3DNew(floor_res * floor_res);
	nlist = rmVertex3DNew(floor_res * floor_res);

	/* the floor needs to have a lot of quads/triangles so that
	 the shading is correct. build arrays of verts & normals. */
	
	z = v[0].z;
	dz = (v[3].z - v[0].z) / (float)(floor_res-1);
	y = v[0].y;
	
	indx = 0;
	
	for (iv = 0; iv < floor_res; iv++, z+=dz)
	{
	    x = v[0].x;
	    dx = (v[1].x - v[0].x)/(float)(floor_res-1);
	    
	    for (iu=0; iu < floor_res; iu++, x+=dx)
	    {
		vlist[indx].x = x;
		vlist[indx].y = y;
		vlist[indx].z = z;

		nlist[indx] = n;
		indx++;
	    }
	}
	
	floor = rmNodeNew("floor", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
	p = rmPrimitiveNew(RM_QUADMESH);

	rmPrimitiveSetQmeshDims(p,floor_res, floor_res);
	rmPrimitiveSetVertex3D(p,floor_res*floor_res,vlist,
			       RM_COPY_DATA,NULL);
	rmPrimitiveSetNormal3D(p,floor_res*floor_res,nlist,
			       RM_COPY_DATA,NULL);
	
	rmNodeAddPrimitive(floor,p);

	rmNodeComputeBoundingBox(floor);
	rmNodeAddChild(stationarygroup, floor);

	rmVertex3DDelete(vlist);
	rmVertex3DDelete(nlist);

    }

    /* build primary orb */
    {
	RMnode *sloth;
	
        sloth = rmNodeNew("sloth", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
	my_sphere(sloth, 0.0F, 0.0F, 0.0F, 1.0F, NULL);
	
	rmNodeComputeBoundingBox(sloth);
	rmNodeAddChild(stationarygroup, sloth);
    }

    {
	/* build zippy orb */
	RMvertex3D c={0.0F, 0.0F, 0.0F};
	RMvertex3D pos={1.5F, -1.0F, 0.0F};
	RMcolor4D red={1.0,0.0,0.0,1.0};
	RMnode *zippy;
	
	RMlight *redLight;

	movinggroup = rmNodeNew("moving group", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
	
	color.r = .9F;
	color.g = color.b = 0.25F;

	zippy = rmNodeNew("zippy", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
	my_sphere(zippy, pos.x, pos.y, pos.z, 0.25F, &color);
	
	{
	    float specexp = 10.F;
	    rmNodeSetSpecularExponent(zippy, specexp);  
	}
	
	rmNodeComputeBoundingBox(zippy);
	rmNodeSetCenter(movinggroup,&c);
	rmNodeAddChild(movinggroup, zippy);

	redLight = rmLightNew();
	rmLightSetXYZ(redLight, &pos);
	rmLightSetColor(redLight, NULL, &red, &red);
	rmLightSetType (redLight, RM_LIGHT_POINT);
	
	rmNodeSetSceneLight(movinggroup,RM_LIGHT3,redLight);
	
	rmNodeAddChild(MyRoot, movinggroup);
    }

    rmNodeAddChild(movinggroup, stationarygroup);
    
    rmNodeAddChild(rmRootNode(),MyRoot); 

    /* this does union of bounding boxes in the
       entire tree, starting from the leaf nodes
       and going upwards.*/
    rmNodeUnionAllBoxes(rmRootNode());
    
    rmNodeComputeCenterFromBoundingBox(rmRootNode());
    rmNodeComputeCenterFromBoundingBox(MyRoot);

    {
	RMcolor4D bgcolor={0.2,0.2,0.3,1.0};
	/*
	 * assign a background color to take effect at "MyRoot"
	 */
	rmNodeSetSceneBackgroundColor(MyRoot,&bgcolor);
    }

    rmStatsComputeDemography(rmRootNode());
}

int
my_idle_func(RMpipe *p,
	     int pointerX,
	     int pointerY)
{
    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(movinggroup,&old) == RM_WHACKED)
	rmMatrixIdentity(&old);
    rmMatrixMultiply(&old,&m,&old);
    rmNodeSetRotateMatrix(movinggroup,&old);

    rmMatrixInverse(&m,&m);
    if (rmNodeGetRotateMatrix(stationarygroup,&old) == RM_WHACKED)
	rmMatrixIdentity(&old);
    rmMatrixMultiply(&old,&m,&old);
    rmNodeSetRotateMatrix(stationarygroup,&old);

    rmFrame(p, rmRootNode());

    /* foil compiler warnings */
    pointerX = pointerY = 0;
    
    return 1;
}

void
myinitfunc(RMpipe *p,
	   RMnode *n)
{
    int do_print = 1;		/* hack */
    
    my_build_objs();
    my_set_scene(n, rmPipeGetChannelFormat(p));
    rmauxSetGeomTransform(MyRoot,p);  
    rmauxSetCamera3DTransform(n, p); 
    

    do_print=0;			/* 5/5/99 */
    if (do_print == 1)		/* won't see output in win32 unless you change
				 NULL to a char string with a filename, then
				 the output will be written in ASCII format
				 to that file. */
	rmPrintSceneGraph(n, RM_PRINT_VERBOSE,NULL);

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

#ifdef RM_WIN
int WINAPI WinMain (HINSTANCE hInstance,
		    HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
{
    MSG      msg; 
    HWND     hWnd;
    RMenum   channelFormat;
    RMenum   targetPlatform = RM_PIPE_WGL;
    RMenum   processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    void *fptr;
#else  /* assume RM_X */
int
main()
{
    void *msg;			/* needed for rmauxEventLoop */
    RMenum   processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum   targetPlatform = RM_PIPE_GLX;
    RMenum channelFormat;
#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;
	int managed = RM_TRUE;

	w = rmauxCreateXWindow(lone_pipe,
			       (Window)NULL, /* parent window */
			       0,0,img_width,img_height,
			       "RM for X-Windows","icon-title",managed);
	/* 
	 * 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); 

    /* 
     * 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);	

    /* uncomment this next line if you want the object to rotate
       by itself while the user is idle. */
    rmauxSetIdleFunc(lone_pipe,my_idle_func);

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

}
