// NeL - MMORPG Framework
// 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 .
#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 &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 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 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 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 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 &triangleIndices= lodBuild.TriangleIndices;
const vector &skinWeights= lodBuild.SkinWeights;
const vector &uvs= lodBuild.UVs;
const vector &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;i0);
// for all slots not 0
for(uint j=0;j0)
{
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 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 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 &tmpAlphas) const
{
// clear
tmpAlphas.clear();
// alocate, and fill
tmpAlphas.resize(getNumVertices(), 0);
}
// ***************************************************************************
void CLodCharacterShape::addBoneAlpha(uint boneId, std::vector &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