/*
 * Copyright (C) 2001-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: spotlight.c,v 1.8 2005/06/08 16:29:04 wes Exp $
 * $Revision: 1.8 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: spotlight.c,v $
 * Revision 1.8  2005/06/08 16:29:04  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.7  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.6  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.5  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.4  2002/06/17 00:42:44  wes
 * Added -s command line flag to generate per-frame rendering statistics.
 *
 * Revision 1.3  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.2  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.1  2001/05/26 14:23:16  wes
 * Initial entry.
 *
 */

/*
 * usage: spotlight [-w imgWidthPixels] [-h imgHeightPixels] [-s (turn on/display frame rate statistics)]
 *
 * This program creates a scene consisting of 5 walls and a spotlight.
 * You can interactively move the spotlight around to see the effect
 * of spot lighting in a scene. The walls are reasonably dense quadmeshes
 * since OpenGL uses vertex lighting - a single quad would produce
 * crummy results.
 */

#include <stdio.h>
#ifndef RM_WIN
#include <unistd.h>
#endif
#include <rm/rm.h>   
#include <rm/rmaux.h>
#include "procmode.h"


/* default window size, this can be overridden by command line args */
#define DEFAULT_IMAGE_WIDTH 640
#define DEFAULT_IMAGE_HEIGHT 525

/*
 * you can safely modify the following four items to suit your tastes.
 */

#define SUBDIVISIONS 100	/* defines resolution of quadmeshes */
#define DEFAULT_SPOT_CUTOFF 45.0F /* half-angle width of spotlight beam */

static RMcolor4D spotColor = {0.9, 0.5, 0.5, 1.0}; /* color of spotlight */
static float spotExponent = 4.0F; /* values range 0..128, 0 produces
				   uniform distribution, higher values
				   concentrate light in center of spot */

/*
 * these define the position and orientation of the spotlight when
 * created. you can change the default position if you want. the
 * size of the scene is a cube bounded by 0,0,0 and 10,10,10. if
 * you change the spotDirection, be sure to specify a unit vector.
 */
static RMvertex3D spotPosition = {5.0F, 5.0F, 5.0F};
static RMvertex3D spotDirection = {0.0F, -1.0F, 0.0F};
    
static RMpipe *myPipe=NULL;
static RMnode *myRoot=NULL;
static RMnode *dummyNode=NULL;
static RMnode *spotIcon=NULL;

static RMcamera3D *myCamera=NULL;

static int doStats=0;

void
buildXZQuadmesh(RMnode *addTo,
		RMvertex3D *vmin,
		RMvertex3D *vmax,
		int subdivisions,
		int ySign)
{
    /*
     * build a quadmesh parallel to the X-Z plane.
     */
    RMvertex3D *v = rmVertex3DNew(subdivisions*subdivisions);
    RMvertex3D *n = rmVertex3DNew(subdivisions*subdivisions);
    RMvertex3D refNormal={0.0, 1.0, 0.0};
    RMprimitive *p = rmPrimitiveNew(RM_QUADMESH);
    int i,j, cnt=0;
    RMvertex3D w;
    double dx, dz;

    refNormal.y *= ySign;
    rmPrimitiveSetQmeshDims(p, subdivisions, subdivisions);

    dx = (vmax->x - vmin->x)/(subdivisions-1);
    dz = (vmax->z - vmin->z)/(subdivisions-1);
    w.z = vmin->z;
    w.y = vmin->y;
    for (j=0;j<subdivisions;j++)
    {
	w.x = vmin->x;
	for (i=0;i<subdivisions;i++)
	{
	    v[cnt] = w;
	    n[cnt] = refNormal;
	    cnt++;
	    w.x += dx;
	}
	w.z += dz;
    }

    rmPrimitiveSetVertex3D(p, subdivisions*subdivisions, v, RM_COPY_DATA,NULL);
    rmPrimitiveSetNormal3D(p, subdivisions*subdivisions, n, RM_COPY_DATA,NULL);

    rmNodeAddPrimitive(addTo, p);
    
    rmVertex3DDelete(v);
    rmVertex3DDelete(n);
}

void
buildXYQuadmesh(RMnode *addTo,
		RMvertex3D *vmin,
		RMvertex3D *vmax,
		int subdivisions,
		int zSign)
{
    /*
     * build a quadmesh parallel to the X-Y plane.
     */
    RMvertex3D *v = rmVertex3DNew(subdivisions*subdivisions);
    RMvertex3D *n = rmVertex3DNew(subdivisions*subdivisions);
    RMvertex3D refNormal={0.0, 0.0, 1.0};
    RMprimitive *p = rmPrimitiveNew(RM_QUADMESH);
    int i,j, cnt=0;
    RMvertex3D w;
    double dx, dy;

    refNormal.z *= zSign;
    rmPrimitiveSetQmeshDims(p, subdivisions, subdivisions);

    dx = (vmax->x - vmin->x)/(subdivisions-1);
    dy = (vmax->y - vmin->y)/(subdivisions-1);
    w.z = vmin->z;
    w.y = vmin->y;
    for (j=0;j<subdivisions;j++)
    {
	w.x = vmin->x;
	for (i=0;i<subdivisions;i++)
	{
	    v[cnt] = w;
	    n[cnt] = refNormal;
	    cnt++;
	    w.x += dx;
	}
	w.y += dy;
    }

    rmPrimitiveSetVertex3D(p, subdivisions*subdivisions, v, RM_COPY_DATA,NULL);
    rmPrimitiveSetNormal3D(p, subdivisions*subdivisions, n, RM_COPY_DATA,NULL);

    rmNodeAddPrimitive(addTo, p);
    
    rmVertex3DDelete(v);
    rmVertex3DDelete(n);
}

void
buildYZQuadmesh(RMnode *addTo,
		RMvertex3D *vmin,
		RMvertex3D *vmax,
		int subdivisions,
		int xSign)
{
    /*
     * build a quadmesh parallel to the Y-Z plane.
     */
    RMvertex3D *v = rmVertex3DNew(subdivisions*subdivisions);
    RMvertex3D *n = rmVertex3DNew(subdivisions*subdivisions);
    RMvertex3D refNormal={1.0, 0.0, 0.0};
    RMprimitive *p = rmPrimitiveNew(RM_QUADMESH);
    int i,j, cnt=0;
    RMvertex3D w;
    double dy, dz;

    refNormal.x *= xSign;
    rmPrimitiveSetQmeshDims(p, subdivisions, subdivisions);

    dy = (vmax->y - vmin->y)/(subdivisions-1);
    dz = (vmax->z - vmin->z)/(subdivisions-1);
    
    w.x = vmin->x;
    w.y = vmin->y;
    for (j=0;j<subdivisions;j++)
    {
	w.z = vmin->z;
	for (i=0;i<subdivisions;i++)
	{
	    v[cnt] = w;
	    n[cnt] = refNormal;
	    cnt++;
	    w.z += dz;
	}
	w.y += dy;
    }

    rmPrimitiveSetVertex3D(p, subdivisions*subdivisions, v, RM_COPY_DATA,NULL);
    rmPrimitiveSetNormal3D(p, subdivisions*subdivisions, n, RM_COPY_DATA,NULL);

    rmNodeAddPrimitive(addTo, p);
    
    rmVertex3DDelete(v);
    rmVertex3DDelete(n);
}

void
buildCamera(RMnode *addTo,
	    int imgWidth,
	    int imgHeight)
{
    RMcamera3D *c = rmCamera3DNew();
    RMvertex3D eye={5.0, 5.0, 22.0};
    RMvertex3D at={5.0, 5.0, 0.0};
    RMvertex3D up={0.0, 1.0, 0.0};

    rmCamera3DSetEye(c, &eye);
    rmCamera3DSetAt(c, &at);
    rmCamera3DSetHither(c, 5.0F);
    rmCamera3DSetYon(c, 40.0F);
    rmCamera3DSetFOV(c, 45.0F);
    rmCamera3DSetUpVector(c, &up);
    rmCamera3DSetAspectRatio(c, (float)imgWidth/(float)imgHeight);
    rmCamera3DSetProjection(c, RM_PROJECTION_PERSPECTIVE);
    rmNodeSetSceneCamera3D(addTo, c);

    myCamera = c;
}

void
setViewport(RMnode *addTo)
{
    float vp[4] = {0.0F, 0.0F, 1.0F, 1.0F};
    
    rmNodeSetSceneViewport(addTo, vp);
}

void
setFBClear(RMnode *addTo)
{
    RMcolor4D c={0.2, 0.2, 0.3, 1.0};
    
    rmNodeSetSceneBackgroundColor(addTo, &c);
}

void
setArenaLighting(RMnode *addTo)
{
    RMlight *l0;
    RMcolor4D diffuse = {0.3, 0.3, 0.3, 1.0};
    RMcolor4D defAmbient = {0.3, 0.3, 0.3, 1.0};
    RMvertex3D pos    = {5.0, 7.5, 5.0}; /* point light */
    RMlightModel *lm;

    l0 = rmLightNew();
    rmLightSetType(l0, RM_LIGHT_POINT); 
    rmLightSetColor(l0, NULL, &diffuse, &diffuse);
    rmLightSetXYZ(l0, &pos);

    rmNodeSetSceneLight(addTo, RM_LIGHT0, l0);
    rmLightDelete(l0);
    

    lm = rmLightModelNew();
    rmLightModelSetAmbient(lm, &defAmbient);
    rmLightModelSetTwoSided(lm, RM_FALSE);
    rmLightModelSetLocalViewer(lm, RM_TRUE); 
    rmNodeSetSceneLightModel(addTo, lm);
    rmLightModelDelete(lm);
}

void
setSpotLight(RMnode *addTo,
	     RMnode *iconNode,
	     RMvertex3D *pos,
	     RMvertex3D *dir)
{
    /*
     * build a spotlight RMlight, and stick it in the addTo node.
     * create a cone to represent the position, orientation and
     * spread angle of the spotlight, and stick it in the iconNode.
     */
    RMlight *l0;
    RMnode *n=rmNodeNew("spotLight", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    RMprimitive *p = rmPrimitiveNew(RM_CONES);
    RMvertex3D v[2];
    float scale[1];
    RMnode *t;


    if (rmNodeGetNumChildren(iconNode) > 0)
    {
	t = rmNodeGetIthChild(iconNode, 0);
	rmNodeRemoveAllChildren(iconNode);
	rmNodeDelete(t);
    }
    
    rmNodeAddChild(iconNode, n);

    v[1] = *pos;
    v[0].x = pos->x + dir->x;
    v[0].y = pos->y + dir->y;
    v[0].z = pos->z + dir->z;
    scale[0] = (float)(sin(RM_DEGREES_TO_RADIANS(DEFAULT_SPOT_CUTOFF )));

    rmPrimitiveSetVertex3D(p, 2, v, RM_COPY_DATA, NULL);
    rmPrimitiveSetRadii(p, 1, scale, RM_COPY_DATA, NULL);

    rmNodeSetDiffuseColor(n, &spotColor);
    rmNodeSetSpecularColor(n, &spotColor);
    rmNodeSetAmbientColor(n, &spotColor);
    
    rmNodeAddPrimitive(n, p);
    
    l0 = rmLightNew();
    rmLightSetType(l0, RM_LIGHT_SPOT); 
    rmLightSetColor(l0, NULL, &spotColor, &spotColor);
    rmLightSetXYZ(l0, pos);
    rmLightSetSpotDirection(l0, dir);
    rmLightSetSpotCutoff(l0, DEFAULT_SPOT_CUTOFF);
    rmLightSetSpotExponent(l0, spotExponent);

    rmNodeSetSceneLight(addTo, RM_LIGHT1, l0);
    rmLightDelete(l0);
}

void
myInitFunc(RMpipe *p,
	   RMnode *n)
{
    int imgWidth, imgHeight;
    RMnode *objRoot = rmNodeNew("objRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    
    RMvertex3D origin={0.0F, 0.0F, 0.0F};
    RMvertex3D xzMax={10.0F, 0.0F, 10.0F};
    RMvertex3D yzMax1={0.0F, 10.0F, 10.0F};
    RMvertex3D origin2={10.0F, 0.0F, 0.0F};
    RMvertex3D yzMax2={10.0F, 10.0F, 10.0F};
    RMvertex3D origin3={0.0F, 10.0F, 0.0F};
    RMvertex3D xzMax2={10.0F, 10.0F, 10.0F};

    RMvertex3D xyMax={10.0F, 10.0F, 0.0F};

    /* build walls */
    buildXZQuadmesh(objRoot,&origin, &xzMax, SUBDIVISIONS, 1);
    buildXZQuadmesh(objRoot,&origin3, &xzMax2, SUBDIVISIONS, -1);
    buildYZQuadmesh(objRoot, &origin, &yzMax1, SUBDIVISIONS, 1); 
    buildYZQuadmesh(objRoot, &origin2, &yzMax2, SUBDIVISIONS, -1);
    buildXYQuadmesh(objRoot, &origin, &xyMax, SUBDIVISIONS, 1);
    
    rmNodeComputeBoundingBox(objRoot);
    rmNodeComputeCenterFromBoundingBox(objRoot);

    myRoot = rmNodeNew("myRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmPipeGetWindowSize(p, &imgWidth, &imgHeight);

    /* create the 3D viewer for the scene */
    buildCamera(myRoot, imgWidth, imgHeight);

    /* framebuffer clear & viewport specs */
    setFBClear(myRoot); 
    setViewport(n);
    
    rmNodeAddChild(n, myRoot);
    rmNodeAddChild(myRoot, objRoot);
    rmNodeUnionAllBoxes(n);
    rmNodeComputeCenterFromBoundingBox(n);

    /* create non-spot light source & lighting environment */
    setArenaLighting(myRoot);
    
    spotIcon = rmNodeNew("spotIcon", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    /* create the spotlight. */
    setSpotLight(myRoot, spotIcon, &spotPosition, &spotDirection);
    
    rmNodeAddChild(myRoot, spotIcon);

    /* the dummyNode is used to harvest rotation matrices from the
     rmaux routines that do trackball-style rotations. we'll use the
     rotation matrix to change the orientation of the spotlight. */
    dummyNode = rmNodeNew("dummyNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);

    /* attach the trackball rotation to the dummy node. */
    rmauxSetGeomTransform(dummyNode, myPipe);

    /* you can move the viewpoint around */
    rmauxSetCamera3DTransform(myRoot, p);

    /*
     * set handler to reset aspect ratio when the window is resized.
     */
    rmauxSetResizeFunc(p, myRoot, rmauxDefaultResizeFunc);
    
    if (doStats != 0)
	rmStatsComputeDemography(n);
    
    /* cough up one frame - needed for multistage rendering so that
     the user isn't presented an empty window. */
    if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE)
	rmFrame(p, n);
    
    rmFrame(p, n);
}

void
appDrawFunc(RMpipe *p, RMnode *n)
{
    RMvertex3D newDir;
    RMmatrix m;

    /* move the spotlight around according to the current rotation
     matrix computed by rmaux. we transform the original spotlight
     direction by the rotation matrix of the dummyNode. */
    
    newDir = spotDirection;
    if ((rmNodeGetRotateMatrix(dummyNode,&m)) != RM_WHACKED)
	rmPointMatrixTransform(&newDir, &m, &newDir);

    setSpotLight(myRoot, spotIcon, &spotPosition, &newDir);

    if (doStats != 0)
	rmStatsStartTime();
    
    /* redraw the scene */
    rmFrame(p, n);
    
    if (doStats != 0)
    {
	rmStatsEndTime();
	rmStatsPrint();
    }
}

void
usage(char *s)
{
    printf("usage: %s [-w imgWidthPixels ] [-h imgHeightPixels] [-s (turn on frame rate statistics)]\n",s);
}

void
parseArgs(int ac,
	  char *av[],
	  int *imgWidth,
	  int *imgHeight)
{
    int i;

    i = 1;
    while (i < ac)
    {
	if (strcmp(av[i],"-w") == 0)
	{
	    i++;
	    ac--;
	    sscanf(av[i],"%d", imgWidth);
	}
	else if (strcmp(av[i],"-h") == 0)
	{
	    i++;
	    ac--;
	    sscanf(av[i],"%d", imgHeight);
	}
	else if (strcmp(av[i],"-s") == 0)
	    doStats = 1;
	else
	{
	    usage(av[0]);
	    exit(-1);
	}
	i++;
    }
}

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

    imgWidth = DEFAULT_IMAGE_WIDTH;
    imgHeight = DEFAULT_IMAGE_HEIGHT;
    
    parseArgs(__argc, __argv, &imgWidth, &imgHeight);
    
#else  /* assume RM_X */
int
main(int argc,
     char *argv[])
{
    void *msg;			/* needed for rmauxEventLoop
				 win32/unix API consistency */
    int imgWidth, imgHeight;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_GLX;

    imgWidth = DEFAULT_IMAGE_WIDTH;
    imgHeight = DEFAULT_IMAGE_HEIGHT;
    
    parseArgs(argc, argv, &imgWidth, &imgHeight);
#endif

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

    /* 
     * create the rendering pipe. this step is required in both
     * Win32 and X.
     */
    myPipe = rmPipeNew(targetPlatform);
    
    processingMode = RM_PIPE_MULTISTAGE;
    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,imgWidth,imgHeight,"RM for Windows",
			       hInstance,fptr);
	if (hWnd == 0)
	  exit(-1);

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

	w = rmauxCreateXWindow(myPipe,
			       (Window)NULL, /* parent window */
			       0,0,imgWidth,imgHeight,
			       "RM for X-Windows","icon-title",RM_TRUE);

	/* 
	 * assign the window to the rendering pipe.
	 */
	rmPipeSetWindow(myPipe,w,imgWidth,imgHeight);

    }
#endif

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

    rmauxSetInitFunc(myInitFunc);
    rmauxSetRenderFunc(appDrawFunc);

    /*
     * set key handler function so this prog will exit on "q" key.
     */
    rmauxSetKeyFunc(myPipe, rmauxDefaultKeyFunc);

    rmauxEventLoop(myPipe,rmRootNode(), &msg);

    rmPipeDelete(myPipe);
    rmFinish();

    return(1);
}
   
