/** 
 * @file llmeshreduction.cpp
 * @brief LLMeshReduction class implementation
 *
 * $LicenseInfo:firstyear=2004&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llmeshreduction.h"
#include "llgl.h"
#include "llvertexbuffer.h"

#include "glod/glod.h"

static BOOL stop_gloderror()
{
	GLuint error = glodGetError();

	if (error != GLOD_NO_ERROR)
	{
		llwarns << "GLOD error detected: " << std::hex << error << llendl;
		return TRUE;
	}

	return FALSE;
}


void create_vertex_buffers_from_model(LLModel* model, std::vector<LLPointer <LLVertexBuffer> >& vertex_buffers)
{
#if 0 //VECTORIZE THIS ?
	vertex_buffers.clear();
	
	for (S32 i = 0; i < model->getNumVolumeFaces(); ++i)
	{
		const LLVolumeFace &vf = model->getVolumeFace(i);
		U32 num_vertices = vf.mNumVertices;
		U32 num_indices = vf.mNumIndices;

		if (!num_vertices || ! num_indices)
		{
			continue;
		}

		LLVertexBuffer* vb =
			new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0, 0);
		
		vb->allocateBuffer(num_vertices, num_indices, TRUE);

		LLStrider<LLVector3> vertex_strider;
		LLStrider<LLVector3> normal_strider;
		LLStrider<LLVector2> tc_strider;
		LLStrider<U16> index_strider;

		vb->getVertexStrider(vertex_strider);
		vb->getNormalStrider(normal_strider);
		vb->getTexCoord0Strider(tc_strider);

		vb->getIndexStrider(index_strider);

		// build vertices and normals
		for (U32 i = 0; (S32)i < num_vertices; i++)
		{
			*(vertex_strider++) = vf.mVertices[i].mPosition;
			*(tc_strider++) = vf.mVertices[i].mTexCoord;
			LLVector3 normal = vf.mVertices[i].mNormal;
			normal.normalize();
			*(normal_strider++) = normal;
		}

		// build indices
		for (U32 i = 0; i < num_indices; i++)
		{
			*(index_strider++) = vf.mIndices[i];
		}


		vertex_buffers.push_back(vb);
	}
#endif
}

void create_glod_object_from_vertex_buffers(S32 object, S32 group, std::vector<LLPointer <LLVertexBuffer> >& vertex_buffers)
{
	glodNewGroup(group);
	stop_gloderror();
	glodNewObject(object, group, GLOD_DISCRETE);
	stop_gloderror();

	for (U32 i = 0; i < vertex_buffers.size(); ++i)
	{
		vertex_buffers[i]->setBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0);
		
		U32 num_indices = vertex_buffers[i]->getNumIndices();
		
		if (num_indices > 2)
		{
			glodInsertElements(object, i, GL_TRIANGLES, num_indices, GL_UNSIGNED_SHORT,
							   vertex_buffers[i]->getIndicesPointer(), 0, 0.f);
		}
		stop_gloderror();
	}
	
	glodBuildObject(object);
	stop_gloderror();
}

// extract the GLOD data into vertex buffers
void create_vertex_buffers_from_glod_object(S32 object, S32 group, std::vector<LLPointer <LLVertexBuffer> >& vertex_buffers)
{
	vertex_buffers.clear();
	
	GLint patch_count = 0;
	glodGetObjectParameteriv(object, GLOD_NUM_PATCHES, &patch_count);
	stop_gloderror();

	GLint* sizes = new GLint[patch_count*2];
	glodGetObjectParameteriv(object, GLOD_PATCH_SIZES, sizes);
	stop_gloderror();

	GLint* names = new GLint[patch_count];
	glodGetObjectParameteriv(object, GLOD_PATCH_NAMES, names);
	stop_gloderror();

	for (S32 i = 0; i < patch_count; i++)
	{
		LLPointer<LLVertexBuffer> buff =
			new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0, 0);
			
		if (sizes[i*2+1] > 0 && sizes[i*2] > 0)
		{
			buff->allocateBuffer(sizes[i*2+1], sizes[i*2], true);
			buff->setBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0);
			glodFillElements(object, names[i], GL_UNSIGNED_SHORT, buff->getIndicesPointer());
			stop_gloderror();
		}
		else
		{
            // this face was eliminated, create a dummy triangle (one vertex, 3 indices, all 0)
			buff->allocateBuffer(1, 3, true);
		}

		vertex_buffers.push_back(buff);
	}
	
	glodDeleteObject(object);
	stop_gloderror();
	glodDeleteGroup(group);
	stop_gloderror();
	
	delete [] sizes;
	delete [] names;
}


LLPointer<LLModel> create_model_from_vertex_buffers(std::vector<LLPointer <LLVertexBuffer> >& vertex_buffers)
{
	// extract the newly reduced mesh

	// create our output model
	LLVolumeParams volume_params;
	volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
	LLPointer<LLModel> out_model = new LLModel(volume_params, 0.f);

	out_model->setNumVolumeFaces(vertex_buffers.size());

	// build new faces from each vertex buffer
	for (GLint i = 0; i < vertex_buffers.size(); ++i)
	{
		LLStrider<LLVector3> pos;
		LLStrider<LLVector3> norm;
		LLStrider<LLVector2> tc;
		LLStrider<U16> index;

		vertex_buffers[i]->getVertexStrider(pos);
		vertex_buffers[i]->getNormalStrider(norm);
		vertex_buffers[i]->getTexCoord0Strider(tc);
		vertex_buffers[i]->getIndexStrider(index);

		out_model->setVolumeFaceData(i, pos, norm, tc, index,
									 vertex_buffers[i]->getNumVerts(), vertex_buffers[i]->getNumIndices());
	}
	
	return out_model;
}



LLMeshReduction::LLMeshReduction()
{
	mCounter = 1;

	glodInit();
}

LLMeshReduction::~LLMeshReduction()
{
	glodShutdown();
}


LLPointer<LLModel> LLMeshReduction::reduce(LLModel* in_model, F32 limit, S32 mode)
{
	LLVertexBuffer::unbind();

	// create vertex buffers from model
	std::vector<LLPointer<LLVertexBuffer> > in_vertex_buffers;
	create_vertex_buffers_from_model(in_model, in_vertex_buffers);

	// create glod object from vertex buffers
	stop_gloderror();
	S32 glod_group = mCounter++;
	S32 glod_object = mCounter++;
	create_glod_object_from_vertex_buffers(glod_object, glod_group, in_vertex_buffers);

	
	// set reduction parameters
	stop_gloderror();

	if (mode == TRIANGLE_BUDGET)
	{
		// triangle budget mode
		glodGroupParameteri(glod_group, GLOD_ADAPT_MODE, GLOD_TRIANGLE_BUDGET);
		stop_gloderror();		
		glodGroupParameteri(glod_group, GLOD_ERROR_MODE, GLOD_OBJECT_SPACE_ERROR);
		stop_gloderror();		
		S32 triangle_count = (S32)limit;
		glodGroupParameteri(glod_group, GLOD_MAX_TRIANGLES, triangle_count);
		stop_gloderror();		
	}
	else if (mode == ERROR_THRESHOLD)
	{ 
		// error threshold mode
		glodGroupParameteri(glod_group, GLOD_ADAPT_MODE, GLOD_ERROR_THRESHOLD);
		glodGroupParameteri(glod_group, GLOD_ERROR_MODE, GLOD_OBJECT_SPACE_ERROR);
		F32 error_threshold = limit;
		glodGroupParameterf(glod_group, GLOD_OBJECT_SPACE_ERROR_THRESHOLD, error_threshold);
		stop_gloderror();
	}
	
	else
	{
		// not a legal mode
		return NULL;
	}


	// do the reduction
	glodAdaptGroup(glod_group);
	stop_gloderror();

	// convert glod object into vertex buffers
	std::vector<LLPointer<LLVertexBuffer> > out_vertex_buffers;
	create_vertex_buffers_from_glod_object(glod_object, glod_group, out_vertex_buffers);

	// convert vertex buffers into a model
	LLPointer<LLModel> out_model = create_model_from_vertex_buffers(out_vertex_buffers);

	
	return out_model;
}


