/*-
 * Copyright (c) 1999,2000 Qube Software, Ltd.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of the source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Any redistribution solely in binary form must conspicuously
 *    reproduce the following disclaimer in documentation provided with the
 *    binary redistribution.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'', WITHOUT ANY WARRANTIES, EXPRESS
 * OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  LICENSOR SHALL
 * NOT BE LIABLE FOR ANY LOSS OR DAMAGES RESULTING FROM THE USE OF THIS
 * SOFTWARE, EITHER ALONE OR IN COMBINATION WITH ANY OTHER SOFTWARE.
 * 
 * 	$Id: water.cpp,v 1.30 2000/05/04 14:46:11 nps Exp $
 */

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <float.h>

#include "maths.h"
#include "draw.h"
#include "image.h"
#include "event.h"

using namespace Draw;
using namespace Maths;
using namespace Image;

struct Vertex
{
    Vec3f		coord;
    Vec3f		norm;
    Color4b		col;
    Vec2f		tex;
};

struct Model
{
    VertexArray*	vertices;
    PrimitiveArray*	primitives;
    Matrix4f		matrix;
    Material		material;
    Shader*		shader;

    void		begin(Buffer* buffer);
    void		end(Buffer* buffer);
    void		render(Buffer* buffer);
};

void
Model::begin(Buffer* buffer)
{
    buffer->pushMatrix(Buffer::ModelMatrix);
    buffer->multiplyMatrix(Buffer::ModelMatrix, matrix);
}

void
Model::end(Buffer* buffer)
{
    buffer->popMatrix(Buffer::ModelMatrix);
}

void
Model::render(Buffer* buffer)
{
    buffer->setMaterial(material);
    buffer->setVertices(vertices);
    buffer->setShader(shader);
    buffer->render(primitives, 0, primitives->size());
}

void
buildVertexGrid(Factory* f, VertexArray* vArray,
		unsigned int width, unsigned int depth,
		float mul, float offset, float scale)
{
    int i, j;
    vArray->lock();
    
    Vertex v;

    int hWidth = width / 2;
    int hDepth = depth / 2;
    float freq = 1.0f;
    
    for (i = 0; i < hWidth; ++i) {
	v.coord.x = (i - hWidth) * mul;
	
	for (j = 0; j < hDepth; ++j) {
	    unsigned int idx1 = (i * depth) + j;
	    unsigned int idx2 = ((width - 1 - i) * depth) + j;
	    unsigned int idx3 = (i * depth) + depth - 1 - j;
	    unsigned int idx4 = ((width - 1 - i) * depth) + depth - 1 - j;

	    v.coord.z = (hDepth - j) * mul;

	    float r = sqrtf(v.coord.x * v.coord.x + v.coord.z * v.coord.z);

	    float fullScale = scale * expf(-r * 0.04f);
	    
	    v.coord.y = sinf(freq * (r + offset)) * fullScale;

	    float a = atanf(cosf(freq * (r + offset)) * fullScale);
	    float b = atan2f(v.coord.x, v.coord.z);
	    
	    v.norm = Vec3f(-sinf(a)*sinf(b), cosf(a), -sinf(a)*cosf(b));
	    v.col = Vec3f(1, 1, 1);
	    v.tex = Vec2f(0.5f + v.coord.x / (width * mul),
			  0.5f + v.coord.z / (depth * mul));
	    vArray->set(idx1, (void*)&v);

	    v.coord.x = -v.coord.x;
	    v.norm.x = -v.norm.x;
	    v.tex = Vec2f(0.5f + v.coord.x / (width * mul),
			  0.5f + v.coord.z / (depth * mul));

	    vArray->set(idx2, (void*)&v);

	    v.coord.x = -v.coord.x;
	    v.coord.z = -v.coord.z;
	    v.norm.x = -v.norm.x;
	    v.norm.z = -v.norm.z;
	    v.tex = Vec2f(0.5f + v.coord.x / (width * mul),
			  0.5f + v.coord.z / (depth * mul));
	    
	    vArray->set(idx3, (void*)&v);

	    v.coord.x = -v.coord.x;
	    v.norm.x = -v.norm.x;
	    v.tex = Vec2f(0.5f + v.coord.x / (width * mul),
			  0.5f + v.coord.z / (depth * mul));

	    vArray->set(idx4, (void*)&v);

	    v.coord.x = -v.coord.x;
	}
    }

    vArray->unlock();
}

void
buildTriangleGrid(Factory* f, PrimitiveArray* pArray, VertexArray* vArray,
		  unsigned int width, unsigned int depth)
{
    PrimitiveArray::Element* tri;

    pArray->lock();
    vArray->lock();
    for (unsigned int i = 0; i < width - 1; ++i) {
	for (unsigned int j = 0; j < depth - 1; ++j) {
	    unsigned int k = (i * depth) + j;
	    Vertex* v0 = (Vertex*) vArray->at(k);
	    Vertex* v1 = (Vertex*) vArray->at(k + depth);
	    Vertex* v2 = (Vertex*) vArray->at(k + 1);
	    Vertex* v3 = (Vertex*) vArray->at(k + depth + 1);
	    tri = pArray->at(2*k);

	    /*
	     * Calculate which triangulation to use based on the angle
	     * between opposite normals. The edge across the quad
	     * should be drawn along the flattest diagonal (i.e. the
	     * one with the largest dot product).
	     */
	    float cos03 = dot(v0->norm, v3->norm);
	    float cos12 = dot(v1->norm, v2->norm);
	    if (cos03 < cos12) {
		tri[0] = k;
		tri[1] = k + depth;
		tri[2] = k + 1;
		tri[3] = k + 1;
		tri[4] = k + depth;
		tri[5] = k + depth + 1;
	    } else {
		tri[0] = k;
		tri[1] = k + depth + 1;
		tri[2] = k + 1;
		tri[3] = k;
		tri[4] = k + depth;
		tri[5] = k + depth + 1;
	    }
	}
    }
    vArray->unlock();
    pArray->unlock();
}

PrimitiveArray*
makeWireframeFromTriangles(Factory* f, PrimitiveArray* pArray)
{
    PrimitiveArray::Element* prims;
    PrimitiveArray::Element* tri;
    PrimitiveArray* edges;
    unsigned int i, maxIndex;

    edges = f->createPrimitiveArray(PrimitiveArray::Lines, 0);

    prims = pArray->lock();

    maxIndex = 0;
    for (i = 0; i < 3 * pArray->size(); i++)
	if (prims[i] > maxIndex)
	    maxIndex = prims[i];

    maxIndex++;
    char* edgeTable = new char[maxIndex * maxIndex];
    memset(edgeTable, 0, maxIndex * maxIndex);

#define ADD_EDGE(i0, i1)			\
    {						\
	if (!edgeTable[i0 * maxIndex + i1]) {	\
	    edgeTable[i0 * maxIndex + i1] = 1;	\
	    edgeTable[i1 * maxIndex + i0] = 1;	\
	    edges->add(i0, i1);			\
	}					\
    }

    for (i = 0; i < pArray->size(); i++) {
	tri = pArray->at(i);
	ADD_EDGE(tri[0], tri[1]);
	ADD_EDGE(tri[1], tri[2]);
	ADD_EDGE(tri[2], tri[0]);
    }

    delete[] edgeTable;

    pArray->unlock();

    return edges;
}

/*
 * Refract each vertex in 'surface' and calculate the refracted view
 * of 'model'.
 */
void
refractModel(Model& surface,
	     const Matrix4f& surfaceTransform,
	     Model& model,
	     const Matrix4f& modelTransform,
	     const Vec3f& eye)
{
    /*
     * Rip though all of the vertices of surface firing a ray from the
     * eye point to the vertex and then refracting it and seeing if it
     * hits the model and reading the texco from there.
     */
    float airIndex = 1.0f;
    float waterIndex = 1.3333f;
    float indexRatio = airIndex / waterIndex;
    Vec3f localEye = eye.inverseTransform(surfaceTransform);
    Matrix4f surfaceToModel = surfaceTransform * modelTransform.invert();

    surface.vertices->lock();
    for (unsigned int i = 0; i < surface.vertices->size(); i++) {
	Vertex* v = (Vertex*) surface.vertices->at(i);

	// Calculate a ray from the eye to the surface vertex.
	Vec3f dir = v->coord - localEye;
	dir.normalize();

	// Refract the ray using the surface normal
	dir = refract(v->norm, dir, indexRatio);

	// Transform into model coordinates and intersect with the model,
	// extracting the texture coordinates
	Rayf ray(v->coord.transform(surfaceToModel),
		 dir.rotate(surfaceToModel));
	unsigned int hitIndex;
	float hitDistance = 100.0f;
	Vec2f tex;
	bool hit =
	    model.primitives->rayIntersect(model.vertices,
					   ray,
					   0, model.primitives->size(),
					   hitIndex, hitDistance,
					   FVF::TexCoord(1),
					   (void*) &tex);

	// Put the texture coordinate back into the surface
	if (hit) {
	    v->tex = tex;
	    v->col = Vec3f(0.8f, 0.8f, 0.8f);
	} else {
	    v->tex = Vec2f(0, 0);
	    v->col = Vec3f(0, 0, 0);
	}
    }
    surface.vertices->unlock();
}

/*
 * Reflect each vertex in 'surface' and calculate the reflected view
 * of 'model'.
 */
void
reflectModel(Model& surface,
	     const Matrix4f& surfaceTransform,
	     Model& model,
	     const Matrix4f& modelTransform,
	     const Vec3f& eye)
{
    /*
     * Rip though all of the vertices of surface firing a ray from the
     * eye point to the vertex and then reflecting it and seeing if it
     * hits the model and reading the texco from there.
     */
    Vec3f localEye = eye.inverseTransform(surfaceTransform);
    Matrix4f surfaceToModel = surfaceTransform * modelTransform.invert();

    surface.vertices->lock();
    for (unsigned int i = 0; i < surface.vertices->size(); i++) {
	Vertex* v = (Vertex*) surface.vertices->at(i);

	// Calculate a ray from the eye to the surface vertex.
	Vec3f dir = v->coord - localEye;
	dir.normalize();

	// Reflect the ray using the surface normal
	dir = reflect(v->norm, dir);

	// Transform into model coordinates and intersect with the model,
	// extracting the texture coordinates
	Rayf ray(v->coord.transform(surfaceToModel),
		 dir.rotate(surfaceToModel));
	unsigned int hitIndex;
	float hitDistance = 100.0f;
	Vec2f tex;
	bool hit =
	    model.primitives->rayIntersect(model.vertices,
					   ray,
					   0, model.primitives->size(),
					   hitIndex, hitDistance,
					   FVF::TexCoord(1),
					   (void*) &tex);

	// Put the texture coordinate back into the surface
	if (hit) {
	    v->tex = tex;
	    v->col = Vec3f(0.8f, 0.8f, 0.8f);
	} else {
	    v->tex = Vec2f(0, 0);
	    v->col = Vec3f(0, 0, 0);
	}
    }
    surface.vertices->unlock();
}

static Draw::Texture*
loadBitmap(Renderer* renderer, const char* name)
{
    bool imageOk = true;
    Draw::Texture* texture;

    unsigned int width;
    unsigned int height;
    unsigned int maxdim;
    Format::Type format = Format::RGB888;
    
    Pixmap* image = Pixmap::load(name, format, 8);
    if (!image)
	return 0;

    width = image->extent().x;
    height = image->extent().y;

    maxdim = width;
    if (height > width)
	maxdim = height;

    if (width != height)
	imageOk = false;

    unsigned int levels = 0;
    for (levels = 0; (1U << levels) < maxdim; levels++);

    if (maxdim != (1U << levels))
	imageOk = false;

    if (!imageOk) {
	/*
	 * Reformat the image to a square power of two image.
	 */
	if (levels > 8) levels = 8;
	maxdim = 1U << levels;
	Pixmap* scale = new Pixmap(maxdim, maxdim, image->extent().z,
				   format, 8);
	
	copy(*image, Box2ui(0, 0, width, height),
	     *scale, Box2ui(0, 0, maxdim, maxdim));
	width = height = maxdim;
	delete image;
	image = scale;
    }

    texture = renderer->createTexture(levels + 1, image->extent().z);
	
    for (unsigned int i = levels; i != 0; i--) {
	texture->loadLevel(i, (unsigned char*) image->address(0, 0));
	    
	if (i > 0) {
	    Pixmap* scale = new Pixmap(width / 2,
				       height / 2,
				       image->extent().z,
				       format,
				       8);
	    copy(*image, Box2ui(0, 0, width, height),
		 *scale, Box2ui(0, 0, width/2, height/2));
	    width /= 2;
	    height /= 2;
	    delete image;
	    image = scale;
	}
    }
    texture->loadLevel(0, (unsigned char*) image->address(0, 0));
    delete image;
    
    return texture;
}

int
main(int argc, char** argv)
{
    static const unsigned int width = 50;
    static const unsigned int depth = 50;
    static const float mul = 1.25f;
    float waveHeight = 0;
    float maxWaveHeight = 0.7f;
    float waveHeightDiff = 0.05f;
    bool reflecting = false;
    bool wireframe = false;

    bool fScreen = false;
    if (argc > 1 && strcmp(argv[1], "-f") == 0) fScreen = true;
    
    Model water;
    Model seabed;
    Model sky;
    
    Factory* f = Factory::create();
    Renderer* r = f->createRenderer(NULL, "Water", 0, 0, 640, 480, fScreen);
    Buffer* buffer = r->backBuffer();
    Texture* tex1 = loadBitmap(r, "teapot3.bmp");
    Shader* shader1 = r->createShader(tex1);
    Texture* tex2 = loadBitmap(r, "sky.bmp");
    Shader* shader2 = r->createShader(tex2);
    LightManager* lightman = r->createLightManager();

    r->setBackgroundColor(Vec3f(0.5f, 1, 1));
    
    Light l[2];
    l[0].spot(Vec3f(-5, 5, -2), Vec3f(-10, 5, -2), 1.0f, 1.5f, 20);
    l[1].point(Vec3f(-5, 5, -2), 35);
    l[0].falloff0 = 0.2f;
    l[0].falloff1 = 0.3f;
    l[0].falloff2 = 0.0f;
    l[0].color = Vec3f(0.6f, 0.6f, 1);
    l[1].falloff0 = 0.2f;
    l[1].falloff1 = 0.3f;
    l[1].falloff2 = 0.0f;
    l[1].color = Vec3f(1, 1, 1);
    
    lightman->set(l, 2);
    lightman->setOptions(LightManager::ModulateAmbient
			 |LightManager::ModulateDiffuse
			 |LightManager::LocalViewer);
    lightman->setAmbient(Vec3f(1, 1, 1));
    buffer->setLights(lightman);
    
    buffer->setFrustum(Box2f(-0.5f, -0.5f, 0.5f, 0.5f), 1.0f, 60.0f);
    
    Material m;
    m.emissive = Vec3f(0, 0, 0);
    m.ambient = Vec3f(1, 1, 1);
    m.diffuse = Vec3f(1, 1, 1);
    m.specular = Vec3f(1, 1, 1);
    m.shininess = 5;
    m.brilliance = 1;

    Matrix4f mat;
    mat.setIdentity();
    mat.position(Vec3f(0, 20, -40));
    mat.setRotation(Vec3f(0, -0.4f, 0.8f), Vec3f(0, 0.8f, 0.4f));
    buffer->setMatrix(Buffer::CameraMatrix, mat);

    water.vertices = f->createVertexArray(FVF::Coord | 
					  FVF::Normal | 
					  FVF::MaterialColor |
					  FVF::TexCoord(1),
					  width * depth);
    water.primitives = f->createPrimitiveArray(PrimitiveArray::Triangles,
					       width * depth * 2);
    water.matrix.setIdentity();
    water.material = m;
    water.shader = shader1;

    seabed.vertices = f->createVertexArray(FVF::Coord | 
					   FVF::Normal | 
					   FVF::MaterialColor |
					   FVF::TexCoord(1),
					   4);
    seabed.primitives = f->createPrimitiveArray(PrimitiveArray::Quads, 1);
    {
	PrimitiveArray::Element* quad = seabed.primitives->lock();
	quad[0] = 0;
	quad[1] = 2;
	quad[2] = 3;
	quad[3] = 1;
	seabed.primitives->unlock();
    }
    buildVertexGrid(f, seabed.vertices,
		    2, 2, width * mul / 2, 0.0f, 0.0f);
    seabed.matrix.setIdentity();
    seabed.matrix.position(Vec3f(0, -10, 0));
    seabed.material = m;
    seabed.shader = shader1;
    seabed.shader->stageEnable(0, ShaderStage::TextureTransform);

    sky.vertices = f->createVertexArray(FVF::Coord | 
					FVF::Normal | 
					FVF::MaterialColor |
					FVF::TexCoord(1),
					4);
    sky.primitives = f->createPrimitiveArray(PrimitiveArray::Quads, 1);
    {
	PrimitiveArray::Element* quad = sky.primitives->lock();
	quad[0] = 0;
	quad[1] = 1;
	quad[2] = 3;
	quad[3] = 2;
	sky.primitives->unlock();
    }
    buildVertexGrid(f, sky.vertices,
		    2, 2, width * mul, 0.0f, 0.0f);
    sky.matrix.setIdentity();
    sky.matrix.position(Vec3f(0, 10, 0));
    sky.material = m;
    sky.shader = shader2;
    
    float offset = 0;
    Matrix4f m1, m2;

    m1.setIdentity();
    m2.setIdentity();

    Vec3f lightAxis(10, 5, 2);
    lightAxis.normalize();
    Quat4f rot(lightAxis, 0.01f);

    Matrix4f seaTex;
    seaTex.setIdentity();
    seaTex.position(Vec3f(0.02f, 0.02f, 0));
    
    while (1) {
	l[0].direction = rot.direction();
	lightman->set(0, l[0]);
	
	rot *= Quat4f(Vec3f(0, 0, 1), 0.1f);
	
	m1 = m1 * m2;
	buffer->pushMatrix(Buffer::ModelMatrix, m1);

	buffer->multiplyMatrix(Buffer::Texture0Matrix, seaTex);

	seabed.begin(buffer);
	seabed.render(buffer);
	Matrix4f seaWorld = buffer->matrix(Buffer::ModelMatrix);
	seabed.end(buffer);

	sky.begin(buffer);
	sky.render(buffer);
	Matrix4f skyWorld = buffer->matrix(Buffer::ModelMatrix);
	sky.end(buffer);

	buildVertexGrid(f, water.vertices,
			width, depth, mul, offset, waveHeight);
	buildTriangleGrid(f, water.primitives, water.vertices,
			  width, depth);

	waveHeight += waveHeightDiff;

	if (waveHeight < 0) waveHeightDiff = 0.05f;
	else if (waveHeight > maxWaveHeight) waveHeightDiff = -0.005f;

	if (reflecting)
	    reflectModel(water, buffer->matrix(Buffer::ModelMatrix),
			 sky, skyWorld,
			 buffer->worldEye());
	else
	    refractModel(water, buffer->matrix(Buffer::ModelMatrix),
			 seabed, seaWorld,
			 buffer->worldEye());

	if (wireframe) {
	    PrimitiveArray* wire =
		makeWireframeFromTriangles(f, water.primitives);
	    PrimitiveArray* tmp = water.primitives;
	    water.primitives = wire;
	    water.render(buffer);
	    water.primitives = tmp;
	    delete wire;
	} else
	    water.render(buffer);

	r->update();

	offset -= 0.3f;
	buffer->popMatrix(Buffer::ModelMatrix);

	Draw::Event e;
	bool running;
	running = r->nextEvent(e, false);
	while (running && e.code != Draw::Event::None) {
	    static bool drag = false;
	    static Vec2f lastPos;
	    
	    switch (e.code) {
	    case Draw::Event::KeyPress:
		switch (e.key) {
		case Draw::Key::space:
		    reflecting = !reflecting;
		    if (reflecting)
			water.shader = sky.shader;
		    else
			water.shader = seabed.shader;
		    break;

		case Draw::Key::w:
		    wireframe = !wireframe;
		    break;
		    
		case Draw::Key::q:
		    r->shutdown();
		    break;
		}
		break;
		
	    case Draw::Event::MousePress:
		drag = true;
		break;
		
	    case Draw::Event::MouseRelease:
		drag = false;
		m2.setIdentity();
		break;
		
	    case Draw::Event::MousePosition:
		{
		    Vec2f pos = Vec2f((float)e.mousePosition.x,
				      (float)e.mousePosition.y);
		    if (drag) {
			Vec2f delta = pos - lastPos;
			
			float deltaR = delta.modulus();
			float radius = 300;
			float denom;
			
			denom = sqrtf(radius * radius + deltaR * deltaR);
			
			if (deltaR == 0 || denom == 0) break;
			
			float angle = deltaR / denom;
			m2.setRotation(Vec3f(delta.y / deltaR,
					     delta.x / deltaR, 0),
				       -angle);
		    }
		    lastPos = pos;
		}
		break;

	    case Event::WindowSize:
		r->resize(e.windowSize.width, e.windowSize.height);
		r->backBuffer()->setWindow(Box2f(0, 0,
						 float(e.windowSize.width),
						 float(e.windowSize.height)),
					   true);
		break;

	    default:
		break;		// ignore other events
	    }
	    running = r->nextEvent(e, false);
	}
	if (!running)
	    break;
    }

    delete f;
    
    return 0;
}


#ifdef STATIC

/* Make sure the drivers we want are linked in. */

#ifdef q_windows
extern int QDriver_OS_Win32;
int* osref = &QDriver_OS_Win32;

extern int QDriver_Input_Win32;
int* inpref = &QDriver_Input_Win32;

extern int QDriver_Display_DX7;
int* displayref = &QDriver_Display_DX7;
#else    
extern int QDriver_OS_X11;
int* osref = &QDriver_OS_X11;

extern int QDriver_Display_OpenGL;
int* displayref = &QDriver_Display_OpenGL;
#endif

#endif // STATIC
