/*
 * Copyright (C) 2005-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: strands.c,v 1.5 2008/11/09 14:37:04 wes Exp $
 * $Revision: 1.5 $
 * $Name: v180-alpha-01 $
 * $Log: strands.c,v $
 * Revision 1.5  2008/11/09 14:37:04  wes
 * Update dates to 2008
 *
 * Revision 1.4  2006/08/08 03:49:21  wes
 * Updates to reflect ergonomic changes to rmaux
 *
 * Revision 1.3  2005/06/12 21:31:14  wes
 * Use black for background color.
 *
 * Revision 1.2  2005/06/08 16:26:46  wes
 * Code cleanup to eliminate compiler warnings.
 *
 * Revision 1.1  2005/05/16 01:32:03  wes
 * Initial entry.
 *
 */

/*
 * Purpose: exercise 1D texturing and normals on line segments.
 *
 * usage: strands [-w imgWidthPixels ] [-h imgHeightPixels]
 *     [-n NNN (specify number of strands. Default is 200.)]
 *     [-l 0 | 1 (1 means generate per-vertex normals to induce
 *         lighting/shading, 0 is no normals/lights/shading. Default is off.)]
 *     [-bs (add vanishing backstop)]
 *     [-dn (display normals)]
 *     [-tc 0 | 1 (1 means generate 1D texture coordinates and assign a
 *        texture, 0 means no texture coordinates/texturing. Default is on.)]
 *     [-p lstrip | lines (specify primitive type) ]
 */

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

#define DEFAULT_IMAGE_WIDTH 640
#define DEFAULT_IMAGE_HEIGHT 525

static int doNormals=0;
static int displayNormals=0;
static int doBackstop=0;
static int doTCs=1;
static int nStrands=200;
static RMenum primType=RM_LINE_STRIP;

RMvertex3D gridMin = {-1.0, 0.0, -1.0};
RMvertex3D gridMax = {1.0, 1.0, 1.0};

RMcolor4D baseOneDTexture[4] =
    {
      {0.9F, 0.8F, 0.2F, 1.0F},	/* yellow */
      {1.0F, 0.1F, 0.1F, 1.0F}, /* red */
      {0.3F, 0.2F, 0.7F, 1.0F},	/* darkish blue */
      {0.2F, 0.2F, 0.3F, 1.0F} }; /* dark gray */

#define MAXV 64
typedef struct
{
    RMvertex3D v[MAXV];		/* verts */
    float      tc[MAXV];	/* 1d texture coords */
    RMvertex3D n[MAXV]; 	/* normals */

    /* random, per-strand parameters */
    double theta;		/* polar direction projected to y=0 */
    double h;			/* height */
    double r;			/* radius */
} Strand;

void
addVanishingBackstop(RMnode *myRoot)
{
    RMnode *bsnode;
    RMprimitive *p;
    RMvertex3D v[4], n[4];

    bsnode = rmNodeNew("backstop-node",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);

    if (doBackstop == 0)
	rmNodeSetTraverseEnable(bsnode, RM_FALSE);

    /* do y=gridMin.y - floor */
#if 1
    p = rmPrimitiveNew(RM_QUADS);
    
    v[0] = gridMin;
    v[1] = v[0];
    v[1].x = gridMax.x;
    v[2] = v[1];
    v[2].z = gridMax.z;
    v[3] = v[2];
    v[3].x = gridMin.x;

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

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

    rmNodeAddPrimitive(bsnode, p);
#endif
    /* do y=gridMax.y - ceiling */
#if 1
    p = rmPrimitiveNew(RM_QUADS);
    
    v[0] = gridMin;
    v[0].y = gridMax.y;
    v[1] = v[0];
    v[1].z = gridMax.z;
    v[2] = gridMax;
    v[3] = v[2];
    v[3].z = gridMin.z;
    
    n[0].x = n[0].z = 0.0;
    n[0].y = 1.0;
    n[1] = n[2] = n[3] = n[0];

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

    rmNodeAddPrimitive(bsnode, p);
#endif
    
    /* do z=gridMin.z - front wall */
#if 1
    p = rmPrimitiveNew(RM_QUADS);
    
    v[0] = gridMin;
    v[1] = v[0];
    v[1].y = gridMax.y;
    v[2] = v[1];
    v[2].x = gridMax.x;
    v[3] = v[2];
    v[3].y = gridMin.y;
    
    n[0].x = n[0].y = 0.0;
    n[0].z = 1.0;
    n[1] = n[2] = n[3] = n[0];

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

    rmNodeAddPrimitive(bsnode, p);
#endif
    
    /* do z=gridMax.z - back wall */
#if 1
    p = rmPrimitiveNew(RM_QUADS);
    
    v[0] = gridMin;
    v[0].z = gridMax.z;
    v[1] = v[0];
    v[1].x = gridMax.x;
    v[2] = gridMax;
    v[3] = v[2];
    v[3].x = gridMin.x;
    
    n[0].x = n[0].y = 0.0;
    n[0].z = -1.0;
    n[1] = n[2] = n[3] = n[0];

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

    rmNodeAddPrimitive(bsnode, p);
#endif

#if 1
    /* do x=gridMin.x - left wall */
    p = rmPrimitiveNew(RM_QUADS);
    
    v[0] = gridMin;
    v[1] = v[0];
    v[1].z = gridMax.z;
    v[2] = v[1];
    v[2].y = gridMax.y;
    v[3] = v[2];
    v[3].z = gridMin.z;
    
    n[0].z = n[0].y = 0.0;
    n[0].x = 1.0;
    n[1] = n[2] = n[3] = n[0];

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

    rmNodeAddPrimitive(bsnode, p);
#endif
    
#if 1
    /* do x=gridMax.x - right wall */
    p = rmPrimitiveNew(RM_QUADS);
    
    v[0] = gridMax;
    v[1] = v[0];
    v[1].y = gridMin.y;
    v[2] = v[1];
    v[2].z = gridMin.z;
    v[3] = v[2];
    v[3].y = gridMax.y;
    
    n[0].z = n[0].y = 0.0;
    n[0].x = -1.0;
    n[1] = n[2] = n[3] = n[0];

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

    rmNodeAddPrimitive(bsnode, p);
#endif
    
    rmNodeSetPolygonCullMode(bsnode, RM_CULL_FRONT);
    /*    rmNodeSetPolygonDrawMode(bsnode, RM_BACK, RM_LINE); */
    rmNodeComputeBoundingBox(bsnode);

    rmNodeAddChild(myRoot, bsnode);
}

void
computeCamera(RMnode *myRoot,
	      int width, int height)
{
    RMcamera3D *c;

    c = rmCamera3DNew();
    rmDefaultCamera3D(c);
    rmCamera3DComputeViewFromGeometry(c, myRoot, width, height);

    rmNodeSetSceneCamera3D(myRoot, c);
    rmCamera3DDelete(c);
}

double
evalFunction(double t,
	     double h)
{
    double rval;
    double xval;

    xval = t;

    rval = -1. * xval * xval + xval;
    rval *= h;
    return rval;
}

void
computeStrand(Strand *s, 
	      double c, 
	      double si, 
	      int nverts)
{
    int i;
    double x, dx, z, dz, y, t, dt;

    x = (gridMax.x - gridMin.x) * 0.5 + gridMin.x;
    y = (gridMax.y - gridMin.y) * 0.5 + gridMin.y;
    z = (gridMax.z - gridMin.z) * 0.5 + gridMin.z;

    dx = s->r * c / (double)(nverts - 1);
    dz = s->r * si / (double)(nverts - 1);

    t = 0.0;
    dt = 1.0/(double)(nverts-1);

    /* compute verts */
    for (i=0; i < nverts; i++, x += dx, z += dz, t += dt)
    {
	s->v[i].y = evalFunction(t, 2.0 * s->h);
	s->v[i].x = x;
	s->v[i].z = z;

	s->tc[i] = 1.0 - t;		/* a 1D texture coordinate */
    }

    /* compute normals */
    for (i=0; i < nverts-1; i++)
    {
	RMvertex3D diff, cross1, cross2;
	rmVertex3DDiff(&(s->v[i+1]),&(s->v[i]), &diff);
	rmVertex3DCross(&diff, &(s->v[i]), &cross1);
	rmVertex3DCross(&cross1, &diff, &cross2);
	rmVertex3DNormalize(&cross2);
	s->n[i] = cross2;
    }
    s->n[i] = s->n[i-1];
}

/* input rand numbers in range 0..RAND_MAX. we mask it to 0..65536, */
/* then convert to range 0..1 */
#define TOFLOAT(a) ((float)(a)/(float)(RAND_MAX))

RMnode *
makeStrands(int nStrands)
{
    int i;
    float  r1, r2, r3;
    Strand *s = (Strand *)malloc(sizeof(Strand)*nStrands);
    RMnode *n = rmNodeNew("strands", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    int nverts = 16;

    /*    srand(time(NULL));*/ /* a random seed */
    srand(512); /* a constant seed */

    /* first, compute core values associated with each strand. */
    for (i=0;i<nStrands;i++)
    {
	double c, si;

	r1 = TOFLOAT(rand());
	r2 = TOFLOAT(rand());
	r3 = TOFLOAT(rand());

	/* convert 0..1 to 0..2PI */
	s[i].theta = r1 * 2.0 * RM_PI;

	/* convert 0..1 to size of grid in Y */
	/* r2 in range 0..1...want it in range 1..2 */
	r2 = (r2 + 2.0) * 0.5;
	s[i].h = r2 * ((gridMax.y - gridMin.y) + gridMin.y);

	/* convert 0..1 to half-width of grid in X */
	s[i].r = r3 * (gridMax.x - gridMin.x) * 0.5;

	c = cos(s[i].theta);
	si = sin(s[i].theta);

	computeStrand(s+i, c, si, nverts);
    }

    /* then, build the actual primitive */
    if (primType == RM_LINE_STRIP)
    {
	for (i=0;i<nStrands;i++)
	{
	    RMprimitive *p = rmPrimitiveNew(RM_LINE_STRIP);
	    
	    rmPrimitiveSetVertex3D(p, nverts, s[i].v, RM_COPY_DATA, NULL);
	    
	    if (doNormals == 1)
		rmPrimitiveSetNormal3D(p, nverts, s[i].n, RM_COPY_DATA, NULL);

	    if (doTCs == 1)
		rmPrimitiveSetTexcoord1D(p, nverts, s[i].tc, RM_COPY_DATA, NULL);

	    if (displayNormals == 1)
	    {
		int j;
		for (j = 0; j < nverts; j++)
		{
		    RMvertex3D v[2];
		    p = rmPrimitiveNew(RM_LINES);
		    v[0] = s[i].v[j];
		    v[1].x = v[0].x + s[i].n[j].x;
		    v[1].y = v[0].y + s[i].n[j].y;
		    v[1].z = v[0].z + s[i].n[j].z;
		    rmPrimitiveSetVertex3D(p, 2, v, RM_COPY_DATA, NULL);
		    rmNodeAddPrimitive(n, p);
		}
	    }
	    rmNodeAddPrimitive(n, p);
	}
    }
    else if (primType == RM_LINES)
    {
	int nsegs = nverts-1;
	RMvertex3D *v = rmVertex3DNew(nsegs*2);
	RMvertex3D *nr = rmVertex3DNew(nsegs*2);
	float *tc = (float *)malloc(sizeof(float)*nsegs*2);

	for (i=0;i<nStrands;i++)
	{
	    int j;
	    
	    RMprimitive *p = rmPrimitiveNew(RM_LINES);
	    rmNodeAddPrimitive(n, p);

	    for (j=0;j<nsegs;j++)
	    {
		v[j*2] = s[i].v[j];
		v[j*2+1] = s[i].v[j+1];

		nr[j*2] = s[i].n[j];
		nr[j*2+1] = s[i].n[j+1];

		tc[j*2] = s[i].tc[j];
		tc[j*2+1] = s[i].tc[j+1];
	    }

	    rmPrimitiveSetVertex3D(p, nsegs*2, v, RM_COPY_DATA, NULL);

	    /* the following #if statements were used to exercise
	     different code paths in the draw code. */
#if 1
	    if (doNormals == 1) /* per vertex normals */
		rmPrimitiveSetNormal3D(p, nsegs*2, nr, RM_COPY_DATA, NULL);
#else
	    if (doNormals == 1) /* per segment normals */
		rmPrimitiveSetNormal3D(p, nsegs, s[i].n, RM_COPY_DATA, NULL);
#endif

	    /* the following #if statements were used to exercise
	     different code paths in the draw code. */
#if 1
	    if (doTCs == 1) 	/* per vertex tc's */
		rmPrimitiveSetTexcoord1D(p, nsegs*2, tc, RM_COPY_DATA, NULL);
#else
	    if (doTCs == 1) 	/* per segment tc's */
		rmPrimitiveSetTexcoord1D(p, nsegs, s[i].tc, RM_COPY_DATA, NULL);
#endif
	}

	rmVertex3DDelete(v);
	rmVertex3DDelete(nr);
	free((void *)tc);
    }

    rmNodeComputeBoundingBox(n);

    return n;
}

void
makeStrandsTexture(RMnode *n)
{
    RMimage *img;
    RMtexture *t;
    int nTexels = sizeof(baseOneDTexture)/sizeof(RMcolor4D);

    img = rmImageNew(1, nTexels, 1, 1, RM_IMAGE_RGBA, RM_FLOAT, RM_COPY_DATA);
    t = rmTextureNew(1);

    rmImageSetPixelData(img, (void *)baseOneDTexture, RM_COPY_DATA, NULL);
    rmTextureSetImages(t, &img, 1, 0);
    rmNodeSetSceneTexture(n, t);

#if 0
    rmImageDelete(img);
    rmTextureDelete(t);
#endif
}

void
myInitFunc(RMpipe *p,
	   RMnode *n,
	   void *usrData)
{
    RMnode *myRoot, *strandsRoot;
    int width, height;
    /* insert code to build the initial scene graph here. be sure to
     add your scene graph to the node "n" */
    {
	RMcolor4D bgColor = {0.0F, 0.0F, 0.0F, 1.0F};
	rmPipeSetSceneBackgroundColor(p, &bgColor);
    }

    myRoot = rmNodeNew("myRoot", RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
    rmNodeAddChild(rmRootNode(), myRoot);

    addVanishingBackstop(myRoot);
    rmNodeUnionAllBoxes(myRoot);
    rmNodeComputeCenterFromBoundingBox(myRoot);

    strandsRoot = makeStrands(nStrands);

    if (doTCs == 1)
	makeStrandsTexture(strandsRoot);

    rmNodeAddChild(myRoot, strandsRoot);

    rmPipeGetWindowSize(p, &width, &height);
    computeCamera(myRoot, width, height);
    rmDefaultLighting(rmRootNode());
    
    rmauxSetGeomTransform(myRoot, p);
    rmauxSetCamera3DTransform(myRoot, p);
    
    rmFrame(p, n);
}

void
usage(char *s)
{
    printf("usage: %s [-w imgWidthPixels ] [-h imgHeightPixels] \n \
 [-n NNN (specify number of strands. Default is 200.)] \n \
 [-l {0 | 1} (1 means generate per-vertex normals to induce \n \
     lighting/shading, 0 is no normals/lights/shading. Default is off.)] \n \
 [-bs (add vanishing backstop)] \n \
 [-dn (display normals)] \n \
 [-tc {0 | 1} (1 means generate 1D texture coordinates and assign a \n \
      texture, 0 means no texture coordinates/texturing. Default is on.)] \n \
 [-p lstrip | lines (specify primitive type, default is line strip) ] \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 if (strcmp(av[i],"-n") == 0)
	{
	    i++;
	    sscanf(av[i],"%d", &nStrands);
	}
	else if (strcmp(av[i],"-bs") == 0)
	{
	    doBackstop = 1;
	}
	else if (strcmp(av[i],"-l") == 0)
	{
	    i++;
	    if (atoi(av[i]) == 1)
		doNormals = 1;
	    else
		doNormals = 0;
	}
	else if (strcmp(av[i], "-dn") == 0)
	{
	    displayNormals = 1;
	}
	else if (strcmp(av[i], "-tc") == 0)
	{
	    i++;
	    if (atoi(av[i]) == 1)
		doTCs = 1;
	    else
		doTCs = 0;
	}
	else if (strcmp(av[i], "-p") == 0)
	{
	    i++;
	    if (strcmp(av[i],"lstrip") == 0)
		primType = RM_LINE_STRIP;
	    else if (strcmp(av[i],"lines") == 0)
		primType = RM_LINES;
	    else
	    {
		usage(av[0]);
		exit(-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,"OpenRM 1-D Texture Test - 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,
			       "OpenRM 1-D Texture Test - X11","OpenRM 1-D Texture Test",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);	

    rmauxSetInitCallbackFunc(myInitFunc, 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 */ 
