/*-
 * 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: spot.cpp,v 1.13 2000/05/04 14:46:09 nps Exp $
 */

#include <stdlib.h>

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

using namespace Draw;
using namespace Maths;

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

struct Triangles
{
    VertexArray*	vertices;
    PrimitiveArray*	triangles;
};

static Triangles triangles;

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

    int hWidth = width / 2;
    int hDepth = depth / 2;
    
    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;

	    v.coord.y = 0;
	    v.norm = Vec3f(0, 1, 0);
	    v.col = Color4b(Vec3f((v.coord.y + 1) * 0.5f,
				  0.4f,
				  0.5f));
	    v.col = Vec3f(1, 1, 1);;
	    v.tex = Vec2f(v.coord.x, v.coord.z);
	    
	    vArray->set(idx1, (void*)&v);

	    v.coord.x = -v.coord.x;
	    v.norm.x = -v.norm.x;
	    v.tex.x = -v.tex.x;

	    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.x = -v.tex.x;
	    v.tex.y = -v.tex.y;
	    
	    vArray->set(idx3, (void*)&v);

	    v.coord.x = -v.coord.x;
	    v.norm.x = -v.norm.x;
	    v.tex.x = -v.tex.x;

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

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

    vArray->unlock();
}

PrimitiveArray*
buildTriangleGrid(Factory* f, unsigned int width, unsigned int depth)
{
    PrimitiveArray* tArray =
	f->createPrimitiveArray(PrimitiveArray::Triangles, 0);

    for (unsigned int i = 0; i < width - 1; ++i) {
	for (unsigned int j = 0; j < depth - 1; ++j) {
	    unsigned int k = (i * depth) + j;

	    tArray->add(k, k + depth, k + 1);
	    tArray->add(k + 1, k + depth, k + depth + 1);
	}
    }

    return tArray;
}

static Texture* makeTexture(Renderer* r)
{
#define B	128,128,128
#define W	255,255,255
#define G	192,192,192

    unsigned char checker3[] = {
	B,B,B,B,W,W,W,W,
	B,B,B,B,W,W,W,W,
	B,B,B,B,W,W,W,W,
	B,B,B,B,W,W,W,W,
	W,W,W,W,B,B,B,B,
	W,W,W,W,B,B,B,B,
	W,W,W,W,B,B,B,B,
	W,W,W,W,B,B,B,B,
    };
    unsigned char checker2[] = {
	B,B,W,W,
	B,B,W,W,
	W,W,B,B,
	W,W,B,B,
    };
    unsigned char checker1[] = {
	B,W,
	W,B,
    };
    unsigned char checker0[] = {
	G
    };

    Texture* tex = r->createTexture(4, 24);
    tex->loadLevel(3, checker3);
    tex->loadLevel(2, checker2);
    tex->loadLevel(1, checker1);
    tex->loadLevel(0, checker0);
    return tex;

#undef B
#undef W
}

int
main()
{
    static const unsigned int width = 100;
    static const unsigned int depth = 100;
    static const float mul = 25.0f / width;
    
    Factory* f = Factory::create();
    Renderer* r = f->createRenderer(NULL, "Spot", 0, 0, 640, 480, false);
    Buffer* buffer = r->backBuffer();
    Texture* checker = makeTexture(r);
    Shader* shader = r->createShader(checker);
    LightManager* lightman = r->createLightManager();

    Light l[3];
    l[0].spot(Vec3f(0, 10, 12), Vec3f(0, -10, -12), 0.2f, 0.3f, 30);
    l[0].color = Vec3f(1, 0, 0);
    l[1].spot(Vec3f(-10, 10, -5), Vec3f(10, -10, 5), 0.2f, 0.3f, 30);
    l[1].color = Vec3f(0, 1, 0);
    l[2].spot(Vec3f(10, 10, -5), Vec3f(-10, -10, 5), 0.2f, 0.3f, 30);
    l[2].color = Vec3f(0, 0, 1);
    
    lightman->set(l, 3);
    lightman->setOptions(LightManager::LocalViewer);
    lightman->setAmbient(Vec3f(0.4f, 0.4f, 0.4f));
    buffer->setLights(lightman);
    
    buffer->setFrustum(Box2f(-0.5f, -0.5f, 0.5f, 0.5f), 1.0f, 100.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;
    buffer->setMaterial(m);

    Matrix4f mat;
    mat.setIdentity();
    //mat.setRotation(Vec3f(0, -20, 50), Vec3f(0, 1, 0));
    //mat.position(Vec3f(0, 30, -50));
    mat.setRotation(Vec3f(0, -50, 0), Vec3f(0, 0, 1));
    mat.position(Vec3f(0, 50, 0));
    buffer->setMatrix(Buffer::CameraMatrix, mat);

    triangles.vertices = f->createVertexArray(FVF::Coord | 
					      FVF::Normal | 
					      FVF::MaterialColor |
					      FVF::TexCoord(1),
					      width * depth);
    triangles.triangles = buildTriangleGrid(f, width, depth);
    buildVertexGrid(f, triangles.vertices, width, depth, mul);

    Matrix4f m1, m2;

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

    while (1) {
	m1 = m1 * m2;
	buffer->pushMatrix(Buffer::ModelMatrix, m1);

	buffer->setVertices(triangles.vertices);
	buffer->setShader(shader);
	buffer->render(triangles.triangles, 0,
		       triangles.triangles->size());
	r->update();

	buffer->popMatrix(Buffer::ModelMatrix);
	
	Draw::Event e;
	if (r->nextEvent(e, false)) { // don't wait for an event
	    static bool drag = false;
	    static Vec2f lastPos;
	    
	    switch (e.code) {
	    case Draw::Event::KeyPress:
		switch (e.key) {
		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;

	    default:
		break;		// ignore other events
	    }
	} else {
	    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
