/*
 * (c) copyright 1993, silicon graphics, inc.
 * ALL RIGHTS RESERVED 
 * Permission to use, copy, modify, and distribute this software for 
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that 
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission. 
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * US Government Users Restricted Rights 
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>

#include "wave.hxx"


#define PI 3.14159265358979323846f

#define GETWCOORD(frame, x, y) (&(mesh.coords[frame*mesh.numCoords+(x)+(y)*(iWidthX+1)]))
#define GETFACET(frame, x, y) (&(mesh.facets[frame*mesh.numFacets+(x)+(y)*iWidthX]))

#ifndef cosf
#define cosf( x ) \
    ( (float) cos( (x) ) )
#define sinf( x ) \
    ( (float) sin( (x) ) )
#endif

#ifndef sqrtf
#define sqrtf( x ) \
    ( (float) sqrt( (x) ) )
#endif

/******************** WAVE *************************************************
* 
* Creates a moving wave in the x-y plane, with varying height along the z-axis.
*
* The x,y ranges are confined to (-.5, .5).  Height can be any positive number.
* A number of frames are generated, which are drawn sequentially on Draw calls.
* It can run in either smooth or flat shaded modes.  In smooth mode, the
* materials are generated with glMaterialColor.

* Two of the opposite corners of the wave are pinned down, while the wave moves
* diagonally between the other corners.
*
* It looks like there is only one period of the wave visible at any one time,
* and no way to adjust this. (mf: we need more periods)
*
* In the standard GL reference axis scheme, the normals for wave all have
* normals pointing in the -z direction.  Looking at wave from that direction,
* the quad orientation is CW.  So need to rotate by 180 around y if viewing
* down the -z axis.  Alternatively, could construct the wave differently
***************************************************************************/

WAVE::WAVE()
{
    nFrames = 10;
    iWidthX = 10;
    iWidthY = 10;
    iCheckerSize = 2;
    fHeight = 0.2f;

    iCurFrame = 0;

    InitMaterials();
    InitMesh();
}

WAVE::~WAVE()
{
    if( mesh.coords )
        free( mesh.coords );
    if( mesh.facets )
        free( mesh.facets );
}

void
WAVE::Draw()
{
//mf: could be a candidate for vertex array ??
    WCOORD *coord;
    FACET *facet;
    float *lastColor;
    float *thisColor;
    GLint i, j;

    // Set any state required (caller will have to 'push' this state), or ? 
    // should do it here ?
//mf: need to set front face, etc.  Caller can probably do this, since it's
// going to need to know how the wave is constructed anyways.

    if (bSmooth) {
        glShadeModel(GL_SMOOTH);
    } else {
        glShadeModel(GL_FLAT);
    }
    glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);
    
    iCurFrame++;

    // Draw the wave frame

    if (iCurFrame >= nFrames) {
        iCurFrame = 0;
    }

    for (i = 0; i < iWidthX; i++) {
	glBegin(GL_QUAD_STRIP);
	lastColor = NULL;
	for (j = 0; j < iWidthY; j++) {
	    facet = GETFACET(iCurFrame, i, j);
	    if (!bSmooth && bLighting) {
        	glNormal3fv(facet->normal);
	    }
	    if (bLighting) {
		    thisColor = facet->color;
		    glColor3fv(facet->color);
	    } else {
		    thisColor = facet->color;
		    glColor3fv(facet->color);
	    }

	    if (!lastColor || (thisColor[0] != lastColor[0] && bSmooth)) {
		if (lastColor) {
		    glEnd();
		    glBegin(GL_QUAD_STRIP);
		}
		coord = GETWCOORD(iCurFrame, i, j);
		if (bSmooth && bLighting) {
		    glNormal3fv(coord->normal);
		}
		glVertex3fv(coord->vertex);

		coord = GETWCOORD(iCurFrame, i+1, j);
		if (bSmooth && bLighting) {
		    glNormal3fv(coord->normal);
		}
		glVertex3fv(coord->vertex);
	    }

	    coord = GETWCOORD(iCurFrame, i, j+1);
	    if (bSmooth && bLighting) {
		glNormal3fv(coord->normal);
	    }
	    glVertex3fv(coord->vertex);

	    coord = GETWCOORD(iCurFrame, i+1, j+1);
	    if (bSmooth && bLighting) {
		glNormal3fv(coord->normal);
	    }
	    glVertex3fv(coord->vertex);

	    lastColor = thisColor;
	}
	glEnd();
    }
}

void 
WAVE::InitMesh()
{
    WCOORD *coord;
    FACET *facet;
    float dp1[3], dp2[3];
    float *pt1, *pt2, *pt3;
    float angle, d, x, y;
    GLint numFacets, numCoords, frameNum, i, j;

    numFacets = iWidthX * iWidthY;
    numCoords = (iWidthX + 1) * (iWidthY + 1);

    mesh.numCoords = numCoords;
    mesh.numFacets = numFacets;

    mesh.coords = (WCOORD *)malloc(nFrames*numCoords*
					    sizeof(WCOORD));
    mesh.facets = (FACET *)malloc(nFrames*numFacets*
					    sizeof(FACET));

    assert( mesh.coords != NULL || mesh.facets != NULL);

    for (frameNum = 0; frameNum < nFrames; frameNum++) {
	for (i = 0; i <= iWidthX; i++) {
	    x = i / (float)iWidthX;
	    for (j = 0; j <= iWidthY; j++) {
		y = j / (float)iWidthY;

		d = (float) sqrtf(x*x+y*y);
		if (d == 0.0f) {
		    d = 0.0001f;
		}
		angle = 2 * PI * d + (2 * PI / nFrames * frameNum);

		coord = GETWCOORD(frameNum, i, j);

		coord->vertex[0] = x - 0.5f;
		coord->vertex[1] = y - 0.5f;
		coord->vertex[2] = (fHeight - fHeight * d) * cosf(angle);

		coord->normal[0] = -(fHeight / d) * x * ((1 - d) * 2 * PI *
				   sinf(angle) + cosf(angle));
		coord->normal[1] = -(fHeight / d) * y * ((1 - d) * 2 * PI *
				   sinf(angle) + cosf(angle));
		coord->normal[2] = -1.0f;

		d = 1.0f / sqrtf(coord->normal[0]*coord->normal[0]+
			       coord->normal[1]*coord->normal[1]+1);
		coord->normal[0] *= d;
		coord->normal[1] *= d;
		coord->normal[2] *= d;
	    }
	}
	for (i = 0; i < iWidthX; i++) {
	    for (j = 0; j < iWidthY; j++) {
		facet = GETFACET(frameNum, i, j);
		if (((i/iCheckerSize)%2)^(j/iCheckerSize)%2) {
			facet->color[0] = 1.0f;
			facet->color[1] = 0.2f;
			facet->color[2] = 0.2f;
		} else {
			facet->color[0] = 0.2f;
			facet->color[1] = 1.0f;
			facet->color[2] = 0.2f;
		}
		pt1 = GETWCOORD(frameNum, i, j)->vertex;
		pt2 = GETWCOORD(frameNum, i, j+1)->vertex;
		pt3 = GETWCOORD(frameNum, i+1, j+1)->vertex;

		dp1[0] = pt2[0] - pt1[0];
		dp1[1] = pt2[1] - pt1[1];
		dp1[2] = pt2[2] - pt1[2];

		dp2[0] = pt3[0] - pt2[0];
		dp2[1] = pt3[1] - pt2[1];
		dp2[2] = pt3[2] - pt2[2];

		facet->normal[0] = dp1[1] * dp2[2] - dp1[2] * dp2[1];
		facet->normal[1] = dp1[2] * dp2[0] - dp1[0] * dp2[2];
		facet->normal[2] = dp1[0] * dp2[1] - dp1[1] * dp2[0];

		d = 1.0f / sqrtf(facet->normal[0]*facet->normal[0]+
			       facet->normal[1]*facet->normal[1]+
			       facet->normal[2]*facet->normal[2]);

		facet->normal[0] *= d;
		facet->normal[1] *= d;
		facet->normal[2] *= d;
	    }
	}
    }
}

#if 0
	if (bLighting) {
	    glEnable(GL_LIGHTING);
	    glEnable(GL_LIGHT0);
		glEnable(GL_COLOR_MATERIAL);
	} else {
	    glDisable(GL_LIGHTING);
	    glDisable(GL_LIGHT0);
		glDisable(GL_COLOR_MATERIAL);
	}
#endif