/*-
 * 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: primbuilder.cpp,v 1.11 2000/06/15 10:44:45 peterj Exp $
 */

#include <stddef.h>
#include <string.h>		// for memcpy
#include "primbuilder.h"

PrimBuilder::PrimBuilder()
{
    top.uScale(3);
    top.vScale(3);
    
    bottom.uScale(1);
    bottom.vScale(1);
    
    cylmapper.uScale(2);
    cylmapper.vScale(1);
    
    mapper.uScale(2);
    mapper.vScale(2);
}

void
PrimBuilder::addTriangle(int w1, int w2, int w3)
{
    tris->add(w1, w2, w3);
}

unsigned int
PrimBuilder::addWedge(const Wedge& w)
{
    return verts->add((void*)&w);
}

void
PrimBuilder::clear()
{
    delete verts;
    delete tris;
}

void
PrimBuilder::clearRenderables()
{
    delete [] renderables;
    renderables = 0;
}

void
PrimBuilder::alloc()
{
    verts = factory->createVertexArray(Draw::FVF::Coord |
				       Draw::FVF::Normal |
				       Draw::FVF::MaterialColor |
				       Draw::FVF::TexCoord(1),
				       0);
    
    tris = factory->createPrimitiveArray(Draw::PrimitiveArray::Triangles, 0);
}

static void
collectTriangles(unsigned int v1, unsigned int v2, unsigned int v3)
{
    PrimBuilder::primitive.addTriangle(v1, v2, v3);
}

static unsigned int
collectWedge(const Wedge& wedge)
{
    Wedge w = wedge;

    const Vec3f& v = wedge.vertex;
    w.vertexColor = Color4b(Vec4f((v.x + 1) * 0.5f,
                                  (v.y + 1) * 0.5f,
                                  (v.z + 1) * 0.5f, 1));
    w.vertexColor = Color4b(255,255,255);

    return PrimBuilder::primitive.addWedge(w);
}

void
PrimBuilder::buildPrim(unsigned int p)
{
    clearRenderables();
    alloc();

    type = p;
    
    switch (p) {
    case 0:
	PrimGenerator::makeSphere(50, 50, mapper,
				  collectTriangles,
				  collectWedge);
	break;
    case 1:
	PrimGenerator::makeCylinder(50, 20, 10,
				    top, bottom, cylmapper,
				    collectTriangles,
				    collectWedge);
	break;
    case 2:
	PrimGenerator::makeFrustum(0.25, 50, 30, 20,
				   top, bottom, cylmapper,
				   collectTriangles,
				   collectWedge);
	break;
    }

    generateRenderables();
    clear();
}

void
PrimBuilder::render(Buffer* buffer, Shader* shader,
		    LightManager* lightman)
{
    Draw::Material material;
    material.emissive = 0;
    material.ambient = Vec3f(1, 1, 1);
    material.diffuse = Vec3f(1, 1, 1);
    material.specular = Vec3f(1, 1, 1);
    material.shininess = 15;
    material.brilliance = 1;
    
    buffer->setMaterial(material);
    
    buffer->setLights(lightman);
    buffer->setShader(shader);
    
    for (unsigned int i = 0; i < renderCount; ++i) {
	TriPair& tp = renderables[i];

	buffer->setVertices(tp.verts);
	buffer->render(tp.tris, 0, tp.tris->size(), 0, tp.verts->size() - 1);
    }
}

void
PrimBuilder::reBuild()    
{
    buildPrim(type);
}

/**
 * Take the factory built vertex array and build renderer vertex arrays from
 * it.
 */

// Each src vertex might end up in multiple dest arrays if it happens to be on
// the edge.

struct DestSlot
{
    unsigned int renderable;
    unsigned int index;
};

struct VertexAllocation
{
    VertexAllocation()
	{
	    slotCount = 0;
	    slots = 0;
	}
    ~VertexAllocation()
	{
	    if (slots) {
		delete [] slots;
	    }
	}

    bool inRenderable(unsigned int r, unsigned int& idx)
	{
	    for (unsigned int i = 0; i < slotCount; ++i) {
		if (slots[i].renderable == r) {
		    idx = slots[i].index;
		    return true;
		}
	    }

	    return false;
	}

    void addRenderable(unsigned int r, unsigned int idx)
	{
	    DestSlot* newSlots = new DestSlot[slotCount + 1];
	    for (unsigned int i = 0; i < slotCount; ++i) {
		newSlots[i] = slots[i];
	    }

	    newSlots[slotCount].renderable = r;
	    newSlots[slotCount].index = idx;

	    if (slots)
		delete [] slots;

	    slotCount++;
	    slots = newSlots;
	}
    
    unsigned int slotCount;
    DestSlot* slots;
};

void
PrimBuilder::generateRenderables()
{
    // First guess at how many renderables we will need.
    unsigned int vertCount = verts->size();
    unsigned int triCount = tris->size();
    
    // Build an empty array to find out how big they can be.
    VertexArray* sizer = renderer->createVertexArray(0, 0);
    unsigned int maxSize = sizer->maxSize();
    delete sizer;

    if (maxSize == 0) {
	renderCount = 1;
	maxSize = verts->size();
    } else {
	renderCount = (vertCount / maxSize) + 1;
    }
    
    renderables = new TriPair[renderCount];

#ifndef q_ps2
    // Optimize the primitive array to make the order a bit better. This
    // arranges the primitives in such a way that each primitive introduces
    // the least new vertices into use, assuming a vertex cache of about 10 or
    // so.
    tris->optimize();
#else
    // XXX On the PS2 this causes problems when compiling optimised, so disable
    // it for now
#endif

    unsigned int primPos = 0;

    tris->lock();
    verts->lock();

    VertexAllocation* allocedSlot = new VertexAllocation[verts->size()];

    unsigned int i = 0;
    while (primPos < triCount) {
	if (i >= renderCount) {
	    // Increase renderables
	    TriPair* tpnew = new TriPair[renderCount + 1];
	    for (unsigned int j = 0; j < renderCount; ++j) {
		tpnew[j].tris = renderables[j].tris;
		renderables[j].tris = 0;

		tpnew[j].verts = renderables[j].verts;
		renderables[j].verts = 0;
	    }

	    delete [] renderables;
	    renderables = tpnew;
	    ++renderCount;
	}
	
	TriPair& tp = renderables[i];

	tp.verts = renderer->createVertexArray(verts->format(), maxSize);
	tp.tris =
	    factory->createPrimitiveArray(Draw::PrimitiveArray::Triangles, 0);

	tp.verts->lock(Draw::VertexArray::Truncate);
	
	// Now go through the primitive array pulling out maxSize vertices.
	unsigned int soFar = 0;
	
	while (soFar < maxSize && primPos < triCount) {
	    Draw::PrimitiveArray::Element* idxs = tris->at(primPos);

	    // So we need to make sure that all the elements of this primitive
	    // can fit into the current vertex array.
	    unsigned int destIdx[3];
	    bool found[3];

	    // Now we need to make sure that all the indexes that were not
	    // already in this array can fit in it, otherwise we need to start
	    // a new one.
	    
	    unsigned int neededSlots = 3;
	    for (unsigned int k = 0; k < 3; ++k) {
		found[k] = allocedSlot[idxs[k]].inRenderable(i, destIdx[k]);
		if (found[k])
		    --neededSlots;
	    }
	    
	    if ((soFar + neededSlots) <= maxSize) {
		// We can allocate in this array.

		for (unsigned int k = 0; k < 3; ++k) {
		    if (!found[k]) {
			destIdx[k] = soFar;
			
			void* destData = tp.verts->at(destIdx[k]);
			void* srcData = verts->at(idxs[k]);

			memcpy(destData, srcData, verts->vertexSize());

			allocedSlot[idxs[k]].addRenderable(i, destIdx[k]);
			++soFar;
		    }
		}

		// We now have the primitive
		tp.tris->add(destIdx[0], destIdx[1], destIdx[2]);
		++primPos;
	    } else {
		// No more slots left.
		
		break;
	    }
	}

	tp.verts->unlock();
	tp.verts->optimize();
	++i;
    }

    delete [] allocedSlot;
    
    tris->unlock();
    verts->unlock();
}

