// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "std3d.h"

#include "nel/3d/lod_character_shape.h"
#include "nel/misc/vectord.h"
#include "nel/misc/fast_floor.h"
#include "nel/3d/lod_character_texture.h"
#include "nel/misc/triangle.h"
#include "nel/misc/polygon.h"
#include "nel/misc/hierarchical_timer.h"


using namespace std;
using namespace NLMISC;


namespace NL3D
{


// ***************************************************************************
// ***************************************************************************
// CLodCharacterShape
// ***************************************************************************
// ***************************************************************************


// ***************************************************************************
const CLodCharacterShapeBuild::CPixelInfo		CLodCharacterShapeBuild::CPixelInfo::EmptyPixel(
		CVector::Null, CVector(1000,0,0));


// ***************************************************************************
CLodCharacterShapeBuild::CLodCharacterShapeBuild()
{
	_Width= 0;
	_Height= 0;
}


// ***************************************************************************
void	CLodCharacterShapeBuild::compile(const std::vector<bool> &triangleSelection, uint textureOverSample)
{
	nlassert(UVs.size()==Vertices.size());
	nlassert(Normals.size()==Vertices.size());

	// take the sqrtf.
	textureOverSample= (uint)sqrtf((float)textureOverSample);
	textureOverSample= max(textureOverSample, 1U);

	_Width= NL3D_CLOD_TEXT_WIDTH;
	_Height= NL3D_CLOD_TEXT_HEIGHT;

	uint	wOver= _Width*textureOverSample;
	uint	hOver= _Height*textureOverSample;
	std::vector<CPixelInfo>		overTextureInfo;

	// do some tri selection?
	bool	triSelectOk= triangleSelection.size()==TriangleIndices.size()/3;


	// **** reset the texture and init with "empty" pixel (ie normal.x==1000, which is not possible because normal.norm()==1)
	_TextureInfo.resize(_Width*_Height);
	fill(_TextureInfo.begin(), _TextureInfo.end(), CPixelInfo::EmptyPixel);
	// do it to the oversampled texture
	overTextureInfo.resize(wOver*hOver, CPixelInfo::EmptyPixel);


	// **** For each triangle in the shape, polyfill this triangle from a texture view, in the overSampledTexture
	CPolygon2D	poly;
	poly.Vertices.resize(3);
	for(uint i=0; i<TriangleIndices.size();i+=3)
	{
		// if selection OK, and the tri is not selected, skip
		if(triSelectOk && !triangleSelection[i/3])
			continue;

		// Setup triangle.
		// -----------
		uint	idx[3];
		idx[0]= TriangleIndices[i+0];
		idx[1]= TriangleIndices[i+1];
		idx[2]= TriangleIndices[i+2];
		// compute corners UVs, in texel size
		CTriangle		triangleUV;
		triangleUV.V0= CVector(UVs[idx[0]].U*wOver, UVs[idx[0]].V*hOver, 0);
		triangleUV.V1= CVector(UVs[idx[1]].U*wOver, UVs[idx[1]].V*hOver, 0);
		triangleUV.V2= CVector(UVs[idx[2]].U*wOver, UVs[idx[2]].V*hOver, 0);
		// compute corners pixelInfos
		CPixelInfo		pInf[3];
		for(uint corner=0; corner<3; corner++)
		{
			pInf[corner].Pos= Vertices[idx[corner]];
			pInf[corner].Normal= Normals[idx[corner]];
		}
		// Compute Gradients
		CVector		GradPosX, GradPosY, GradPosZ;
		CVector		GradNormalX, GradNormalY, GradNormalZ;
		triangleUV.computeGradient(pInf[0].Pos.x, pInf[1].Pos.x, pInf[2].Pos.x, GradPosX);
		triangleUV.computeGradient(pInf[0].Pos.y, pInf[1].Pos.y, pInf[2].Pos.y, GradPosY);
		triangleUV.computeGradient(pInf[0].Pos.z, pInf[1].Pos.z, pInf[2].Pos.z, GradPosZ);
		triangleUV.computeGradient(pInf[0].Normal.x, pInf[1].Normal.x, pInf[2].Normal.x, GradNormalX);
		triangleUV.computeGradient(pInf[0].Normal.y, pInf[1].Normal.y, pInf[2].Normal.y, GradNormalY);
		triangleUV.computeGradient(pInf[0].Normal.z, pInf[1].Normal.z, pInf[2].Normal.z, GradNormalZ);
		// Compute Gradients offset
		float		OffPosX, OffPosY, OffPosZ;
		float		OffNormalX, OffNormalY, OffNormalZ;
		OffPosX= pInf[0].Pos.x - GradPosX*triangleUV.V0;
		OffPosY= pInf[0].Pos.y - GradPosY*triangleUV.V0;
		OffPosZ= pInf[0].Pos.z - GradPosZ*triangleUV.V0;
		OffNormalX= pInf[0].Normal.x - GradNormalX*triangleUV.V0;
		OffNormalY= pInf[0].Normal.y - GradNormalY*triangleUV.V0;
		OffNormalZ= pInf[0].Normal.z - GradNormalZ*triangleUV.V0;


		// PolyFiller
		// -----------
		CVector2f	dCenter(0.5f, 0.5f);
		// select texels if their centers is in the triangle
		poly.Vertices[0]= triangleUV.V0-dCenter;
		poly.Vertices[1]= triangleUV.V1-dCenter;
		poly.Vertices[2]= triangleUV.V2-dCenter;
		// polyFiller
		CPolygon2D::TRasterVect	rasters;
		sint	minY;
		poly.computeBorders(rasters, minY);
		for(sint y=0;y<(sint)rasters.size();y++)
		{
			sint x0= rasters[y].first;
			sint x1= rasters[y].second;
			CVector	v;
			// get the center coord of this texel
			v.y= y+minY+0.5f;
			v.z= 0;
			for(sint x=x0;x<=x1;x++)
			{
				// get the center coord of this texel
				v.x= x+0.5f;
				// Compute Pos/Normal on this texel.
				CPixelInfo	texelInf;
				texelInf.Pos.x= OffPosX + GradPosX * v;
				texelInf.Pos.y= OffPosY + GradPosY * v;
				texelInf.Pos.z= OffPosZ + GradPosZ * v;
				texelInf.Normal.x= OffNormalX + GradNormalX * v;
				texelInf.Normal.y= OffNormalY + GradNormalY * v;
				texelInf.Normal.z= OffNormalZ + GradNormalZ * v;
				// normalize.
				texelInf.Normal.normalize();
				// Store (NB: overwrite any pixel)
				sint	texX= ((uint)x)%wOver;
				sint	texY= ((uint)y+minY)%hOver;
				overTextureInfo[texY*wOver+texX]= texelInf;
			}
		}

	}


	// **** Down sample the overSampled texture.
	sint	x,y;
	if(textureOverSample==1)
	{
		_TextureInfo= overTextureInfo;
	}
	else
	{
		// eg: 3 gives 1. delta ranges from -1 to 1.
		float	middle= (textureOverSample-1)/2.f;

		// for all pixels.
		for(y=0;y<(sint)_Height;y++)
		{
			for(x=0;x<(sint)_Width;x++)
			{
				// for all samples, take the best one.
				sint		xo, yo;
				float		bestDist= FLT_MAX;
				CPixelInfo	bestPixel= CPixelInfo::EmptyPixel;
				for(yo=0;yo<(sint)textureOverSample;yo++)
				{
					for(xo=0;xo<(sint)textureOverSample;xo++)
					{
						// compute distance to the pixel center.
						float	dist= sqr(yo-middle)+sqr(xo-middle);
						const CPixelInfo	&overPixel= overTextureInfo[(y*textureOverSample+yo)*wOver+(x*textureOverSample+xo)];
						// if the overPixel is not empty.
						if( !(overPixel==CPixelInfo::EmptyPixel) )
						{
							// take it if the best: nearest to the pixel center.
							if(dist<bestDist)
							{
								bestDist= dist;
								bestPixel= overPixel;
							}
						}
					}
				}

				// fill textureInfo
				_TextureInfo[y*_Width+x]= bestPixel;
			}
		}
	}


	// **** Dilate texture 1 time: each empty pixel info get neighbor full pixel (8box)
	// copy the non dilated texture
	std::vector<CPixelInfo>		tmpTextureInfo= _TextureInfo;
	// process all pixels
	for(y=0;y<(sint)_Height;y++)
	{
		sint	y0=y-1, y1=y+1;
		y0= max(y0, 0);
		y1= min(y1, (sint)_Height-1);
		for(x=0;x<(sint)_Width;x++)
		{
			CPixelInfo	&texelInf= _TextureInfo[y*_Width+x];
			// if an empty pixel.
			if(texelInf==CPixelInfo::EmptyPixel)
			{
				// dilate: look around for non empty pixel.
				sint	x0=x-1, x1=x+1;
				x0= max(x0, 0);
				x1= min(x1, (sint)_Width-1);
				// For the 8 possible pixels (nb: look us too, but doesn't matter since we are an empty pixel)
				for(sint yb= y0; yb<=y1;yb++)
				{
					for(sint xb= x0; xb<=x1;xb++)
					{
						// if the neighbor is not an empty pixel. NB: avoid override problems using not Dilated texture
						CPixelInfo	&nbTexelInf= tmpTextureInfo[yb*_Width+xb];
						if( !(nbTexelInf==CPixelInfo::EmptyPixel) )
						{
							// write it in the center pixel, and skip the search
							texelInf= nbTexelInf;
							yb= y1+1;
							break;
						}
					}
				}
			}
		}
	}

}


// ***************************************************************************
void	CLodCharacterShapeBuild::serial(NLMISC::IStream &f)
{
	// NEL_CLODBULD
	f.serialCheck((uint32)'_LEN');
	f.serialCheck((uint32)'DOLC');
	f.serialCheck((uint32)'DLUB');

	/*
	Version 1:
		- UVs and Normals + texture info
	*/
	sint	ver= f.serialVersion(1);

	f.serialCont(Vertices);
	f.serialCont(SkinWeights);
	f.serialCont(BonesNames);
	#ifdef NL_LOD_CHARACTER_INDEX16
		// must serial 16 bits index as 32 bits
		if (f.isReading())
		{
			std::vector<uint32> readVect;
			f.serialCont(readVect);
			TriangleIndices.resize(readVect.size());
			for(uint k = 0; k < readVect.size(); ++k)
			{
				nlassert(readVect[k] <= 0xffff);
				TriangleIndices[k] = (uint16) readVect[k];
			}
		}
		else
		{
			std::vector<uint32> saveVect(TriangleIndices.size());
			std::copy(TriangleIndices.begin(), TriangleIndices.end(), saveVect.begin()); //  copy will do the job
			f.serialCont(saveVect);
		}
	#else
		f.serialCont(TriangleIndices);
	#endif
	if(ver>=1)
	{
		f.serialCont(UVs);
		f.serialCont(Normals);
		f.serial(_Width, _Height);
		f.serialCont(_TextureInfo);
	}
	else
	{
		// Must init dummy UVs/normals
		UVs.resize(Vertices.size(), CUV(0,0));
		Normals.resize(Vertices.size(), CVector::K);
		// Must init dummy texture
		_Width= NL3D_CLOD_TEXT_WIDTH;
		_Height= NL3D_CLOD_TEXT_HEIGHT;
		_TextureInfo.resize(_Width*_Height, CPixelInfo::EmptyPixel);
	}

}


// ***************************************************************************
const CLodCharacterShapeBuild::CPixelInfo	*CLodCharacterShapeBuild::getTextureInfoPtr()
{
	if(_TextureInfo.empty())
		return NULL;
	else
		return &_TextureInfo[0];
}


// ***************************************************************************
// ***************************************************************************
// CLodCharacterShape
// ***************************************************************************
// ***************************************************************************


// ***************************************************************************
CLodCharacterShape::CLodCharacterShape()
{
	_NumVertices= 0;
	_NumTriangles= 0;
}

// ***************************************************************************
void			CLodCharacterShape::buildMesh(const std::string &name, const CLodCharacterShapeBuild &lodBuild)
{
	uint	numVertices= (uint)lodBuild.Vertices.size();
	const vector<uint32>				&triangleIndices= lodBuild.TriangleIndices;
	const vector<CMesh::CSkinWeight>	&skinWeights= lodBuild.SkinWeights;
	const vector<CUV>					&uvs= lodBuild.UVs;
	const vector<CVector>				&normals= lodBuild.Normals;

	nlassert(numVertices>0);
	nlassert(triangleIndices.size()>0);
	nlassert((triangleIndices.size()%3)==0);
	nlassert(skinWeights.size() == numVertices);
	nlassert(uvs.size() == numVertices);
	nlassert(normals.size() == numVertices);

	// reset data
	contReset(_Anims);
	contReset(_AnimMap);
	contReset(_Bones);
	contReset(_BoneMap);
	contReset(_TriangleIndices);
	contReset(_UVs);
	contReset(_Normals);

	// Copy data.
	_Name= name;
	_NumVertices= numVertices;
	_NumTriangles= (uint32)triangleIndices.size()/3;
	#ifdef NL_LOD_CHARACTER_INDEX16
		_TriangleIndices.resize(triangleIndices.size());
		for(uint k = 0; k < triangleIndices.size(); ++k)
		{
			nlassert(triangleIndices[k] <= 0xffff);
			_TriangleIndices[k] = (uint16) triangleIndices[k];
		}
	#else
		_TriangleIndices= triangleIndices;
	#endif
	_UVs= uvs;
	_Normals= normals;

	// check indices.
	uint	i;
	for(i=0;i<triangleIndices.size();i++)
	{
		nlassert(triangleIndices[i]<_NumVertices);
	}

	// Copy bone names, and compute bone Map
	_Bones.resize(lodBuild.BonesNames.size());
	for(i=0; i<_Bones.size(); i++)
	{
		_Bones[i].Name= lodBuild.BonesNames[i];
		_BoneMap.insert( make_pair(_Bones[i].Name, i) );
	}

	// "Normalize" SkinWeights for CLodCharacterShape
	for(i=0;i<skinWeights.size();i++)
	{
		nlassert(skinWeights[i].Weights[0]>0);
		// for all slots not 0
		for(uint j=0;j<NL3D_MESH_SKINNING_MAX_MATRIX;j++)
		{
			// if this this slot is used.
			if(skinWeights[i].Weights[j]>0)
			{
				uint boneId= skinWeights[i].MatrixId[j];
				nlassert(boneId < _Bones.size());
				// init the vInf data
				CVertexInf	vInf;
				vInf.VertexId= i;
				vInf.Influence= skinWeights[i].Weights[j];
				// Insert this vertex influence in the bone.
				_Bones[boneId].InfVertices.push_back(vInf);
			}
			else
				// stop for this vertex.
				break;
		}
	}
}

// ***************************************************************************
bool			CLodCharacterShape::addAnim(const CAnimBuild &animBuild)
{
	// first, verify don't exist.
	if(getAnimIdByName(animBuild.Name)!=-1)
		return false;

	// build basics of the animation
	CAnim	dstAnim;
	dstAnim.Name= animBuild.Name;
	dstAnim.AnimLength= animBuild.AnimLength;
	// Possible to have an Anim with just one key. setup an epsilon for animLength if 0.
	if(dstAnim.AnimLength<=0)
		dstAnim.AnimLength= 0.001f;
	dstAnim.OOAnimLength= 1.0f / animBuild.AnimLength;
	dstAnim.NumKeys= animBuild.NumKeys;
	// verify size of the array
	nlassert(dstAnim.NumKeys>0);
	nlassert(dstAnim.NumKeys * _NumVertices == animBuild.Keys.size());
	// resize dest array
	dstAnim.Keys.resize(animBuild.Keys.size());


	// Pack animation. 1st pass: compute max size over the animation vertices
	uint	i;
	// minimum shape size is , say, 1 cm :)
	CVector		maxSize(0.01f, 0.01f, 0.01f);
	for(i=0;i<animBuild.Keys.size();i++)
	{
		// take the maxSize of the abs values
		maxSize.maxof(maxSize, -animBuild.Keys[i]);
		maxSize.maxof(maxSize, animBuild.Keys[i]);
	}

	// compute the UnPackScaleFactor ie maxSize, to be multiplied by max Abs value of a sint16
	dstAnim.UnPackScaleFactor= maxSize * (1.0f/32767);

	// Pack animation. 2st pass: pack.
	CVectorD		packScaleFactor;
	packScaleFactor.x= 1.0 / dstAnim.UnPackScaleFactor.x;
	packScaleFactor.y= 1.0 / dstAnim.UnPackScaleFactor.y;
	packScaleFactor.z= 1.0 / dstAnim.UnPackScaleFactor.z;
	// For all key vertices
	for(i=0;i<animBuild.Keys.size();i++)
	{
		CVector		v= animBuild.Keys[i];
		CVector3s	&dstV= dstAnim.Keys[i];

		// compress
		v.x= float(v.x*packScaleFactor.x);
		v.y= float(v.y*packScaleFactor.y);
		v.z= float(v.z*packScaleFactor.z);
		// clamp to sint16 limits (for float precision problems).
		clamp(v.x, -32767, 32767);
		clamp(v.y, -32767, 32767);
		clamp(v.z, -32767, 32767);
		// get into the vector3s
		dstV.x= (sint16)floor(v.x);
		dstV.y= (sint16)floor(v.y);
		dstV.z= (sint16)floor(v.z);
	}


	// Add the anim to the array, and add an entry to the map
	_Anims.push_back(dstAnim);
	_AnimMap.insert(make_pair(dstAnim.Name, (uint32)_Anims.size()-1));

	return true;
}

// ***************************************************************************
void			CLodCharacterShape::CAnim::serial(NLMISC::IStream &f)
{
	(void)f.serialVersion(0);

	f.serial(Name);
	f.serial(NumKeys);
	f.serial(AnimLength);
	f.serial(OOAnimLength);
	f.serial(UnPackScaleFactor);
	f.serialCont(Keys);
}


// ***************************************************************************
void			CLodCharacterShape::CBoneInfluence::serial(NLMISC::IStream &f)
{
	(void)f.serialVersion(0);

	f.serial(Name);
	f.serialCont(InfVertices);
}


// ***************************************************************************
void			CLodCharacterShape::serial(NLMISC::IStream &f)
{
	// NEL_CLODSHAP
	f.serialCheck((uint32)'_LEN');
	f.serialCheck((uint32)'DOLC');
	f.serialCheck((uint32)'PAHS');

	/*
	Version 1:
		- UVs and Normals.
	*/
	sint ver= f.serialVersion(1);

	f.serial(_Name);
	f.serial(_NumVertices);
	f.serial(_NumTriangles);
	f.serialCont(_Bones);
	f.serialCont(_BoneMap);
	// nb : indices are always saved in 32 bits for compatibility
	#ifndef NL_LOD_CHARACTER_INDEX16
		f.serialCont(_TriangleIndices);
	#else
		if (f.isReading())
		{
			std::vector<uint32> savedIndices;
			f.serialCont(savedIndices);
			_TriangleIndices.resize(savedIndices.size());
			for(uint k = 0; k < savedIndices.size(); ++k)
			{
				nlassert(savedIndices[k] <= 0xffff);
				_TriangleIndices[k] = (uint16) savedIndices[k];
			}
		}
		else
		{
			std::vector<uint32> savedIndices;
			savedIndices.resize(_TriangleIndices.size());
			for(uint k = 0; k < savedIndices.size(); ++k)
			{
				savedIndices[k] = _TriangleIndices[k];
			}
			f.serialCont(savedIndices);
		}
	#endif
	f.serialCont(_Anims);
	f.serialCont(_AnimMap);

	if(ver>=1)
	{
		f.serialCont(_UVs);
		f.serialCont(_Normals);
	}
	else
	{
		// Must init dummy UVs/normals
		_UVs.resize(_NumVertices, CUV(0,0));
		_Normals.resize(_NumVertices, CVector::K);
	}
}

// ***************************************************************************
sint			CLodCharacterShape::getAnimIdByName(const std::string &name) const
{
	CstItStrIdMap	it= _AnimMap.find(name);
	if(it == _AnimMap.end())
		return -1;
	else
		return it->second;
}


// ***************************************************************************
sint			CLodCharacterShape::getBoneIdByName(const std::string &name) const
{
	CstItStrIdMap	it= _BoneMap.find(name);
	if(it == _BoneMap.end())
		return -1;
	else
		return it->second;
}


// ***************************************************************************
const TLodCharacterIndexType	*CLodCharacterShape::getTriangleArray() const
{
	if(_NumTriangles)
		return &_TriangleIndices[0];
	else
		return NULL;
}

// ***************************************************************************
const CLodCharacterShape::CVector3s	*CLodCharacterShape::getAnimKey(uint animId, TGlobalAnimationTime time, bool wrapMode, CVector &unPackScaleFactor) const
{
	H_AUTO( NL3D_LodCharacterShape_getAnimKey )

	float	localTime;

	if(animId>=_Anims.size())
		return NULL;

	// get the anim.
	const CAnim &anim= _Anims[animId];

	// scale info
	unPackScaleFactor= anim.UnPackScaleFactor;

	// Loop mgt.
	if(wrapMode)
		localTime= (float)fmod((float)time, (float)anim.AnimLength);
	else
		localTime= (float)time;

	// Clamp to the range.
	clamp(localTime, 0, anim.AnimLength);

	// get the key.
	sint	keyId= (sint)floor( (localTime*anim.OOAnimLength) * anim.NumKeys );
	clamp(keyId, 0, sint(anim.NumKeys-1));

	// return the key.
	return &anim.Keys[keyId * _NumVertices];
}

// ***************************************************************************
const CUV		*CLodCharacterShape::getUVs() const
{
	if(_NumVertices==0)
		return NULL;

	return &_UVs[0];
}

// ***************************************************************************
const CVector	*CLodCharacterShape::getNormals() const
{
	if(_NumVertices==0)
		return NULL;

	return &_Normals[0];
}



// ***************************************************************************
// ***************************************************************************
// Bone Alpha Testing
// ***************************************************************************
// ***************************************************************************


// ***************************************************************************
void			CLodCharacterShape::startBoneAlpha(std::vector<uint8>	&tmpAlphas) const
{
	// clear
	tmpAlphas.clear();
	// alocate, and fill
	tmpAlphas.resize(getNumVertices(), 0);
}

// ***************************************************************************
void			CLodCharacterShape::addBoneAlpha(uint boneId, std::vector<uint8> &tmpAlphas) const
{
	// Yoyo: This is an error to not have the same skeleton that the one stored in the lod shape. But must not crash
	if(boneId>=_Bones.size())
		return;
	const CBoneInfluence	&bone= _Bones[boneId];

	// for all vertices influenced by this bone, must set the alpha to full
	for(uint i=0; i<bone.InfVertices.size(); i++)
	{
		const CVertexInf	&vInf= bone.InfVertices[i];
		tmpAlphas[vInf.VertexId]= 255;
	}
}


} // NL3D