/*
 * Copyright (C) 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: appTexturedPrims.c,v 1.2 2005/06/08 18:31:41 wes Exp $
 * $Revision: 1.2 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: appTexturedPrims.c,v $
 * Revision 1.2  2005/06/08 18:31:41  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.1  2005/02/27 19:38:10  wes
 * Initial entry.
 *
 */

/*
 * purpose: this demo application shows how to use (1) application-supplied
 * texture object IDs and (2) application-supplied display list IDs. The
 * program uses an application-supplied texture object ID with an
 * RM quadmesh primitive, and uses an RM-managed texture with an
 * application-supplied display list ID for the underlying geometry.
 *
 * usage: autoTmapAppTextures [-w imgWidthPixels -h imgHeightPixels]
 */

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

static RMpipe *myPipe=NULL;

#define DEFAULT_IMAGE_WIDTH 640
#define DEFAULT_IMAGE_HEIGHT 525

void
makeSphereVertsNormalsTCs(float radius,
			  float tcLimit,
			  int numLongitudes,
			  int numLatitudes,
			  RMvertex3D **vReturn,
			  RMvertex3D **nReturn,
			  RMvertex2D **tcReturn)
{
    double lon, dLon, lat, dLat, dRadius;
    int i, j, indx;
    extern double sin(), cos();
    RMvertex3D *v=NULL, *t=NULL, *n;
    RMvertex2D *tc=NULL;

    t = rmVertex3DNew(numLongitudes+1);

    dRadius = (double)radius;

    /* this hunk of code makes a circle */
    lon = 0.0;
    dLon = (2.0*RM_PI)/(double)(numLongitudes);
    for (i=0; i <= numLongitudes; i++, lon += dLon)
    {
	double c, s;
	
	c = cos(lon) * dRadius;
	s = sin(lon) * dRadius;

	t[i].x = c;
	t[i].z = s;
	t[i].y = 0.0F;
    }

    /* now, take the basic circle vertices and stitch together a quadmesh */
    v = rmVertex3DNew((numLatitudes+1)*(numLongitudes+1));
    n = rmVertex3DNew((numLatitudes+1)*(numLongitudes+1));
    lat = 0.0;
    dLat = (RM_PI)/(double)(numLatitudes);
    indx = 0;

    for (j=0; j <= numLatitudes; j++, lat += dLat)
    {
	double c, s;
	
	c = cos(lat) * dRadius;
	s = sin(lat);

	for (i=0; i <= numLongitudes; i++, indx++)
	{
	    v[indx].y = c;
	    v[indx].x = t[i].x * s;
	    v[indx].z = t[i].z * s;

	    n[indx].x = v[indx].x / dRadius;
	    n[indx].y = v[indx].y / dRadius;
	    n[indx].z = v[indx].z / dRadius;
	}
    }

    /* the TCs are easy */
    tc = rmVertex2DNew((numLatitudes+1)*(numLongitudes+1));
    lat=0.0;
    dLat = (double)(tcLimit)/(double)numLatitudes;
    indx = 0;
    
    for (j=0; j <= numLatitudes; j++, lat += dLat)
    {
	lon = 0.0;
	dLon = (double)tcLimit/(double)(numLongitudes);
	
	for (i=0; i <= numLongitudes; i++, indx++, lon += dLon)
	{
	    tc[indx].x = -lon;
	    tc[indx].y = -lat;
	}
    }

    rmVertex3DDelete(t);
    *vReturn = v;
    *nReturn = n;
    *tcReturn = tc;
}

RMprimitive *
makeSpherePrimitive(float radius,
		    float tcLimit,
		    int numLongitudes,
		    int numLatitudes)
{
    RMvertex3D *v=NULL, *n=NULL;
    RMvertex2D *tc=NULL;
    RMprimitive *p = rmPrimitiveNew(RM_QUADMESH);

    makeSphereVertsNormalsTCs(radius, tcLimit, numLongitudes, numLatitudes, &v, &n, &tc);

    rmPrimitiveSetVertex3D(p, (numLatitudes+1)*(numLongitudes+1), v, RM_COPY_DATA, NULL);
    rmPrimitiveSetNormal3D(p, (numLatitudes+1)*(numLongitudes+1), n, RM_COPY_DATA, NULL);
    rmPrimitiveSetTexcoord2D(p, (numLatitudes+1)*(numLongitudes+1), tc, RM_COPY_DATA, NULL);
    rmPrimitiveSetQmeshDims(p, numLongitudes+1, numLatitudes+1);

    /* free up verts, etc */
    rmVertex3DDelete(n);
    rmVertex3DDelete(v);
    rmVertex2DDelete(tc);

    return p;
}

GLuint
makeSphereDisplayList(float radius,
		      float tcLimit,
		      int numLongitudes,
		      int numLatitudes)
{
    RMvertex3D *v=NULL, *n=NULL;
    RMvertex2D *tc=NULL;
    GLuint l;
    int i, j, indx, rowOffset;

    makeSphereVertsNormalsTCs(radius, tcLimit, numLongitudes, numLatitudes, &v, &n, &tc);

    l = glGenLists(1);
    glNewList(l, GL_COMPILE);

    indx = 0;
    rowOffset = numLongitudes+1;
    
    for (j=0;j<numLatitudes;j++)
    {
	glBegin(GL_QUAD_STRIP);
	for (i=0;i<numLongitudes+1;i++,indx++)
	{
	    glTexCoord2fv((float *)(tc+indx));
	    glNormal3fv((float *)(n+indx));
	    glVertex3fv((float *)(v+indx));
			
	    glTexCoord2fv((float *)(tc+indx+rowOffset));
	    glNormal3fv((float *)(n+indx+rowOffset));
	    glVertex3fv((float *)(v+indx+rowOffset));
	}
	glEnd();
    }

    glEndList();
    return l;
}

void
makeRMTexturedAppPrim(RMnode *addTo)
{
    RMimage *i = rmiReadJPEG("data/seaSurfaceTemperature.jpg");
    RMtexture *t = rmTextureNew(2);
    RMnode *textureNode = rmNodeNew("textureNode", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
    RMnode *sphereNode = rmNodeNew("sphereNode", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    RMprimitive *p = rmPrimitiveNew(RM_APP_DISPLAYLIST);

    float radius=1.0F;
    float tcLimit=1.0;
    RMcolor4D c={1.0, 1.0, 1.0, 1.0};
    RMvertex3D bmin, bmax;

    if (i == NULL)
    {
	rmError("unable to read the texture image file. Bye!");
	exit(-1);
    }

    rmNodeSetUnlitColor(sphereNode, &c);

    bmin.x = bmin.y = bmin.z = -1.0 * radius;
    bmax.x = bmax.y = bmax.z = radius;
    rmNodeSetBoundingBox(sphereNode, &bmin, &bmax);
    rmNodeComputeCenterFromBoundingBox(sphereNode);

    rmNodeAddChild(textureNode, sphereNode);
    rmNodeAddChild(addTo, textureNode);
    rmNodeUnionAllBoxes(addTo);

    rmImageMirror(i, RM_IMAGE_MIRROR_HEIGHT);
    rmTextureSetImages(t, &i, 1, 0);
    rmTextureSetEnv(t, GL_DECAL, NULL); 
    rmTextureSetEnv(t, GL_MODULATE, NULL);
    rmTextureSetWrapMode(t, GL_REPEAT);
    rmNodeSetSceneTexture(textureNode, t);

    /* assign the callback that will actually invoke the display list */
    rmPrimitiveSetAppDisplayList(p, makeSphereDisplayList(radius, tcLimit, 80, 40));
    rmNodeAddPrimitive(sphereNode, p);

    rmTextureDelete(t, RM_TRUE);
}

GLuint 
makeGLTextureFromJPEG(const char *fname)
{
    int w, h;
    RMimage *img = rmiReadJPEG(fname);
    GLuint tid;

    if (img == NULL)
	return -1;

    /* 
     * assumptions:  rmi will return an RMimage from a JPEG file that:
     * 1. pixels are of type RM_UNSIGNED_BYTE
     * 2. pixel format is RGB (not RGBA)
     * 3. we're going to expand from RGB to RGBA, where A is computed as
     *    being the luminance value of the RGBA channels
     * 4. we can actually make OpenGL calls here because we're being
     *    invoked from the "init func", which is called after
     *    OpenGL is initialized.
     */

    /* temp, for now, just build a darned RGB texture. eventually we
       want to expand from RGB to RGBA */
    rmImageMirror(img, RM_IMAGE_MIRROR_HEIGHT);
    rmImageGetImageSize(img, NULL, &w, &h, NULL, NULL, NULL);
    glGenTextures(1, &tid);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, tid);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, 
		 GL_RGB, GL_UNSIGNED_BYTE,
		 (void *)(rmImageGetPixelData(img)));
    glDisable(GL_TEXTURE_2D);
    
    return tid;
}

/*
 * In this routine, we create a sphere primitive with texture coords and
 * normals using the routine in this file that generates a sphere qmesh.
 * We also build a texture from this routine, and assign the GLuint
 * GL textureID rather than using the usual rmTextureSetImages() approach
 * to specifying texel data. This approach shows how to use the GL texture
 * IDs generated by third-party tools within an RM scene graph.
 */
void
makeAppTexturedSphere(RMnode *addTo)
{
    RMtexture *t;
    RMnode *textureNode = rmNodeNew("textureNode-AppTexture", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
    RMnode *sphereNode = rmNodeNew("sphereNode-AppTexture", RM_RENDERPASS_3D, RM_RENDERPASS_TRANSPARENT);
    RMcolor4D c = {1.0, 1.0, 1.0, 0.5};
    GLuint myTextureID = makeGLTextureFromJPEG("data/weather.jpg"); 

    float radius=1.1F;
    float tcLimit = 6.0;

    /* generate the sphere RMprimitive and add it to the RMnode*/
    RMprimitive *p = makeSpherePrimitive(radius, tcLimit, 80, 40);

    rmNodeSetUnlitColor(sphereNode, &c);
    rmNodeAddPrimitive(sphereNode, p);
    rmNodeComputeBoundingBox(sphereNode);

    rmNodeAddChild(textureNode, sphereNode);
    rmNodeAddChild(addTo, textureNode);
    rmNodeUnionAllBoxes(addTo);

    /*
     * To use the GLuint texture object ID created third party tools, you
     * still have to create an RMtexture object and assign it to an RMnode
     * as a scene parameter. Instead of specifying texel data to the RMtexture
     * with rmTextureSetImages, you supply the GLuint textureID to the
     * RMtexture with rmTextureSetTextureID.
     */
    t = rmTextureNew(2);
    rmTextureSetTextureID(t, &myTextureID); 
    rmTextureSetEnv(t, GL_DECAL, NULL); 
    rmTextureSetEnv(t, GL_MODULATE, NULL);
    rmTextureSetWrapMode(t, GL_REPEAT);
    rmNodeSetSceneTexture(textureNode, t);  
    rmTextureDelete(t, RM_TRUE);
}

void
makeBackgroundClear(RMnode *addTo)
{
    RMcolor4D c={0.1, 0.1, 0.2, 1.0};
    RMnode *n = rmNodeNew("backgroundClear", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeSetSceneBackgroundColor(n, &c);
    rmNodeAddChild(addTo, n);
}

void
computeSceneCamera(RMnode *addTo,
		   const RMpipe *p)
{
    RMcamera3D *c = rmCamera3DNew();
    int width, height;

    rmDefaultCamera3D(c);
    rmPipeGetWindowSize(p, &width, &height);
    rmCamera3DComputeViewFromGeometry(c, addTo, width, height);

    rmNodeSetSceneCamera3D(addTo, c);
}

void
myInitFunc(RMpipe *p,
	   RMnode *addTo)
{
    RMnode *myRoot = rmNodeNew("myRoot", RM_RENDERPASS_3D, RM_RENDERPASS_ALL);
    makeBackgroundClear(addTo);

    makeAppTexturedSphere(myRoot);
    makeRMTexturedAppPrim(myRoot);

#if 0
    makeTexturedSphere(myRoot);
    makeAppTexturedSphere(myRoot);
#endif
    rmNodeAddChild(addTo, myRoot);
    rmNodeUnionAllBoxes(addTo);
    
    computeSceneCamera(myRoot, p);
    
    rmDefaultLighting(addTo);
    rmauxSetGeomTransform(myRoot,p);  
    rmauxSetCamera3DTransform(myRoot, p); 

    rmFrame(p, addTo); 
}

void
myRenderFunc(RMpipe *myPipe,
	     RMnode *subTree)
{
    /* insert code to call frame-based renderer here */
    rmFrame(myPipe, subTree);
}

void
usage(char *s)
{
    printf("usage: %s [-w imgWidthPixels ] [-h imgHeightPixels] \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++;
	    sscanf(av[i],"%d", imgWidth);
	}
	else if (strcmp(av[i],"-h") == 0)
	{
	    i++;
	    sscanf(av[i],"%d", imgHeight);
	}
	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;
    processingMode = RM_PIPE_SERIAL;
    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","RM for X-Windows",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(myRenderFunc);
    
    /*
     * set key handler function so this prog will exit on "q" key.
     */
    rmauxSetKeyFunc(myPipe, rmauxDefaultKeyFunc);
    
    rmauxEventLoop(myPipe,rmRootNode(), &msg);

    rmPipeDelete(myPipe);
    rmFinish();

    return(1);
}
   
