#include <math.h>
#include <assert.h>

#include "demoreel.h"

// Retained mode: a procedural torus knot with env-mapped chrome,
// lit by two colored directional lights. Exercises Geometry
// creation, materials, MatFX and the world/light path.

using namespace rw;

#define KNOT_P 2
#define KNOT_Q 3

#define SEGS 160
#define SIDES 12
#define NUMVERTS ((SEGS+1)*(SIDES+1))
#define NUMTRIS (SEGS*SIDES*2)

static Atomic *knotAtomic;
static Frame *knotFrame;
static Light *ambient;
static Light *keyLight;
static Light *fillLight;
static Texture *envTex;
static Texture *gridTex;

static V3d
knotCenter(float t)
{
	V3d p;
	float r = 6.0f + 2.5f*cosf(KNOT_Q*t);
	p.x = r*cosf(KNOT_P*t);
	p.y = r*sinf(KNOT_P*t);
	p.z = 2.5f*sinf(KNOT_Q*t);
	return p;
}

static Geometry*
CreateKnotGeometry(void)
{
	Geometry *geo = Geometry::create(NUMVERTS, NUMTRIS,
		Geometry::POSITIONS | Geometry::NORMALS |
		Geometry::LIGHT | Geometry::TEXTURED);
	assert(geo);

	MorphTarget *mt = &geo->morphTargets[0];
	V3d *verts = mt->vertices;
	V3d *norms = mt->normals;
	TexCoords *uv = geo->texCoords[0];

	float tuberad = 1.1f;
	int i, j, v;

	v = 0;
	for(i = 0; i <= SEGS; i++){
		float t = (float)i/SEGS*2.0f*3.14159265f;
		V3d c = knotCenter(t);
		V3d tan = normalize(sub(knotCenter(t+0.01f), knotCenter(t-0.01f)));
		static V3d worldup = { 0.0f, 0.0f, 1.0f };
		V3d n = normalize(cross(worldup, tan));
		V3d b = cross(tan, n);

		for(j = 0; j <= SIDES; j++){
			float a = (float)j/SIDES*2.0f*3.14159265f;
			V3d ring = add(scale(n, cosf(a)), scale(b, sinf(a)));
			verts[v] = add(c, scale(ring, tuberad));
			norms[v] = ring;
			// keep u*texwidth below the ~1024 texel GS limit
			uv[v].u = (float)i/SEGS*6.0f;
			uv[v].v = (float)j/SIDES;
			v++;
		}
	}

	Triangle *tri = geo->triangles;
	for(i = 0; i < SEGS; i++)
		for(j = 0; j < SIDES; j++){
			int r0 = i*(SIDES+1) + j;
			int r1 = (i+1)*(SIDES+1) + j;
			tri->v[0] = r0;   tri->v[1] = r0+1; tri->v[2] = r1;
			tri->matId = 0;
			tri++;
			tri->v[0] = r0+1; tri->v[1] = r1+1; tri->v[2] = r1;
			tri->matId = 0;
			tri++;
		}

	Material *mat = Material::create();
	gridTex = MakeGridTexture();
	if(gridTex)
		mat->setTexture(gridTex);
	envTex = MakeEnvTexture();
	MatFX::setEffects(mat, MatFX::ENVMAP);
	MatFX *mfx = MatFX::get(mat);
	if(envTex)
		mfx->setEnvTexture(envTex);
	mfx->setEnvFrame(Scene.camera->getFrame());
	mfx->setEnvCoefficient(0.6f);
	geo->matList.appendMaterial(mat);
	mat->destroy();	// list holds a ref now

	geo->calculateBoundingSphere();
	geo->unlock();

	return geo;
}

static Light*
MakeDirLight(float r, float g, float b, float yaw, float pitch)
{
	static V3d Xaxis = { 1.0f, 0.0f, 0.0f };
	static V3d Zaxis = { 0.0f, 0.0f, 1.0f };
	Light *light = Light::create(Light::DIRECTIONAL);
	light->setColor(r, g, b);
	Frame *f = Frame::create();
	f->rotate(&Xaxis, pitch, COMBINEREPLACE);
	f->rotate(&Zaxis, yaw, COMBINEPOSTCONCAT);
	light->setFrame(f);
	Scene.world->addLight(light);
	return light;
}

static void
KnotInit(void)
{
	Geometry *geo = CreateKnotGeometry();

	knotFrame = Frame::create();
	knotAtomic = Atomic::create();
	knotAtomic->setGeometry(geo, 0);
	geo->destroy();	// atomic holds a ref now
	knotAtomic->setFrame(knotFrame);
	MatFX::enableEffects(knotAtomic);
	Scene.world->addAtomic(knotAtomic);

	ambient = Light::create(Light::AMBIENT);
	ambient->setColor(0.15f, 0.15f, 0.2f);
	Scene.world->addLight(ambient);
	keyLight = MakeDirLight(1.0f, 0.85f, 0.6f, 30.0f, 120.0f);
	fillLight = MakeDirLight(0.3f, 0.4f, 0.9f, 200.0f, 60.0f);
}

static void
KnotTerm(void)
{
	if(knotAtomic){
		Scene.world->removeAtomic(knotAtomic);
		knotAtomic->destroy();
		knotAtomic = nil;
	}
	if(knotFrame){
		knotFrame->destroy();
		knotFrame = nil;
	}
	if(ambient){
		Scene.world->removeLight(ambient);
		ambient->destroy();
		ambient = nil;
	}
	if(keyLight){
		Scene.world->removeLight(keyLight);
		Frame *f = keyLight->getFrame();
		keyLight->setFrame(nil);
		f->destroy();
		keyLight->destroy();
		keyLight = nil;
	}
	if(fillLight){
		Scene.world->removeLight(fillLight);
		Frame *f = fillLight->getFrame();
		fillLight->setFrame(nil);
		f->destroy();
		fillLight->destroy();
		fillLight = nil;
	}
	if(envTex){
		envTex->destroy();
		envTex = nil;
	}
	if(gridTex){
		gridTex->destroy();
		gridTex = nil;
	}
}

static void
KnotUpdate(float dt)
{
	static V3d Xaxis = { 1.0f, 0.0f, 0.0f };
	static V3d Zaxis = { 0.0f, 0.0f, 1.0f };

	knotFrame->rotate(&Zaxis, dt*20.0f, COMBINEPOSTCONCAT);
	knotFrame->rotate(&Xaxis, dt*8.0f, COMBINEPRECONCAT);

	float ca = DemoTime*0.1f;
	V3d campos;
	campos.x = 24.0f*cosf(ca);
	campos.y = 24.0f*sinf(ca);
	campos.z = 8.0f*sinf(DemoTime*0.23f);
	V3d origin = { 0.0f, 0.0f, 0.0f };
	LookAt(Scene.camera->getFrame(), campos, origin);
}

static void
KnotRender(void)
{
	SetRenderState(ZTESTENABLE, 1);
	SetRenderState(ZWRITEENABLE, 1);
	SetRenderState(CULLMODE, CULLBACK);

	knotAtomic->render();
}

DemoScene KnotScene = {
	"Torus knot",
	KnotInit,
	KnotTerm,
	KnotUpdate,
	KnotRender
};
