/*
 * 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: clipper.c,v 1.12 2005/06/12 21:31:00 wes Exp $
 * $Revision: 1.12 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: clipper.c,v $
 * Revision 1.12  2005/06/12 21:31:00  wes
 * Minor code reorganization.
 *
 * Revision 1.11  2005/06/08 18:22:14  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * 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/06/17 00:34:39  wes
 * replaced rmSubtreeFrame with rmFrame for v1.4.2.
 *
 * Revision 1.6  2001/07/15 22:33:18  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:24:49  wes
 * Added wireframe cubes.
 *
 * 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/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.1  2000/08/31 02:14:17  wes
 * Initial entry.
 *
 */

/*
 * This program demonstates the use of a transformable clip plane, along
 * with transformation nesting and polygon draw mode manipulation.
 * A sphere is positioned at the center of the view, with a red directional
 * light source pointing in from the left, and a blue directional
 * light source pointing in from the right. In addition, you'll see
 * a single quad, which represents the position and orientation of
 * the clip plane.
 *
 * The clip plane may be transformed (rotated) by using Shift+Button3.
 * Button2 will rotate the entire model, Shift+Button2 will perform
 * isometric scaling, Button1 translates the entire model parallel to
 * image plane, and Button3 dollys the camera along the Z axis in
 * eye coordinates.
 */

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

/*
 * usage: clipper [-w img_width] [-h img_height] 
 */

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

static RMvertex3D defaultClipPlanePoint={0.0F, 0.0F, 0.0F};
static RMvertex3D defaultClipPlaneNormal={0.0F, 0.0F, 1.0F};

static RMclipPlane clipPlane;
static RMnode *quadNode;
static RMnode *clipNode;
static float saveX, saveY;
static RMmatrix startMatrix, quadNodeMatrixInverse;
static RMmatrix MyRootMatrix, MyRootMatrixInverse;

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
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.2, 0.2, 0.2, 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
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 a custom lighting model */
    myLighting(rmRootNode());
}

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

    rmPrimitiveSetModelFlag(sprim,RM_SPHERES_512);
    
    rmNodeAddPrimitive(addto, sprim);
}

void
my_quad(RMnode *addToNode,
	RMvertex3D *point,
	RMvertex3D *normal,
	float radius)
{
    RMprimitive *p;
    RMvertex3D v[4], n[4];

    /* need to add code that will create a quad that actually
     honors the plane equation specified by "normal" */
    v[0].z = v[1].z = v[2].z = v[3].z = point->z;

    v[0].x = point->x - radius;
    v[0].y = point->y - radius;

    v[1].x = point->x + radius;
    v[1].y = point->y - radius;

    v[2].x = point->x + radius;
    v[2].y = point->y + radius;

    v[3].x = point->x - radius;
    v[3].y = point->y + radius;

    n[0] = n[1] = n[2] = n[3] = *normal;
    
    p = rmPrimitiveNew(RM_QUADS);

    rmPrimitiveSetVertex3D(p, 4, v, RM_COPY_DATA, NULL);
    rmPrimitiveSetNormal3D(p, 4, n, RM_COPY_DATA, NULL);

    rmNodeAddPrimitive(addToNode, p);
}

void
myClipPlane(RMnode *addToNode,
	    RMvertex3D *point,
	    RMvertex3D *normal)
{
    rmClipPlaneSetPointNormal(&clipPlane, point, normal);
    rmClipPlaneEnable(&clipPlane);

    rmNodeSetSceneClipPlane(addToNode,RM_SCENE_CLIP_PLANE0, &clipPlane);
}

void
buildInsideStuff(RMnode *addTo,
		 float x,
		 float y,
		 float z,
		 float radius)
{
    RMprimitive *p;
    RMvertex3D v[4];
    RMcolor3D c[2];

    p = rmPrimitiveNew(RM_BOX3D);

    v[0].x = x;
    v[0].y = y;
    v[0].z = z;

    v[1].x = v[0].x + radius * 0.5;
    v[1].y = v[0].y + radius * 0.5;
    v[1].z = v[0].z + radius * 0.5;
    
    v[2].x = x;
    v[2].y = y;
    v[2].z = z;

    v[3].x = v[2].x - radius * 0.5;
    v[3].y = v[2].y - radius * 0.5;
    v[3].z = v[2].z - radius * 0.5;

    c[0].r = c[0].g = 1.0;
    c[0].b = 0.0;

    c[1].r = c[1].b = 0.0;
    c[1].g = 1.0;

    rmPrimitiveSetVertex3D(p,4, v, RM_COPY_DATA,NULL);
    rmPrimitiveSetColor3D(p,2,c,RM_COPY_DATA, NULL);

    rmNodeAddPrimitive(addTo, p);
    
}

void
my_build_objs(void)
{
    RMnode *sphereNode, *insideNode, *insideWireNode;
    RMnode *nonClipNode;
    RMcolor3D sphereColor={1.0F, 1.0F, 1.0F};
    float radius = 5.0;

    MyRoot = rmNodeNew(MyRootName,RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeAddChild(rmRootNode(),MyRoot);

    clipNode = rmNodeNew("clipNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    nonClipNode = rmNodeNew("nonClipNode", RM_RENDERPASS_3D,
			    RM_RENDERPASS_OPAQUE);

    rmNodeAddChild(MyRoot, clipNode);  
    rmNodeAddChild(MyRoot, nonClipNode); 

    /* build objs */
    sphereNode = rmNodeNew("sphereNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    my_sphere(sphereNode, 0.0F, 0.0F, 0.0F, radius, &sphereColor);
    rmNodeComputeBoundingBox(sphereNode);

    /* put some stuff inside the sphere */
    insideNode = rmNodeNew("insideNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    buildInsideStuff(insideNode, 0.0F, 0.0F, 0.0F, radius);  
    
    insideWireNode = rmNodeNew("insideWireNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    buildInsideStuff(insideWireNode, 0.0F, 0.0F, 0.0F, radius); 

    /* clip plane representation */
    quadNode = rmNodeNew("quadNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    my_quad(quadNode, &defaultClipPlanePoint, &defaultClipPlaneNormal, radius);
    rmNodeComputeBoundingBox(quadNode);
    rmNodeSetPolygonDrawMode(quadNode, RM_BACK, RM_LINE);  

    /* build clip plane */
    myClipPlane(clipNode, &defaultClipPlanePoint, &defaultClipPlaneNormal); 

    /* add stuff to the subtree that will have stuff clipped */
    rmNodeAddChild(clipNode, sphereNode);
    rmNodeAddChild(clipNode, insideNode);  

    /* add stuff to the subtree where no clipping occurs */
    rmNodeAddChild(nonClipNode, quadNode);

    rmNodeSetPolygonDrawMode(insideWireNode, RM_FRONT_AND_BACK, RM_LINE); 
    rmNodeSetShader(insideWireNode, RM_SHADER_NOLIGHT); 
    rmNodeAddChild(nonClipNode, insideWireNode); 

    {
	RMcolor4D bgColor={0.2, 0.2, 0.3, 1.0};
	rmNodeSetSceneBackgroundColor(MyRoot, &bgColor);
	
    }
    rmNodeUnionAllBoxes(rmRootNode());
}

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;
}

static float pixeltovp(int ipixel, int idim)
{
    float t;
    t = (float)(ipixel - (idim>>1)) / (float)(idim >> 1);
    return(t);
}

int
MyStartClipXformFunc(RMpipe *p,
		     int ix,
		     int iy)
{
    int width, height;
    
    rmPipeGetWindowSize(p,&width, &height);
    
    /* save the starting position */
    saveX = pixeltovp(ix,width);
    saveY = -1.0F * pixeltovp(iy,height);
    if (rmNodeGetRotateMatrix(quadNode, &startMatrix) == RM_WHACKED)
	rmMatrixIdentity(&startMatrix);

    rmMatrixInverse(&startMatrix, &quadNodeMatrixInverse);

    if (rmNodeGetRotateMatrix(MyRoot, &MyRootMatrix) == RM_WHACKED)
	rmMatrixIdentity(&MyRootMatrix);
    
    rmMatrixInverse(&MyRootMatrix, &MyRootMatrixInverse);

    rmMatrixMultiply(&startMatrix, &MyRootMatrix, &startMatrix);
    return 1;
}

int
MyClipXformFunc(RMpipe *p,
		int ix,
		int iy)
{
    int width, height;
    float newX, newY;
    RMmatrix m;
    RMvertex3D point,normal;
    
    rmPipeGetWindowSize(p,&width, &height);

    newX = pixeltovp(ix,width);
    newY = -1.0F * pixeltovp(iy,height);

    /* compute the quaternion for x1,y1->x2,y2 */
    rmauxArcBall (&saveX, &saveY, &newX, &newY, &m);

    /* update the rotation matrix on the quad */
    rmMatrixMultiply(&m, &MyRootMatrixInverse, &m);
    rmMatrixMultiply(&startMatrix, &m, &m);
    rmNodeSetRotateMatrix(quadNode, &m);

    /* tweak the clip plane */
    rmClipPlaneGetPointNormal(&clipPlane, &point, &normal);
    rmPointMatrixTransform(&defaultClipPlaneNormal, &m, &normal);
    rmClipPlaneSetPointNormal(&clipPlane, &point, &normal);
    rmNodeSetSceneClipPlane(clipNode, RM_SCENE_CLIP_PLANE0, &clipPlane);

    /* rerender */
    
    rmFrame(p, rmRootNode());
    return(1);
}

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

    /* shift+button3 rotates the clip plane around */
    rmauxSetButtonDownFunc(RM_BUTTON3, RM_SHIFT_MODIFIER, MyStartClipXformFunc);
    rmauxSetButtonMotionFunc(RM_BUTTON3, RM_SHIFT_MODIFIER, MyClipXformFunc);

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

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

    parse_args(__argc,__argv);
#else
int
main(int ac,
     char *av[])
{
    RMpipe *lone_pipe=NULL;
    RMenum targetPlatform = RM_PIPE_GLX;
    RMenum channelFormat;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    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);

    /*
     * set the processing mode on the RMpipe. this must happen before
     * "rmPipeMakeCurrent().
     */
    rmPipeSetProcessingMode(lone_pipe, processingMode);
    rmPipeSetDisplayListEnable(lone_pipe, RM_FALSE);

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

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

}
