/* -----------------------------------------------------------------------------

 PicoModel Library

 Copyright (c) 2002, Randy Reddig & seaw0lf
 All rights reserved.

 Redistribution and use in source and binary forms, with or without modification,
 are permitted provided that the following conditions are met:

 Redistributions of source code must retain the above copyright notice, this list
 of conditions and the following disclaimer.

 Redistributions in binary form must reproduce the above copyright notice, this
 list of conditions and the following disclaimer in the documentation and/or
 other materials provided with the distribution.

 Neither the names of the copyright holders nor the names of its contributors may
 be used to endorse or promote products derived from this software without
 specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 ----------------------------------------------------------------------------- */

/* marker */
#define PICOMODEL_C

/* dependencies */
#include "picointernal.h"

/*
 PicoInit()
 initializes the picomodel library
 */

int PicoInit (void)
{
	/* successfully initialized -sea */
	return 1;
}

/**
 * @brief shuts the pico model library down
 */
void PicoShutdown (void)
{
}

/**
 * @brief returns last picomodel error code (see PME_* defines)
 */
int PicoError (void)
{
	return 0;
}

/**
 * @brief sets the ptr to the malloc function
 */
void PicoSetMallocFunc (void* (*func) ( size_t))
{
	if (func != NULL)
		_pico_ptr_malloc = func;
}

/**
 * @brief sets the ptr to the free function
 */
void PicoSetFreeFunc (void (*func) (void*))
{
	if (func != NULL)
		_pico_ptr_free = func;
}

/**
 * @brief sets the ptr to the file load function
 */
void PicoSetLoadFileFunc (void (*func) (char*, unsigned char**, int*))
{
	if (func != NULL)
		_pico_ptr_load_file = func;
}

/**
 * @brief sets the ptr to the free function
 */
void PicoSetFreeFileFunc (void (*func) (void*))
{
	if (func != NULL)
		_pico_ptr_free_file = func;
}

/**
 * @brief sets the ptr to the print function
 */
void PicoSetPrintFunc (void (*func) (int, const char*))
{
	if (func != NULL)
		_pico_ptr_print = func;
}

static picoModel_t *PicoModuleLoadModel (const picoModule_t *pm, const char* fileName, picoByte_t *buffer, int bufSize,
		int frameNum)
{
	/* see whether this module can load the model file or not */
	if (pm->canload(fileName, buffer, bufSize) == PICO_PMV_OK) {
		/* use loader provided by module to read the model data */
		picoModel_t *model = pm->load(fileName, frameNum, buffer, bufSize);
		if (model == NULL) {
			_pico_free_file(buffer);
			return NULL;
		}

		/* assign pointer to file format module */
		model->module = pm;

		return model;
	}

	return NULL;
}

/**
 * @brief the meat and potatoes function
 */
picoModel_t *PicoLoadModel (char* fileName, int frameNum)
{
	const picoModule_t** modules;
	picoModel_t *model;
	picoByte_t *buffer;
	int bufSize;

	/* init */
	model = NULL;

	/* make sure we've got a file name */
	if (fileName == NULL) {
		_pico_printf(PICO_ERROR, "PicoLoadModel: No filename given (fileName == NULL)");
		return NULL;
	}

	/* load file data (buffer is allocated by host app) */
	_pico_load_file(fileName, &buffer, &bufSize);
	if (bufSize < 0) {
		_pico_printf(PICO_ERROR, "PicoLoadModel: Failed loading model %s", fileName);
		return NULL;
	}

	/* get ptr to list of supported modules */
	modules = PicoModuleList(NULL);

	/* run it through the various loader functions and try
	 * to find a loader that fits the given file data */
	for (; *modules != NULL; modules++) {
		/* get module */
		const picoModule_t *pm = *modules;

		/* sanity check */
		if (pm == NULL)
			break;

		/* module must be able to load */
		if (pm->canload == NULL || pm->load == NULL)
			continue;

		model = PicoModuleLoadModel(pm, fileName, buffer, bufSize, frameNum);
		if (model != NULL) {
			/* model was loaded, so break out of loop */
			break;
		}
	}

	/* free memory used by file buffer */
	if (buffer)
		_pico_free_file(buffer);

	/* return */
	return model;
}

picoModel_t *PicoModuleLoadModelStream (const picoModule_t *module, const char* fileName, void* inputStream,
		PicoInputStreamReadFunc inputStreamRead, size_t streamLength, int frameNum)
{
	picoModel_t *model;
	picoByte_t *buffer;
	int bufSize;

	/* init */
	model = NULL;

	if (inputStream == NULL) {
		_pico_printf(PICO_ERROR, "PicoLoadModel: invalid input stream (inputStream == NULL)");
		return NULL;
	}

	if (inputStreamRead == NULL) {
		_pico_printf(PICO_ERROR, "PicoLoadModel: invalid input stream (inputStreamRead == NULL)");
		return NULL;
	}

	buffer = _pico_alloc(streamLength + 1);

	bufSize = (int) inputStreamRead(inputStream, buffer, streamLength);
	buffer[bufSize] = '\0';

	model = PicoModuleLoadModel(module, fileName, buffer, bufSize, frameNum);
	if (model != 0)
		_pico_free(buffer);

	/* return */
	return model;
}

/**
 * @brief creates a new pico model
 */
picoModel_t *PicoNewModel (void)
{
	picoModel_t *model;

	/* allocate */
	model = _pico_alloc(sizeof(picoModel_t));
	if (model == NULL)
		return NULL;

	/* model set up */
	_pico_zero_bounds(model->mins, model->maxs);

	/* set initial frame count to 1 -sea */
	model->numFrames = 1;

	/* return ptr to new model */
	return model;
}

/**
 * @brief frees a model and all associated data
 */
void PicoFreeModel (picoModel_t *model)
{
	int i;

	/* sanity check */
	if (model == NULL)
		return;

	/* free bits */
	if (model->name)
		_pico_free(model->name);

	if (model->fileName)
		_pico_free(model->fileName);

	/* free shaders */
	for (i = 0; i < model->numShaders; i++)
		PicoFreeShader(model->shader[i]);
	free(model->shader);

	/* free surfaces */
	for (i = 0; i < model->numSurfaces; i++)
		PicoFreeSurface(model->surface[i]);
	free(model->surface);

	/* free the model */
	_pico_free(model);
}

/**
 * @brief adjusts a models's memory allocations to handle the requested sizes.
 * will always grow, never shrink
 */
int PicoAdjustModel (picoModel_t *model, int numShaders, int numSurfaces)
{
	/* dummy check */
	if (model == NULL)
		return 0;

	/* bare minimums */
	/* sea: null surface/shader fix (1s=>0s) */
	if (numShaders < 0)
		numShaders = 0;
	if (numSurfaces < 0)
		numSurfaces = 0;

	/* additional shaders? */
	while (numShaders > model->maxShaders) {
		model->maxShaders += PICO_GROW_SHADERS;
		if (!_pico_realloc((void*) &model->shader, model->numShaders * sizeof(*model->shader),
				model->maxShaders * sizeof(*model->shader)))
			return 0;
	}

	/* set shader count to higher */
	if (numShaders > model->numShaders)
		model->numShaders = numShaders;

	/* additional surfaces? */
	while (numSurfaces > model->maxSurfaces) {
		model->maxSurfaces += PICO_GROW_SURFACES;
		if (!_pico_realloc((void*) &model->surface, model->numSurfaces * sizeof(*model->surface),
				model->maxSurfaces * sizeof(*model->surface)))
			return 0;
	}

	/* set shader count to higher */
	if (numSurfaces > model->numSurfaces)
		model->numSurfaces = numSurfaces;

	/* return ok */
	return 1;
}

/**
 * @brief creates a new pico shader and returns its index. -sea
 */
picoShader_t *PicoNewShader (picoModel_t *model)
{
	picoShader_t *shader;

	/* allocate and clear */
	shader = _pico_alloc(sizeof(*shader));
	if (shader == NULL)
		return NULL;

	/* attach it to the model */
	if (model != NULL) {
		/* adjust model */
		if (!PicoAdjustModel(model, model->numShaders + 1, 0)) {
			_pico_free(shader);
			return NULL;
		}

		/* attach */
		model->shader[model->numShaders - 1] = shader;
		shader->model = model;
	}

	/* setup default shader colors */
	_pico_set_color(shader->ambientColor, 0, 0, 0, 0);
	_pico_set_color(shader->diffuseColor, 255, 255, 255, 1);
	_pico_set_color(shader->specularColor, 0, 0, 0, 0);

	/* no need to do this, but i do it anyway */
	shader->transparency = 0;
	shader->shininess = 0;

	/* return the newly created shader */
	return shader;
}

/**
 * @brief frees a shader and all associated data -sea
 */
void PicoFreeShader (picoShader_t *shader)
{
	/* dummy check */
	if (shader == NULL)
		return;

	/* free bits */
	if (shader->name)
		_pico_free(shader->name);
	if (shader->mapName)
		_pico_free(shader->mapName);

	/* free the shader */
	_pico_free(shader);
}

/**
 * @brief finds a named shader in a model
 */
picoShader_t *PicoFindShader (picoModel_t *model, char* name, int caseSensitive)
{
	int i;

	/* sanity checks */
	if (model == NULL || name == NULL) /* sea: null name fix */
		return NULL;

	/* walk list */
	for (i = 0; i < model->numShaders; i++) {
		/* skip null shaders or shaders with null names */
		if (model->shader[i] == NULL || model->shader[i]->name == NULL)
			continue;

		/* compare the shader name with name we're looking for */
		if (caseSensitive) {
			if (!strcmp(name, model->shader[i]->name))
				return model->shader[i];
		} else if (!_pico_stricmp(name, model->shader[i]->name))
			return model->shader[i];
	}

	/* named shader not found */
	return NULL;
}

/**
 * @brief creates a new pico surface
 */
picoSurface_t *PicoNewSurface (picoModel_t *model)
{
	/* allocate and clear */
	picoSurface_t *surface = _pico_alloc(sizeof(*surface));
	if (surface == NULL)
		return NULL;

	/* attach it to the model */
	if (model != NULL) {
		char surfaceName[64];
		/* adjust model */
		if (!PicoAdjustModel(model, 0, model->numSurfaces + 1)) {
			_pico_free(surface);
			return NULL;
		}

		/* attach */
		model->surface[model->numSurfaces - 1] = surface;
		surface->model = model;

		/* set default name */
		sprintf(surfaceName, "Unnamed_%d", model->numSurfaces);
		PicoSetSurfaceName(surface, surfaceName);
	}

	/* return */
	return surface;
}

/**
 * @brief frees a surface and all associated data
 */
void PicoFreeSurface (picoSurface_t *surface)
{
	int i;

	/* dummy check */
	if (surface == NULL)
		return;

	/* free bits */
	_pico_free(surface->xyz);
	_pico_free(surface->normal);
	_pico_free(surface->smoothingGroup);
	_pico_free(surface->index);
	_pico_free(surface->faceNormal);

	if (surface->name)
		_pico_free(surface->name);

	/* free arrays */
	for (i = 0; i < surface->numSTArrays; i++)
		_pico_free(surface->st[i]);
	free(surface->st);
	for (i = 0; i < surface->numColorArrays; i++)
		_pico_free(surface->color[i]);
	free(surface->color);

	/* free the surface */
	_pico_free(surface);
}

/**
 * @brief adjusts a surface's memory allocations to handle the requested sizes.
 * will always grow, never shrink
 */
int PicoAdjustSurface (picoSurface_t *surface, int numVertexes, int numSTArrays, int numColorArrays, int numIndexes,
		int numFaceNormals)
{
	int i;

	/* dummy check */
	if (surface == NULL)
		return 0;

	/* bare minimums */
	if (numVertexes < 1)
		numVertexes = 1;
	if (numSTArrays < 1)
		numSTArrays = 1;
	if (numColorArrays < 1)
		numColorArrays = 1;
	if (numIndexes < 1)
		numIndexes = 1;

	/* additional vertexes? */
	while (numVertexes > surface->maxVertexes) { /* fix */
		surface->maxVertexes += PICO_GROW_VERTEXES;
		if (!_pico_realloc((void*) &surface->xyz, surface->numVertexes * sizeof(*surface->xyz),
				surface->maxVertexes * sizeof(*surface->xyz)))
			return 0;
		if (!_pico_realloc((void*) &surface->normal, surface->numVertexes * sizeof(*surface->normal),
				surface->maxVertexes * sizeof(*surface->normal)))
			return 0;
		if (!_pico_realloc((void*) &surface->smoothingGroup, surface->numVertexes * sizeof(*surface->smoothingGroup),
				surface->maxVertexes * sizeof(*surface->smoothingGroup)))
			return 0;
		for (i = 0; i < surface->numSTArrays; i++)
			if (!_pico_realloc((void*) &surface->st[i], surface->numVertexes * sizeof(*surface->st[i]),
					surface->maxVertexes * sizeof(*surface->st[i])))
				return 0;
		for (i = 0; i < surface->numColorArrays; i++)
			if (!_pico_realloc((void*) &surface->color[i], surface->numVertexes * sizeof(*surface->color[i]),
					surface->maxVertexes * sizeof(*surface->color[i])))
				return 0;
	}

	/* set vertex count to higher */
	if (numVertexes > surface->numVertexes)
		surface->numVertexes = numVertexes;

	/* additional st arrays? */
	while (numSTArrays > surface->maxSTArrays) { /* fix */
		surface->maxSTArrays += PICO_GROW_ARRAYS;
		if (!_pico_realloc((void*) &surface->st, surface->numSTArrays * sizeof(*surface->st),
				surface->maxSTArrays * sizeof(*surface->st)))
			return 0;
		while (surface->numSTArrays < numSTArrays) {
			surface->st[surface->numSTArrays] = _pico_alloc(surface->maxVertexes * sizeof(*surface->st[0]));
			memset(surface->st[surface->numSTArrays], 0, surface->maxVertexes * sizeof(*surface->st[0]));
			surface->numSTArrays++;
		}
	}

	/* additional color arrays? */
	while (numColorArrays > surface->maxColorArrays) { /* fix */
		surface->maxColorArrays += PICO_GROW_ARRAYS;
		if (!_pico_realloc((void*) &surface->color, surface->numColorArrays * sizeof(*surface->color),
				surface->maxColorArrays * sizeof(*surface->color)))
			return 0;
		while (surface->numColorArrays < numColorArrays) {
			surface->color[surface->numColorArrays] = _pico_alloc(surface->maxVertexes * sizeof(*surface->color[0]));
			memset(surface->color[surface->numColorArrays], 0, surface->maxVertexes * sizeof(*surface->color[0]));
			surface->numColorArrays++;
		}
	}

	/* additional indexes? */
	while (numIndexes > surface->maxIndexes) { /* fix */
		surface->maxIndexes += PICO_GROW_INDEXES;
		if (!_pico_realloc((void*) &surface->index, surface->numIndexes * sizeof(*surface->index),
				surface->maxIndexes * sizeof(*surface->index)))
			return 0;
	}

	/* set index count to higher */
	if (numIndexes > surface->numIndexes)
		surface->numIndexes = numIndexes;

	/* additional face normals? */
	while (numFaceNormals > surface->maxFaceNormals) { /* fix */
		surface->maxFaceNormals += PICO_GROW_FACES;
		if (!_pico_realloc((void*) &surface->faceNormal, surface->numFaceNormals * sizeof(*surface->faceNormal),
				surface->maxFaceNormals * sizeof(*surface->faceNormal)))
			return 0;
	}

	/* set face normal count to higher */
	if (numFaceNormals > surface->numFaceNormals)
		surface->numFaceNormals = numFaceNormals;

	/* return ok */
	return 1;
}

/**
 * @brief Finds first matching named surface in a model.
 */
picoSurface_t *PicoFindSurface (picoModel_t *model, char* name, int caseSensitive)
{
	int i;

	/* sanity check */
	if (model == NULL || name == NULL)
		return NULL;

	/* walk list */
	for (i = 0; i < model->numSurfaces; i++) {
		/* skip null surfaces or surfaces with null names */
		if (model->surface[i] == NULL || model->surface[i]->name == NULL)
			continue;

		/* compare the surface name with name we're looking for */
		if (caseSensitive) {
			if (!strcmp(name, model->surface[i]->name))
				return model->surface[i];
		} else {
			if (!_pico_stricmp(name, model->surface[i]->name))
				return model->surface[i];
		}
	}
	/* named surface not found */
	return NULL;
}

void PicoSetModelName (picoModel_t *model, const char* name)
{
	if (model == NULL || name == NULL)
		return;
	if (model->name != NULL)
		_pico_free(model->name);

	model->name = _pico_clone_alloc(name);
}

void PicoSetModelFileName (picoModel_t *model, const char* fileName)
{
	if (model == NULL || fileName == NULL)
		return;
	if (model->fileName != NULL)
		_pico_free(model->fileName);

	model->fileName = _pico_clone_alloc(fileName);
}

void PicoSetModelFrameNum (picoModel_t *model, int frameNum)
{
	if (model == NULL)
		return;
	model->frameNum = frameNum;
}

void PicoSetModelNumFrames (picoModel_t *model, int numFrames)
{
	if (model == NULL)
		return;
	model->numFrames = numFrames;
}

void PicoSetModelData (picoModel_t *model, void* data)
{
	if (model == NULL)
		return;
	model->data = data;
}

void PicoSetShaderName (picoShader_t *shader, const char* name)
{
	if (shader == NULL || name == NULL)
		return;
	if (shader->name != NULL)
		_pico_free(shader->name);

	shader->name = _pico_clone_alloc(name);
}

void PicoSetShaderMapName (picoShader_t *shader, char* mapName)
{
	if (shader == NULL || mapName == NULL)
		return;
	if (shader->mapName != NULL)
		_pico_free(shader->mapName);

	shader->mapName = _pico_clone_alloc(mapName);
}

void PicoSetShaderAmbientColor (picoShader_t *shader, picoColor_t color)
{
	if (shader == NULL || color == NULL)
		return;
	shader->ambientColor[0] = color[0];
	shader->ambientColor[1] = color[1];
	shader->ambientColor[2] = color[2];
	shader->ambientColor[3] = color[3];
}

void PicoSetShaderDiffuseColor (picoShader_t *shader, picoColor_t color)
{
	if (shader == NULL || color == NULL)
		return;
	shader->diffuseColor[0] = color[0];
	shader->diffuseColor[1] = color[1];
	shader->diffuseColor[2] = color[2];
	shader->diffuseColor[3] = color[3];
}

void PicoSetShaderSpecularColor (picoShader_t *shader, picoColor_t color)
{
	if (shader == NULL || color == NULL)
		return;
	shader->specularColor[0] = color[0];
	shader->specularColor[1] = color[1];
	shader->specularColor[2] = color[2];
	shader->specularColor[3] = color[3];
}

void PicoSetShaderTransparency (picoShader_t *shader, float value)
{
	if (shader == NULL)
		return;
	shader->transparency = value;

	/* cap to 0..1 range */
	if (shader->transparency < 0.0)
		shader->transparency = 0.0;
	if (shader->transparency > 1.0)
		shader->transparency = 1.0;
}

void PicoSetShaderShininess (picoShader_t *shader, float value)
{
	if (shader == NULL)
		return;
	shader->shininess = value;

	/* cap to 0..127 range */
	if (shader->shininess < 0.0)
		shader->shininess = 0.0;
	if (shader->shininess > 127.0)
		shader->shininess = 127.0;
}

void PicoSetSurfaceData (picoSurface_t *surface, void* data)
{
	if (surface == NULL)
		return;
	surface->data = data;
}

void PicoSetSurfaceType (picoSurface_t *surface, picoSurfaceType_t type)
{
	if (surface == NULL)
		return;
	surface->type = type;
}

void PicoSetSurfaceName (picoSurface_t *surface, char* name)
{
	if (surface == NULL || name == NULL)
		return;
	if (surface->name != NULL)
		_pico_free(surface->name);

	surface->name = _pico_clone_alloc(name);
}

void PicoSetSurfaceShader (picoSurface_t *surface, picoShader_t *shader)
{
	if (surface == NULL)
		return;
	surface->shader = shader;
}

void PicoSetSurfaceXYZ (picoSurface_t *surface, int num, picoVec3_t xyz)
{
	if (surface == NULL || num < 0 || xyz == NULL)
		return;
	if (!PicoAdjustSurface(surface, num + 1, 0, 0, 0, 0))
		return;
	_pico_copy_vec(xyz, surface->xyz[num]);
	if (surface->model != NULL)
		_pico_expand_bounds(xyz, surface->model->mins, surface->model->maxs);
}

void PicoSetSurfaceNormal (picoSurface_t *surface, int num, picoVec3_t normal)
{
	if (surface == NULL || num < 0 || normal == NULL)
		return;
	if (!PicoAdjustSurface(surface, num + 1, 0, 0, 0, 0))
		return;
	_pico_copy_vec(normal, surface->normal[num]);
}

void PicoSetSurfaceST (picoSurface_t *surface, int array, int num, picoVec2_t st)
{
	if (surface == NULL || num < 0 || st == NULL)
		return;
	if (!PicoAdjustSurface(surface, num + 1, array + 1, 0, 0, 0))
		return;
	surface->st[array][num][0] = st[0];
	surface->st[array][num][1] = st[1];
}

void PicoSetSurfaceColor (picoSurface_t *surface, int array, int num, const picoColor_t color)
{
	if (surface == NULL || num < 0 || color == NULL)
		return;
	if (!PicoAdjustSurface(surface, num + 1, 0, array + 1, 0, 0))
		return;
	surface->color[array][num][0] = color[0];
	surface->color[array][num][1] = color[1];
	surface->color[array][num][2] = color[2];
	surface->color[array][num][3] = color[3];
}

void PicoSetSurfaceIndex (picoSurface_t *surface, int num, picoIndex_t index)
{
	if (surface == NULL || num < 0)
		return;
	if (!PicoAdjustSurface(surface, 0, 0, 0, num + 1, 0))
		return;
	surface->index[num] = index;
}

void PicoSetSurfaceIndexes (picoSurface_t *surface, int num, picoIndex_t *index, int count)
{
	if (num < 0 || index == NULL || count < 1)
		return;
	if (!PicoAdjustSurface(surface, 0, 0, 0, num + count, 0))
		return;
	memcpy(&surface->index[num], index, count * sizeof(surface->index[num]));
}

void PicoSetFaceNormal (picoSurface_t *surface, int num, picoVec3_t normal)
{
	if (surface == NULL || num < 0 || normal == NULL)
		return;
	if (!PicoAdjustSurface(surface, 0, 0, 0, 0, num + 1))
		return;
	_pico_copy_vec(normal, surface->faceNormal[num]);
}

void PicoSetSurfaceSmoothingGroup (picoSurface_t *surface, int num, picoIndex_t smoothingGroup)
{
	if (num < 0)
		return;
	if (!PicoAdjustSurface(surface, num + 1, 0, 0, 0, 0))
		return;
	surface->smoothingGroup[num] = smoothingGroup;
}

void PicoSetSurfaceSpecial (picoSurface_t *surface, int num, int special)
{
	if (surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL)
		return;
	surface->special[num] = special;
}

const char* PicoGetModelName (picoModel_t *model)
{
	if (model == NULL)
		return NULL;
	if (model->name == NULL)
		return "";
	return model->name;
}

const char* PicoGetModelFileName (picoModel_t *model)
{
	if (model == NULL)
		return NULL;
	if (model->fileName == NULL)
		return "";
	return model->fileName;
}

int PicoGetModelFrameNum (picoModel_t *model)
{
	if (model == NULL)
		return 0;
	return model->frameNum;
}

int PicoGetModelNumFrames (picoModel_t *model)
{
	if (model == NULL)
		return 0;
	return model->numFrames;
}

void* PicoGetModelData (picoModel_t *model)
{
	if (model == NULL)
		return NULL;
	return model->data;
}

int PicoGetModelNumShaders (picoModel_t *model)
{
	if (model == NULL)
		return 0;
	return model->numShaders;
}

picoShader_t *PicoGetModelShader (picoModel_t *model, int num)
{
	/* a few sanity checks */
	if (model == NULL)
		return NULL;
	if (model->shader == NULL)
		return NULL;
	if (num < 0 || num >= model->numShaders)
		return NULL;

	/* return the shader */
	return model->shader[num];
}

int PicoGetModelNumSurfaces (picoModel_t *model)
{
	if (model == NULL)
		return 0;
	return model->numSurfaces;
}

picoSurface_t *PicoGetModelSurface (picoModel_t *model, int num)
{
	/* a few sanity checks */
	if (model == NULL)
		return NULL;
	if (model->surface == NULL)
		return NULL;
	if (num < 0 || num >= model->numSurfaces)
		return NULL;

	/* return the surface */
	return model->surface[num];
}

int PicoGetModelTotalVertexes (picoModel_t *model)
{
	int i, count;

	if (model == NULL)
		return 0;
	if (model->surface == NULL)
		return 0;

	count = 0;
	for (i = 0; i < model->numSurfaces; i++)
		count += PicoGetSurfaceNumVertexes(model->surface[i]);

	return count;
}

int PicoGetModelTotalIndexes (picoModel_t *model)
{
	int i, count;

	if (model == NULL)
		return 0;
	if (model->surface == NULL)
		return 0;

	count = 0;
	for (i = 0; i < model->numSurfaces; i++)
		count += PicoGetSurfaceNumIndexes(model->surface[i]);

	return count;
}

const char* PicoGetShaderName (picoShader_t *shader)
{
	if (shader == NULL)
		return NULL;
	if (shader->name == NULL)
		return "";
	return shader->name;
}

const char* PicoGetShaderMapName (picoShader_t *shader)
{
	if (shader == NULL)
		return NULL;
	if (shader->mapName == NULL)
		return "";
	return shader->mapName;
}

picoByte_t *PicoGetShaderAmbientColor (picoShader_t *shader)
{
	if (shader == NULL)
		return NULL;
	return shader->ambientColor;
}

picoByte_t *PicoGetShaderDiffuseColor (picoShader_t *shader)
{
	if (shader == NULL)
		return NULL;
	return shader->diffuseColor;
}

picoByte_t *PicoGetShaderSpecularColor (picoShader_t *shader)
{
	if (shader == NULL)
		return NULL;
	return shader->specularColor;
}

float PicoGetShaderTransparency (picoShader_t *shader)
{
	if (shader == NULL)
		return 0.0f;
	return shader->transparency;
}

float PicoGetShaderShininess (picoShader_t *shader)
{
	if (shader == NULL)
		return 0.0f;
	return shader->shininess;
}

void* PicoGetSurfaceData (picoSurface_t *surface)
{
	if (surface == NULL)
		return NULL;
	return surface->data;
}

picoSurfaceType_t PicoGetSurfaceType (picoSurface_t *surface)
{
	if (surface == NULL)
		return PICO_BAD;
	return surface->type;
}

const char* PicoGetSurfaceName (picoSurface_t *surface)
{
	if (surface == NULL)
		return NULL;
	if (surface->name == NULL)
		return "";
	return surface->name;
}

picoShader_t *PicoGetSurfaceShader (picoSurface_t *surface)
{
	if (surface == NULL)
		return NULL;
	return surface->shader;
}

int PicoGetSurfaceNumVertexes (picoSurface_t *surface)
{
	if (surface == NULL)
		return 0;
	return surface->numVertexes;
}

picoVec_t *PicoGetSurfaceXYZ (picoSurface_t *surface, int num)
{
	if (surface == NULL || num < 0 || num > surface->numVertexes)
		return NULL;
	return surface->xyz[num];
}

picoVec_t *PicoGetSurfaceNormal (picoSurface_t *surface, int num)
{
	if (surface == NULL || num < 0 || num > surface->numVertexes)
		return NULL;
	return surface->normal[num];
}

picoVec_t *PicoGetSurfaceST (picoSurface_t *surface, int array, int num)
{
	if (surface == NULL || array < 0 || array > surface->numSTArrays || num < 0 || num > surface->numVertexes)
		return NULL;
	return surface->st[array][num];
}

picoByte_t *PicoGetSurfaceColor (picoSurface_t *surface, int array, int num)
{
	if (surface == NULL || array < 0 || array > surface->numColorArrays || num < 0 || num > surface->numVertexes)
		return NULL;
	return surface->color[array][num];
}

int PicoGetSurfaceNumIndexes (picoSurface_t *surface)
{
	if (surface == NULL)
		return 0;
	return surface->numIndexes;
}

picoIndex_t PicoGetSurfaceIndex (picoSurface_t *surface, int num)
{
	if (surface == NULL || num < 0 || num > surface->numIndexes)
		return 0;
	return surface->index[num];
}

picoIndex_t *PicoGetSurfaceIndexes (picoSurface_t *surface, int num)
{
	if (surface == NULL || num < 0 || num > surface->numIndexes)
		return NULL;
	return &surface->index[num];
}

picoVec_t *PicoGetFaceNormal (picoSurface_t *surface, int num)
{
	if (surface == NULL || num < 0 || num > surface->numFaceNormals)
		return NULL;
	return surface->faceNormal[num];
}

int PicoGetSurfaceSpecial (picoSurface_t *surface, int num)
{
	if (surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL)
		return 0;
	return surface->special[num];
}

/* ----------------------------------------------------------------------------
 hashtable related functions
 ---------------------------------------------------------------------------- */

/* hashtable code for faster vertex lookups */
/* power of 2, use & */
/*#define HASHTABLE_SIZE 32768 */
/* prime, use % */
#define HASHTABLE_SIZE 7919
#define HASH_XYZ_EPSILON					0.01f
#define HASH_XYZ_EPSILONSPACE_MULTIPLIER	1.f / HASH_XYZ_EPSILON
#define HASH_ST_EPSILON						0.0001f
#define HASH_NORMAL_EPSILON					0.02f

unsigned int PicoVertexCoordGenerateHash (picoVec3_t xyz)
{
	unsigned int hash = 0;

	picoVec3_t xyz_epsilonspace;

	_pico_scale_vec(xyz, HASH_XYZ_EPSILONSPACE_MULTIPLIER, xyz_epsilonspace);
	xyz_epsilonspace[0] = (float) floor(xyz_epsilonspace[0]);
	xyz_epsilonspace[1] = (float) floor(xyz_epsilonspace[1]);
	xyz_epsilonspace[2] = (float) floor(xyz_epsilonspace[2]);

	hash += ~(*((unsigned int*) &xyz_epsilonspace[0]) << 15);
	hash ^= (*((unsigned int*) &xyz_epsilonspace[0]) >> 10);
	hash += (*((unsigned int*) &xyz_epsilonspace[1]) << 3);
	hash ^= (*((unsigned int*) &xyz_epsilonspace[1]) >> 6);
	hash += ~(*((unsigned int*) &xyz_epsilonspace[2]) << 11);
	hash ^= (*((unsigned int*) &xyz_epsilonspace[2]) >> 16);

	/* hash = hash & (HASHTABLE_SIZE-1); */
	hash = hash % (HASHTABLE_SIZE);
	return hash;
}

picoVertexCombinationHash_t** PicoNewVertexCombinationHashTable (void)
{
	picoVertexCombinationHash_t** hashTable = _pico_alloc(HASHTABLE_SIZE * sizeof(picoVertexCombinationHash_t*));

	memset(hashTable, 0, HASHTABLE_SIZE * sizeof(picoVertexCombinationHash_t*));

	return hashTable;
}

void PicoFreeVertexCombinationHashTable (picoVertexCombinationHash_t** hashTable)
{
	int i;
	picoVertexCombinationHash_t *vertexCombinationHash;
	picoVertexCombinationHash_t *nextVertexCombinationHash;

	/* dummy check */
	if (hashTable == NULL)
		return;

	for (i = 0; i < HASHTABLE_SIZE; i++) {
		if (hashTable[i]) {
			nextVertexCombinationHash = NULL;

			for (vertexCombinationHash = hashTable[i]; vertexCombinationHash; vertexCombinationHash =
					nextVertexCombinationHash) {
				nextVertexCombinationHash = vertexCombinationHash->next;
				if (vertexCombinationHash->data != NULL) {
					_pico_free(vertexCombinationHash->data);
				}
				_pico_free(vertexCombinationHash);
			}
		}
	}

	_pico_free(hashTable);
}

picoVertexCombinationHash_t *PicoFindVertexCombinationInHashTable (picoVertexCombinationHash_t** hashTable,
		picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color)
{
	unsigned int hash;
	picoVertexCombinationHash_t *vertexCombinationHash;

	/* dumy check */
	if (hashTable == NULL || xyz == NULL || normal == NULL || st == NULL || color == NULL)
		return NULL;

	hash = PicoVertexCoordGenerateHash(xyz);

	for (vertexCombinationHash = hashTable[hash]; vertexCombinationHash;
			vertexCombinationHash = vertexCombinationHash->next) {
		/* check xyz */
		if ((fabs(xyz[0] - vertexCombinationHash->vcd.xyz[0])) > HASH_XYZ_EPSILON
				|| (fabs(xyz[1] - vertexCombinationHash->vcd.xyz[1])) > HASH_XYZ_EPSILON
				|| (fabs(xyz[2] - vertexCombinationHash->vcd.xyz[2])) > HASH_XYZ_EPSILON)
			continue;

		/* check normal */
		if ((fabs(normal[0] - vertexCombinationHash->vcd.normal[0])) > HASH_NORMAL_EPSILON
				|| (fabs(normal[1] - vertexCombinationHash->vcd.normal[1])) > HASH_NORMAL_EPSILON
				|| (fabs(normal[2] - vertexCombinationHash->vcd.normal[2])) > HASH_NORMAL_EPSILON)
			continue;

		/* check st */
		if ((fabs(st[0] - vertexCombinationHash->vcd.st[0])) > HASH_ST_EPSILON
				|| (fabs(st[1] - vertexCombinationHash->vcd.st[1])) > HASH_ST_EPSILON)
			continue;

		/* check color */
		if (*((int*) vertexCombinationHash->vcd.color) != *((int*) color))
			continue;

		/* gotcha */
		return vertexCombinationHash;
	}

	return NULL;
}

picoVertexCombinationHash_t *PicoAddVertexCombinationToHashTable (picoVertexCombinationHash_t** hashTable,
		picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color, picoIndex_t index)
{
	unsigned int hash;
	picoVertexCombinationHash_t *vertexCombinationHash;

	/* dumy check */
	if (hashTable == NULL || xyz == NULL || normal == NULL || st == NULL || color == NULL)
		return NULL;

	vertexCombinationHash = _pico_alloc(sizeof(picoVertexCombinationHash_t));

	if (!vertexCombinationHash)
		return NULL;

	hash = PicoVertexCoordGenerateHash(xyz);

	_pico_copy_vec(xyz, vertexCombinationHash->vcd.xyz);
	_pico_copy_vec(normal, vertexCombinationHash->vcd.normal);
	_pico_copy_vec2(st, vertexCombinationHash->vcd.st);
	_pico_copy_color(color, vertexCombinationHash->vcd.color);
	vertexCombinationHash->index = index;
	vertexCombinationHash->data = NULL;
	vertexCombinationHash->next = hashTable[hash];
	hashTable[hash] = vertexCombinationHash;

	return vertexCombinationHash;
}

/**
 * @brief finds a vertex matching the set parameters
 * @todo needs non-naive algorithm
 */
int PicoFindSurfaceVertexNum (picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st,
		int numColors, picoColor_t *color, picoIndex_t smoothingGroup)
{
	int i, j;

	/* dummy check */
	if (surface == NULL || surface->numVertexes <= 0)
		return -1;

	/* walk vertex list */
	for (i = 0; i < surface->numVertexes; i++) {
		/* check xyz */
		if (xyz != NULL
				&& (surface->xyz[i][0] != xyz[0] || surface->xyz[i][1] != xyz[1] || surface->xyz[i][2] != xyz[2]))
			continue;

		/* check normal */
		if (normal != NULL
				&& (surface->normal[i][0] != normal[0] || surface->normal[i][1] != normal[1]
						|| surface->normal[i][2] != normal[2]))
			continue;

		/* check normal */
		if (surface->smoothingGroup[i] != smoothingGroup)
			continue;

		/* check st */
		if (numSTs > 0 && st != NULL) {
			for (j = 0; j < numSTs; j++) {
				if (surface->st[j][i][0] != st[j][0] || surface->st[j][i][1] != st[j][1])
					break;
			}
			if (j != numSTs)
				continue;
		}

		/* check color */
		if (numColors > 0 && color != NULL) {
			for (j = 0; j < numSTs; j++) {
				if (*((int*) surface->color[j]) != *((int*) color[j]))
					break;
			}
			if (j != numColors)
				continue;
		}

		/* vertex matches */
		return i;
	}

	/* nada */
	return -1;
}

typedef struct _IndexArray {
	picoIndex_t *data;
	picoIndex_t *last;
} IndexArray;

static void indexarray_push_back (IndexArray* self, picoIndex_t value)
{
	*self->last++ = value;
}

static void indexarray_reserve (IndexArray* self, size_t size)
{
	self->data = self->last = _pico_calloc(size, sizeof(picoIndex_t));
}

static void indexarray_clear (IndexArray* self)
{
	_pico_free(self->data);
}

typedef struct _BinaryTreeNode {
	picoIndex_t left;
	picoIndex_t right;
} BinaryTreeNode;

typedef struct _BinaryTree {
	BinaryTreeNode* data;
	BinaryTreeNode* last;
} BinaryTree;

static void binarytree_extend (BinaryTree* self)
{
	self->last->left = 0;
	self->last->right = 0;
	++self->last;
}

static size_t binarytree_size (BinaryTree* self)
{
	return self->last - self->data;
}

static void binarytree_reserve (BinaryTree* self, size_t size)
{
	self->data = self->last = _pico_calloc(size, sizeof(BinaryTreeNode));
}

static void binarytree_clear (BinaryTree* self)
{
	_pico_free(self->data);
}

typedef int (*LessFunc) (void*, picoIndex_t, picoIndex_t);

typedef struct _UniqueIndices {
	BinaryTree tree;
	IndexArray indices;
	LessFunc lessFunc;
	void* lessData;
} UniqueIndices;

static size_t UniqueIndices_size (UniqueIndices* self)
{
	return binarytree_size(&self->tree);
}

static void UniqueIndices_reserve (UniqueIndices* self, size_t size)
{
	binarytree_reserve(&self->tree, size);
	indexarray_reserve(&self->indices, size);
}

static void UniqueIndices_init (UniqueIndices* self, LessFunc lessFunc, void* lessData)
{
	self->lessFunc = lessFunc;
	self->lessData = lessData;
}

static void UniqueIndices_destroy (UniqueIndices* self)
{
	binarytree_clear(&self->tree);
	indexarray_clear(&self->indices);
}

static picoIndex_t UniqueIndices_find_or_insert (UniqueIndices* self, picoIndex_t value)
{
	picoIndex_t index = 0;

	for (;;) {
		if (self->lessFunc(self->lessData, value, self->indices.data[index])) {
			BinaryTreeNode* node = self->tree.data + index;
			if (node->left != 0) {
				index = node->left;
				continue;
			} else {
				node->left = (picoIndex_t) binarytree_size(&self->tree);
				binarytree_extend(&self->tree);
				indexarray_push_back(&self->indices, value);
				return node->left;
			}
		}
		if (self->lessFunc(self->lessData, self->indices.data[index], value)) {
			BinaryTreeNode* node = self->tree.data + index;
			if (node->right != 0) {
				index = node->right;
				continue;
			} else {
				node->right = (picoIndex_t) binarytree_size(&self->tree);
				binarytree_extend(&self->tree);
				indexarray_push_back(&self->indices, value);
				return node->right;
			}
		}

		return index;
	}
}

static picoIndex_t UniqueIndices_insert (UniqueIndices* self, picoIndex_t value)
{
	if (self->tree.data == self->tree.last) {
		binarytree_extend(&self->tree);
		indexarray_push_back(&self->indices, value);
		return 0;
	} else {
		return UniqueIndices_find_or_insert(self, value);
	}
}

typedef struct picoSmoothVertices_s {
	picoVec3_t *xyz;
	picoIndex_t *smoothingGroups;
} picoSmoothVertices_t;

static int lessSmoothVertex (void* data, picoIndex_t first, picoIndex_t second)
{
	picoSmoothVertices_t *smoothVertices = data;

	if (smoothVertices->xyz[first][0] != smoothVertices->xyz[second][0])
		return smoothVertices->xyz[first][0] < smoothVertices->xyz[second][0];
	if (smoothVertices->xyz[first][1] != smoothVertices->xyz[second][1])
		return smoothVertices->xyz[first][1] < smoothVertices->xyz[second][1];
	if (smoothVertices->xyz[first][2] != smoothVertices->xyz[second][2])
		return smoothVertices->xyz[first][2] < smoothVertices->xyz[second][2];
	if (smoothVertices->smoothingGroups[first] != smoothVertices->smoothingGroups[second])
		return smoothVertices->smoothingGroups[first] < smoothVertices->smoothingGroups[second];
	return 0;
}

static void _pico_vertices_combine_shared_normals (picoVec3_t *xyz, picoIndex_t *smoothingGroups, picoVec3_t *normals,
		picoIndex_t numVertices)
{
	UniqueIndices vertices;
	IndexArray indices;
	picoSmoothVertices_t smoothVertices = { xyz, smoothingGroups };
	UniqueIndices_init(&vertices, lessSmoothVertex, &smoothVertices);
	UniqueIndices_reserve(&vertices, numVertices);
	indexarray_reserve(&indices, numVertices);

	{
		picoIndex_t i = 0;
		for (; i < numVertices; ++i) {
			size_t size = UniqueIndices_size(&vertices);
			picoIndex_t index = UniqueIndices_insert(&vertices, i);
			if ((size_t) index != size) {
				float* normal = normals[vertices.indices.data[index]];
				_pico_add_vec(normal, normals[i], normal);
			}
			indexarray_push_back(&indices, index);
		}
	}

	{
		picoIndex_t maxIndex = 0;
		picoIndex_t *i = indices.data;
		for (; i != indices.last; ++i) {
			if (*i <= maxIndex) {
				_pico_copy_vec(normals[vertices.indices.data[*i]], normals[i - indices.data]);
			} else {
				maxIndex = *i;
			}
		}
	}

	UniqueIndices_destroy(&vertices);
	indexarray_clear(&indices);
}

typedef picoVec3_t *picoNormalIter_t;
typedef picoIndex_t *picoIndexIter_t;

static void _pico_triangles_generate_weighted_normals (picoIndexIter_t first, picoIndexIter_t end, picoVec3_t *xyz,
		picoVec3_t *normals)
{
	for (; first != end; first += 3) {
		picoVec3_t weightedNormal;
		{
			float* a = xyz[*(first + 0)];
			float* b = xyz[*(first + 1)];
			float* c = xyz[*(first + 2)];
			picoVec3_t ba, ca;
			_pico_subtract_vec(b, a, ba);
			_pico_subtract_vec(c, a, ca);
			_pico_cross_vec(ca, ba, weightedNormal);
		}
		{
			int j = 0;
			for (; j < 3; ++j) {
				float* normal = normals[*(first + j)];
				_pico_add_vec(weightedNormal, normal, normal);
			}
		}
	}
}

static void _pico_normals_zero (picoNormalIter_t first, picoNormalIter_t last)
{
	for (; first != last; ++first) {
		_pico_zero_vec(*first);
	}
}

static void _pico_normals_normalize (picoNormalIter_t first, picoNormalIter_t last)
{
	for (; first != last; ++first) {
		_pico_normalize_vec(*first);
	}
}

static double _pico_length_vec (picoVec3_t vec)
{
	return sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
}

#define NORMAL_UNIT_LENGTH_EPSILON 0.01
#define FLOAT_EQUAL_EPSILON(f, other, epsilon) (fabs(f - other) < epsilon)

static int _pico_normal_is_unit_length (picoVec3_t normal)
{
	return FLOAT_EQUAL_EPSILON(_pico_length_vec(normal), 1.0, NORMAL_UNIT_LENGTH_EPSILON);
}

static int _pico_normal_within_tolerance (picoVec3_t normal, picoVec3_t other)
{
	return _pico_dot_vec(normal, other) > 0.0f;
}

static void _pico_normals_assign_generated_normals (picoNormalIter_t first, picoNormalIter_t last,
		picoNormalIter_t generated)
{
	for (; first != last; ++first, ++generated) {
		if (!_pico_normal_is_unit_length(*first) || !_pico_normal_within_tolerance(*first, *generated)) {
			_pico_copy_vec(*generated, *first);
		}
	}
}

void PicoFixSurfaceNormals (picoSurface_t *surface)
{
	picoVec3_t *normals = (picoVec3_t*) _pico_calloc(surface->numVertexes, sizeof(picoVec3_t));

	_pico_normals_zero(normals, normals + surface->numVertexes);

	_pico_triangles_generate_weighted_normals(surface->index, surface->index + surface->numIndexes, surface->xyz,
			normals);
	_pico_vertices_combine_shared_normals(surface->xyz, surface->smoothingGroup, normals, surface->numVertexes);

	_pico_normals_normalize(normals, normals + surface->numVertexes);

	_pico_normals_assign_generated_normals(surface->normal, surface->normal + surface->numVertexes, normals);

	_pico_free(normals);
}

/**
 * @brief A nice way to add individual triangles to the model.
 * Chooses an appropriate surface based on the shader, or adds a new surface if necessary
 */
void PicoAddTriangleToModel (picoModel_t *model, picoVec3_t** xyz, picoVec3_t** normals, int numSTs, picoVec2_t** st,
		int numColors, picoColor_t** colors, picoShader_t *shader, picoIndex_t *smoothingGroup)
{
	int i, j;
	picoSurface_t *workSurface = NULL;

	/* see if a surface already has the shader */
	for (i = 0; i < model->numSurfaces; i++) {
		workSurface = model->surface[i];
		if (workSurface->shader == shader) {
			break;
		}
	}

	/* no surface uses this shader yet, so create a new surface */
	if (!workSurface || i >= model->numSurfaces) {
		/* create a new surface in the model for the unique shader */
		workSurface = PicoNewSurface(model);
		if (!workSurface) {
			_pico_printf(PICO_ERROR, "Could not allocate a new surface!\n");
			return;
		}

		/* do surface setup */
		PicoSetSurfaceType(workSurface, PICO_TRIANGLES);
		PicoSetSurfaceName(workSurface, shader->name);
		PicoSetSurfaceShader(workSurface, shader);
	}

	/* add the triangle data to the surface */
	for (i = 0; i < 3; i++) {
		/* get the next free spot in the index array */
		int newVertIndex = PicoGetSurfaceNumIndexes(workSurface);

		/* get the index of the vertex that we're going to store at newVertIndex */
		int vertDataIndex = PicoFindSurfaceVertexNum(workSurface, *xyz[i], *normals[i], numSTs, st[i], numColors, colors[i],
				smoothingGroup[i]);

		/* the vertex wasn't found, so create a new vertex in the pool from the data we have */
		if (vertDataIndex == -1) {
			/* find the next spot for a new vertex */
			vertDataIndex = PicoGetSurfaceNumVertexes(workSurface);

			/* assign the data to it */
			PicoSetSurfaceXYZ(workSurface, vertDataIndex, *xyz[i]);
			PicoSetSurfaceNormal(workSurface, vertDataIndex, *normals[i]);

			/* make sure to copy over all available ST's and colors for the vertex */
			for (j = 0; j < numColors; j++) {
				PicoSetSurfaceColor(workSurface, j, vertDataIndex, colors[i][j]);
			}
			for (j = 0; j < numSTs; j++) {
				PicoSetSurfaceST(workSurface, j, vertDataIndex, st[i][j]);
			}

			PicoSetSurfaceSmoothingGroup(workSurface, vertDataIndex, smoothingGroup[i]);
		}

		/* add this vertex to the triangle */
		PicoSetSurfaceIndex(workSurface, newVertIndex, vertDataIndex);
	}
}
