/*
 * Copyright (C) 2000-2008, 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: tcube.c,v 1.12 2008/11/09 14:37:04 wes Exp $
 * $Revision: 1.12 $
 * $Name: v180-alpha-01 $
 * $Log: tcube.c,v $
 * Revision 1.12  2008/11/09 14:37:04  wes
 * Update dates to 2008
 *
 * Revision 1.11  2006/08/08 03:49:21  wes
 * Updates to reflect ergonomic changes to rmaux
 *
 * Revision 1.10  2005/06/08 16:19:35  wes
 * Code cleanup to elimiate compiler warnings.
 *
 * Revision 1.9  2003/11/05 15:03:35  wes
 * Fixed printf misinformation in usage().
 *
 * Revision 1.8  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.7  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.6  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.5  2002/06/17 00:43:21  wes
 * updated copyright line.
 *
 * Revision 1.4  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.3  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.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:51  wes
 * Initial entry.
 *
 * Revision 1.1.1.1  2000/09/02 17:23:53  wes
 * Initial entry.
 *
 */

/*
 * usage: tcube [no args]
 *
 * this demo program creates a "room" with textured walls, floor and
 * ceiling. at this time (9/2000), that's all there is. use the
 * CAVE simulator or live input devices to navigate around. in the
 * future, this demo will be more interesting.
 *
 * for more information about the CAVE library, visit the VRCO
 * website at http://www.vrco.com/.
 */

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

static RMpipe *myPipe=NULL;
static RMnode *MyRoot=NULL;

/*
 * it would be nice to obtain CAVE parms using CAVELib, but this
 * is not possible at this time. we need this information in order
 * to construct geometry that defines the walls of the CAVE.
 */

#define IMG_WIDTH 640
#define IMG_HEIGHT 525

#define CAVEWIDTH 10.0
#define CAVEHEIGHT 10.0
#define CAVEDEPTH 10.0		/* not part of CAVELib */

#define CAVEORIGIN_X 5.0	/* origin in config file */
#define CAVEORIGIN_Y 0.0
#define CAVEORIGIN_Z 5.0

static RMvertex3D caveMin = {0.0F, 0.0F, 0.0F};
static RMvertex3D caveMax = {10.0F, 10.0F, 10.0F};

RMimage
*readAVSImage(const char *fname)
{
    RMimage *t;
    int w,h;
    int i,j,indx, indx2;
    FILE *f;
    unsigned char *static_image_data;
    float *static_image_data_float;

    f = fopen(fname,"r");
    if (f == NULL)
    {
	fprintf(stderr," can't open the AVS image file named <%s> \n",fname);
	return(NULL);
    }

    {
        unsigned char buf[4];
	read(fileno(f),(void *)buf,4);
	w = buf[3] | (buf[2] << 8);
	read(fileno(f),(void *)buf,4);
	h = buf[3] | (buf[2] << 8);
    }

    /* sanity check w,h */

    if ((w < 0) || (w > 4096) ||
	(h < 0) || (h > 4096))
    {
	fprintf(stderr," dubious image dimensions: %d, %d, aborting image read. \n",w,h);
	fclose(f);
	return(NULL);
    }

    static_image_data = (unsigned char *)malloc(sizeof(unsigned char)*w*h*4);

    /* this reads everything at once */
    read(fileno(f),(void *)static_image_data,w*h*4);

    fclose(f);

#define ONE_OVER_255 0.0039215686
    
    /* hack to change from ARGB to RGBA */
    indx = 0;
    indx2 = 3;
    for (i=0;i<w*h;i++)
    {
        unsigned char t;

	t = static_image_data[indx];
	for (j=0;j<3;j++)
	    static_image_data[indx+j] = static_image_data[indx+j+1];
	static_image_data[indx+3] = 0xFF;

	indx += 4;
    }

#define IMAGE_TYPE RM_UNSIGNED_BYTE
    
    if (IMAGE_TYPE == RM_FLOAT)
    {
	static_image_data_float = (float *)malloc(sizeof(float)*w*h*4);
	for (i=0;i<w*h*4;i++)
	    static_image_data_float[i] = (float)(static_image_data[i])*
		ONE_OVER_255;
    }

    /* 
     * problem in win-32 with RM_COPY_DATA: not sure what's up, top
     * part of image gets chewed off.
     *
     * problem w/RGB mode in glDrawPixels . it looks like it wants all
     * the image data to be 4-byte word aligned.
     */


    t = rmImageNew(2,w,h,1,RM_IMAGE_RGBA,
		   IMAGE_TYPE,
		   RM_COPY_DATA);  

    if (IMAGE_TYPE == RM_FLOAT)
    {
	rmImageSetPixelData(t, (void *)static_image_data_float,
			    RM_COPY_DATA,
			    NULL);
    }
    else
    {
	rmImageSetPixelData(t, (void *)static_image_data,
			    RM_COPY_DATA,
			    NULL);
    }

    rmImageMirror(t,RM_IMAGE_MIRROR_HEIGHT);

    return(t);
}

RMtexture *
loadTexture(const char *fname)
{
    RMtexture *t;
    RMimage *img;

    img = readAVSImage(fname);
    if (img == NULL)		/* error msg? */
	return(NULL);

    t = rmTextureNew(2);
    rmTextureSetImages(t,&img,1,RM_FALSE);
    return(t);
}

void
makeMarker(RMnode *addToNode,
	   float xval,
	   float yval,
	   float zval,
	   char *string)
{
    RMnode *n;
    RMprimitive *p;
    RMvertex3D v;
    char *strings[1];

    v.x = xval;
    v.y = yval;
    v.z = zval;

    n = rmNodeNew(string,RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    p = rmPrimitiveNew(RM_TEXT);
    rmPrimitiveSetVertex3D(p, 1, &v, RM_COPY_DATA,NULL);
    strings[0] = string;
    rmPrimitiveSetText(p, 1, strings);
    
    rmNodeAddPrimitive(n,p);
    rmNodeComputeBoundingBox(n);
    rmNodeAddChild(addToNode,n);
}

void
buildTexturedWall(RMnode *addToNode, /* result */
		  RMvertex3D *v, /* 4x 3D verts */
		  RMvertex3D *n, /* single 3D normals */
		  char *nname,	/* name for node */
		  char *fname)	/* file name for texture */
{
    RMnode *wallNode;
    RMprimitive *wallPrim;
    RMtexture *t;
    RMvertex3D n2[4];
    RMvertex2D tc[4];
    
    wallNode = rmNodeNew(nname, RM_RENDERPASS_3D,
			 RM_RENDERPASS_OPAQUE);
    wallPrim = rmPrimitiveNew(RM_QUADS);

    n2[0] = n2[1] = n2[2] = n2[3] = *n;
    
    rmPrimitiveSetVertex3D(wallPrim,4,v,RM_COPY_DATA,NULL);
    rmPrimitiveSetNormal3D(wallPrim,4,n2,RM_COPY_DATA,NULL);
    
    tc[0].x = tc[0].y = 0.0;
    tc[1].x = 1.0;
    tc[1].y = 0.0;
    tc[2].x = tc[2].y = 1.0;
    tc[3].x = 0.0;
    tc[3].y = 1.0;
    
    rmPrimitiveSetTexcoord2D(wallPrim,4,tc,RM_COPY_DATA,NULL);

    rmNodeAddPrimitive(wallNode,wallPrim);
    rmNodeAddChild(addToNode, wallNode);
    rmNodeComputeBoundingBox(wallNode);

    t = loadTexture(fname);
    rmNodeSetSceneTexture(wallNode, t);
    rmTextureDelete(t, RM_TRUE); 
}

void
buildAndTextureWalls(RMnode *addToNode)
{
    RMvertex3D cmin, cmax;
    RMvertex3D wmin, wmax;
    RMnode *wallsRoot;
    RMvertex3D v[4], normal;

    /* compute the corners (min/max) of the physical cave - we want
     our virtual walls to correspond exactly with the physical dislay.
     it would be nice if we could obtain the necessary info directly
     from CAVELib - until that time, the constants are defined above and
     will require manual modification if they don't match the values
     in the CAVEConfig file. the values above were chosen to match
     those that are the defaults. */

    cmin = caveMin;
    cmax = caveMax;

    wallsRoot = rmNodeNew("wallsRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);

    wmin = cmin;
    wmax = cmax;

    /* front wall, at z=0 plane */
    
    v[0] = wmin;
    v[1] = wmin;
    v[1].x = wmax.x;
    v[2] = v[1];
    v[2].y = wmax.y;
    v[3] = v[2];
    v[3].x = wmin.x;
    
    normal.x = normal.y = 0.0F;
    normal.z = 1.0F;

    buildTexturedWall(wallsRoot, v, &normal, "frontWall","data/front.x");

    /* back wall, at z=10 plane */

    v[0] = wmax;
    v[0].y = wmin.y;
    v[1] = v[0];
    v[1].x = wmin.x;
    v[2] = v[1];
    v[2].y = wmax.y;
    v[3] = v[2];
    v[3].x = wmax.x;
    
    normal.x = normal.y = 0.0F;
    normal.z = -1.0F;

    buildTexturedWall(wallsRoot, v, &normal, "backWall","data/back.x");

    /* floor, at y=0 plane*/
    
    v[0] = wmin;
    v[0].z = wmax.z;

    v[1] = v[0];
    v[1].x = wmax.x;

    v[2] = v[1];
    v[2].z = wmin.z;

    v[3] = v[2];
    v[3].x = wmin.x;

    normal.x = normal.z = 0.0F;
    normal.y = 1.0F;

    buildTexturedWall(wallsRoot, v, &normal, "floor","data/floor.x");

    /* ceiling, at y=10 plane*/

    v[0] = wmin;
    v[0].y = wmax.y;

    v[1] = v[0];
    v[1].x = wmax.x;

    v[2] = v[1];
    v[2].z = wmax.z;

    v[3] = v[2];
    v[3].x = wmin.x;

    normal.x = normal.z = 0.0F;
    normal.y = -1.0F;

    buildTexturedWall(wallsRoot, v, &normal, "ceiling","data/bigsky.x");

    /* build left wall , at x=0 plane*/

    v[0] = wmin;
    v[0].z = wmax.z;

    v[1] = v[0];
    v[1].z = wmin.z;

    v[2] = v[1];
    v[2].y = wmax.y;

    v[3] = v[2];
    v[3].z = wmax.z;

    normal.y = normal.z = 0.0F;
    normal.x = 1.0F;

    buildTexturedWall(wallsRoot, v, &normal, "leftWall","data/left.x");

    /* build right wall , at x=10 plane*/

    v[0] = wmin;
    v[0].x = wmax.x;

    v[1] = v[0];
    v[1].z = wmax.z;

    v[2] = v[1];
    v[2].y = wmax.y;

    v[3] = v[2];
    v[3].z = wmin.z;

    normal.y = normal.z = 0.0F;
    normal.x = -1.0F;

    buildTexturedWall(wallsRoot, v, &normal, "rightWall","data/right.x");

    /* these text strings are added to the scene as a "sanity check" */
    makeMarker(addToNode, 0.0, 0.0, 0.0, "0 0 0");
    makeMarker(addToNode, 10.0, 0.0, 10.0, "10 0 10");
    makeMarker(addToNode, 0.0, 0.0, 10.0, "0 0 10");
    
    rmNodeAddChild(addToNode, wallsRoot);
    rmNodeUnionAllBoxes(wallsRoot);
    rmNodeComputeCenterFromBoundingBox(wallsRoot);
}

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

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

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

    lm = rmLightModelNew();
    rmLightModelSetAmbient(lm, &defAmbient);
    rmLightModelSetTwoSided(lm, RM_FALSE);
    rmLightModelSetLocalViewer(lm, RM_FALSE); 
    rmNodeSetSceneLightModel(addToNode, lm);
    rmLightModelDelete(lm);

}

void
buildInitialSceneGraph(void)
{

    MyRoot = rmNodeNew("MyRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    /* obtain the coords of the CAVE walls, and build a set of
     6 walls that are textured. */
    
    buildAndTextureWalls(MyRoot);
    rmNodeUnionAllBoxes(MyRoot);
    rmNodeComputeCenterFromBoundingBox(MyRoot);
    
    setArenaLighting(rmRootNode());

    {
	RMcolor4D c={0.2, 0.2, 0.3, 1.0};
	float one = 1.0F;

	rmNodeSetSceneBackgroundColor(MyRoot, &c);
	rmNodeSetSceneDepthValue(MyRoot, &one);
    }
    rmNodeAddChild(rmRootNode(), MyRoot);
}

void
myInitPipe()
{

#if 0
    int myWidth, myHeight, myOriginX, myOriginY;
    myPipe = rmPipeNew();

    /*
     * we have to manually assign needed parameters to the Pipe
     * using the values set up by CAVElib.
     */

    /* want a channel format that doesn't blow the matrix stack
     upon entry. */
    rmPipeSetChannelFormat(myPipe, RM_MONO_CHANNEL);
    rmPipeSetInitMatrixStackMode(myPipe, RM_FALSE);

    /* turn off swapbuffers - CAVElib handles that. */
    rmPipeSetSwapbuffersFunc(myPipe, NULL);

    /* obtain the OpenGL context from CAVElib, assign that to the pipe */
    rmxPipeSetContext(myPipe, CAVEGLXContext());
    rmxPipeSetDisplay(myPipe, CAVEXDisplay());

    /* obtain and assign window geometry */
    CAVEGetWindowGeometry(&myOriginX, &myOriginY, &myWidth, &myHeight);
    rmPipeSetWindow(myPipe, CAVEXWindow(), myWidth, myHeight);
	
    rmPipeMakeCurrent(myPipe);
#endif
}

void
appInitGL(RMpipe *p,
	  RMnode *n,
	  void *usrData)
{
    /* build the scene graph */
    buildInitialSceneGraph();

    rmNodeUnionAllBoxes(n);
    rmNodeComputeCenterFromBoundingBox(n);

    /* set tmp camera */
    {
	RMcamera3D *c = rmCamera3DNew();
	rmNodeUnionAllBoxes(n);
	rmCamera3DComputeViewFromGeometry(c, MyRoot, IMG_WIDTH, IMG_HEIGHT);
	rmCamera3DSetProjection(c, RM_PROJECTION_PERSPECTIVE);
	rmNodeSetSceneCamera3D(n, c);
	rmauxSetGeomTransform(MyRoot, myPipe);
	rmauxSetCamera3DTransform(n, myPipe);
	rmCamera3DDelete(c);
    }
    
    /*
     * 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
appDrawFunc(void)
{
    rmFrame(myPipe, rmRootNode());
}

void
usage(char *s)
{
    printf("usage: %s [no args] \n", s);
}

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

    i = 1;
    while (i < ac)
    {
	{
	    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 img_width, img_height;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_WGL;

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

    img_width = IMG_WIDTH;
    img_height = IMG_HEIGHT;
    
    parseArgs(argc, argv);
#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,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);
    }
#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

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

    rmauxSetInitCallbackFunc(appInitGL, NULL);
    
    /*
     * set key handler function so this prog will exit on "q" key.
     */
    rmauxSetKeypressCallbackFunc(rmauxDefaultKeyFunc, NULL);
    
    rmauxEventLoop(myPipe,rmRootNode(), &msg);

    rmPipeDelete(myPipe);
    rmFinish();

    return(1);
}
/* EOF */ 
