/*
 * 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: tmap2d.c,v 1.14 2005/06/08 18:34:16 wes Exp $
 * $Revision: 1.14 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: tmap2d.c,v $
 * Revision 1.14  2005/06/08 18:34:16  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.13  2003/04/13 18:13:23  wes
 * Updated copyright dates.
 *
 * Revision 1.12  2003/01/27 05:07:07  wes
 * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
 *
 * Revision 1.11  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.10  2002/06/17 00:44:52  wes
 * Replaced rmSubtreeFrame with rmFrame.
 *
 * Revision 1.9  2001/10/15 00:24:52  wes
 * Use of RM_SOFTWARE resize engine.
 *
 * Revision 1.8  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.7  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.6  2001/03/31 18:47:48  wes
 * Added intial transformation to object is visible at startup, changed
 * texture map resize from RM_HARDWARE to RM_SOFTWARE to guarantee
 * compatiblity with all forms of RMpipe multistage processing.
 *
 * Revision 1.5  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.4  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.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/20 18:04:34  wes
 * Minor tweaks and reorg for OpenRM 1.2.1.
 *
 * Revision 1.1.1.1  2000/02/28 21:55:30  wes
 * OpenRM 1.2 Release
 *
 * Revision 1.9  2000/02/28 17:21:55  wes
 * RM 1.2, pre-OpenRM
 *
 */


#include <rm/rm.h>   
#include <rm/rmaux.h>
#include <rm/rmi.h>

#include "libdio.h"
#include "procmode.h"

static char MyRootName[]={"MyRoot"};
static RMnode *MyRoot;
int img_width=600;
#ifdef RM_X
int img_height=512;
#else  /* assume RM_WIN */
int img_height = 548;
#endif
int do_mipmaps=0;
int prim_type=0;
static char image_filename[]={"data/doghead.jpg"};

void
usage(char *av[])
{
    char buf[256];
    sprintf(buf," usage: %s [-w img_width] [-h img_height] [-mips (compute all mipmaps)]  [-t prim_type (0=single quad, 1=disjoint tris, 2=t-strip, 3=t-fan)] [-i fname (name of avs image file to use as a texture) \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 if (strcmp(av[i],"-mips") == 0)
	{
	    do_mipmaps = 1;
	}
	else if (strcmp(av[i],"-t") == 0)
	{
	    i++;
	    sscanf(av[i],"%d",&prim_type);
	}
	else if (strcmp(av[i],"-i") == 0)
	{
	    i++;
	    strcpy(image_filename,av[i]);
	}
	else
	{
	    usage(av);
	    exit(-1);
	}
	i++;
    }
}

void
my_set_scene(RMnode *camNode,
	     int stereo_format)
{
    RMcamera3D *c=rmCamera3DNew();

    /* assign a default view */
    rmDefaultCamera3D(c);

    /* adjust the view so all geom is visible */
    rmCamera3DComputeViewFromGeometry(c,MyRoot,img_width,img_height);

    /* tweak stereo parms if necessary. */
    if (stereo_format != RM_MONO_CHANNEL)
    {
	rmCamera3DSetStereo(c,RM_TRUE);
	rmCamera3DSetEyeSeparation(c,2.5F);
	rmCamera3DSetFocalDistance (c,0.707F);

    }

    /* then update the camera on the node */
    rmNodeSetSceneCamera3D(camNode,c);

    /* free the copy we were working with. */
    rmCamera3DDelete(c);
    
    /* use RM's default lighting model */
    rmDefaultLighting(camNode);
}

void
build_and_add_quad(RMnode *addto)
{
    RMnode *b;
    RMprimitive *p;
    RMvertex3D v[4];
    RMvertex2D tc[4];
    RMvertex3D n[4];

    b = rmNodeNew("outline box",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    
    p = rmPrimitiveNew(RM_QUADS); 

    v[0].x = v[0].y = v[0].z = 0.0;
    VCOPY(v,v+1);
    v[1].x = 1.0;

    VCOPY(v+1,v+2);
    v[2].z = 1.0;
    
    VCOPY(v+2,v+3);
    v[3].x = 0.0;
    
    n[0].x = n[0].z = 0.0;
    n[0].y = -1.0;
    n[1] = n[0];
    n[2] = n[0];
    n[3] = n[0];
    
    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;

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

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

    rmPrimitiveSetTexcoord2D(p,4,tc, RM_COPY_DATA, NULL); 

    rmNodeAddPrimitive(b,p);
    
    rmNodeComputeBoundingBox(b);
    rmNodeAddChild(addto, b);
}
    
void
build_and_add_distris(RMnode *addto)
{
    RMnode *b;
    RMprimitive *p;
    RMvertex3D v[6];
    RMvertex2D tc[6];
    RMvertex3D n[6];

    b = rmNodeNew("outline box",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    
    p = rmPrimitiveNew(RM_TRIANGLES); 

    v[0].x = v[0].y = v[0].z = 0.0;
    VCOPY(v,v+1);
    v[1].x = 1.0;

    VCOPY(v+1,v+2);
    v[2].z = 1.0;

    VCOPY(v,v+3);
    VCOPY(v+3,v+4);
    v[4].x = v[4].z = 1.0;

    VCOPY(v,v+5);
    v[5].z = 1.0;
    
    n[0].x = n[0].z = 0.0;
    n[0].y = -1.0;
    VCOPY(n,n+1);
    VCOPY(n,n+2);
    VCOPY(n,n+3);
    VCOPY(n,n+4);
    VCOPY(n,n+5);
    
    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 = tc[3].y = 0.0;
    tc[4].x = tc[4].y = 1.0;
    tc[5].x = 0.0;
    tc[5].y = 1.0;
    
    rmPrimitiveSetVertex3D(p,6,v,RM_COPY_DATA,NULL);

    rmPrimitiveSetNormal3D(p,6,n,RM_COPY_DATA, NULL);

    rmPrimitiveSetTexcoord2D(p,6,tc,RM_COPY_DATA, NULL);

    rmNodeAddPrimitive(b,p);
    
    rmNodeComputeBoundingBox(b);
    rmNodeAddChild(addto, b);
}
    
void
build_and_add_tstrip(RMnode *addto)
{
    RMnode *b;
    RMprimitive *p;
    RMvertex3D v[4];
    RMvertex2D tc[4];
    RMvertex3D n[4];

    b = rmNodeNew("outline box",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    
    p = rmPrimitiveNew(RM_TRIANGLE_STRIP); 

    v[0].x = v[0].y = v[0].z = 0.0;
    v[1].x = 1.0; v[1].y = v[1].z = 0.0;
    v[2].x = v[2].y = 0.0;  v[2].z = 1.0;
    v[3].x = v[3].z = 1.0; v[3].y = 0.0;

    n[0].x = n[0].z = 0.0;
    n[0].y = -1.0;
    VCOPY(n,n+1);
    VCOPY(n+1,n+2);
    VCOPY(n+2,n+3);
    
    tc[0].x = tc[0].y = 0.0;
    
    tc[1].x = 1.0;
    tc[1].y = 0.0;

    tc[2].x = 0.0;
    tc[2].y = 1.0;

    tc[3].x = tc[3].y = 1.0;
    
    rmPrimitiveSetVertex3D(p,4,v,RM_COPY_DATA,NULL);

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

    rmPrimitiveSetTexcoord2D(p,4,tc,RM_COPY_DATA,NULL);

    rmNodeAddPrimitive(b,p);
    
    rmNodeComputeBoundingBox(b);
    rmNodeAddChild(addto, b);
}
    
void
build_and_add_tfan(RMnode *addto)
{
    RMnode *b;
    RMprimitive *p;
    RMvertex3D v[4];
    RMvertex2D tc[4];
    RMvertex3D n[4];

    b = rmNodeNew("outline box",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    
    p = rmPrimitiveNew(RM_TRIANGLE_FAN); 

    v[0].x = v[0].y = v[0].z = 0.0;
    v[1].x = 1.0; v[1].y = v[1].z = 0.0;
    v[2].x = v[2].z = 1.0;  v[2].y = 0.0;
    v[3].x = v[3].y = 0.0; v[3].z = 1.0; 

    n[0].x = n[0].z = 0.0;
    n[0].y = -1.0;
    VCOPY(n,n+1);
    VCOPY(n+1,n+2);
    VCOPY(n+2,n+3);
    
    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;
    
    rmPrimitiveSetVertex3D(p,4,v,RM_COPY_DATA,NULL);

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

    rmPrimitiveSetTexcoord2D(p,4,tc,RM_COPY_DATA,NULL);

    rmNodeAddPrimitive(b,p);
    
    rmNodeComputeBoundingBox(b);
    rmNodeAddChild(addto, b);
}
    
void
my_build_objs(RMpipe *p)
{
    RMimage *mytexture,*resizedtexture;
#if 0
    /* RM_HARDWARE does not function reliably when using multithreaded
     and multistage rendering - the OpenGL pipe isn't really initialized
     yet at this point. */
    int resize_engine = RM_HARDWARE;  /* or RM_SOFTWARE */
#endif
    int resize_engine = RM_SOFTWARE;   

    MyRoot = rmNodeNew("MyRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeSetName(MyRoot,MyRootName); /* and has a name.. */
    
    /*
     * read the texture image...
     */

/*    mytexture = dioReadAVSImage(image_filename); */
    mytexture = rmiReadJPEG(image_filename);
    rmImageMirror(mytexture, RM_IMAGE_MIRROR_HEIGHT);
    
    if (mytexture == NULL)
    {
	fprintf(stderr," problem reading the texture. exiting..\n");
	exit(-1);
    }

    /*
     * it's probably not the right size...not an even power of two.
     * fix that.
     */
    {
	int w,h,nw,nh;
	
	rmImageGetImageSize(mytexture,NULL,&w,&h,NULL,NULL,NULL);
	nw = rmNearestPowerOfTwo(w);
	nh = rmNearestPowerOfTwo(h);

	resizedtexture = rmImageNew(2,nw,nh,1,rmImageGetFormat(mytexture),
				    rmImageGetType(mytexture),
				    RM_COPY_DATA);

	rmImageResize(mytexture,resizedtexture,resize_engine,p);
    }

    /*
     * we now have an image of size 2**N,2**M.  build an RMtexture
     * object for use as a scene parameter. populate the RMtexture
     * with the new resized image, or build mipmaps.
     */
    {
	RMtexture *t;
	int mipmap_count = 1;
	RMimage **mipmaps;

	t = rmTextureNew(2);

	if (do_mipmaps == 1)
	{
	    rmTextureSetFilterMode(t,GL_LINEAR_MIPMAP_LINEAR,GL_NEAREST); 
	    mipmap_count = rmImageBuildMipmaps(resizedtexture,
					&mipmaps,resize_engine,p);
	}
	else
	    mipmaps = &resizedtexture;

	rmTextureSetImages(t,mipmaps,mipmap_count,RM_FALSE);
	
	rmNodeSetSceneTexture(MyRoot,t); 

	/*
	 * don't need to rmTextureDelete() the texture because RM will
	 * manage that for us.
	 */
    }
 
    if (prim_type == 0)
	build_and_add_quad(MyRoot);
    else if (prim_type == 1)
	build_and_add_distris(MyRoot);
    else if (prim_type == 2)
	build_and_add_tstrip(MyRoot);
    else if (prim_type == 3)
	build_and_add_tfan(MyRoot);

    rmNodeAddChild(rmRootNode(),MyRoot); 

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

    rmNodeComputeCenterFromBoundingBox(MyRoot);
    rmNodeComputeCenterFromBoundingBox(rmRootNode());
    
    {
	RMcolor4D bgcolor={0.2,0.2,0.3,1.0};
	rmNodeSetSceneBackgroundColor(MyRoot,&bgcolor);
    }

    rmImageDelete(mytexture);	/* don't need it any more */
}

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
setInitialTransform(RMnode *n)
{
    RMmatrix m;
    double c, s, r= RM_DEGREES_TO_RADIANS(-135.0);

    c = cos(r);
    s = sin(r);
    rmMatrixIdentity(&m);
    m.m[1][1] = m.m[2][2] = c;
    m.m[2][1] = -s;
    m.m[1][2] = s;
    rmNodeSetRotateMatrix(n, &m);
}

void
myinitfunc(RMpipe *p,
	   RMnode *n)
{
    my_build_objs(p);
    my_set_scene(n, rmPipeGetChannelFormat(p));
    rmauxSetGeomTransform(MyRoot,p);
    rmauxSetCamera3DTransform(n, p);
    setInitialTransform(MyRoot);
    
    /*
     * 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 channelFormat;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum targetPlatform = RM_PIPE_WGL;
    RMpipe *lone_pipe=NULL;

    parse_args(__argc,__argv);
#else  /* assume RM_X */
int
main(int ac,
     char *av[])
{
    RMpipe *lone_pipe=NULL;
    RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */
    RMenum channelFormat;
    RMenum targetPlatform = RM_PIPE_GLX;
    void *msg;			/* needed for rmauxEventLoop
				 win32/unix API consistency */

    processingMode = RM_PIPE_SERIAL; /* tmp wes 10/17/02 */
    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);
    
    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 */
			       0,0,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","RM",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, rmPipeInit 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);
    rmauxSetRenderFunc(myRenderFunc);
    
    rmauxEventLoop(lone_pipe,rmRootNode(), &msg);

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