khanat-opennel-code/code/nel/tools/3d/build_clodtex/lod_texture_builder.cpp

419 lines
13 KiB
C++

// 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 "lod_texture_builder.h"
#include "nel/misc/aabbox.h"
using namespace std;
using namespace NLMISC;
using namespace NL3D;
// ***************************************************************************
// Quality factor. Useful to get maximum of interseting values in the range 0..255.
#define NL_LTB_MAX_DISTANCE_QUALITY_FACTOR 0.05f
// The bigger, the lower the influence of the normal is. must be >=0
#define NL_LTB_NORMAL_BIAS 1.f
// ***************************************************************************
CLodTextureBuilder::CLodTextureBuilder()
{
_OverSampleDistance= 0.05f;
}
// ***************************************************************************
float CLodTextureBuilder::computeQualityPixel(const CPixelInfo &p0, const CPixelInfo &p1) const
{
float d= (p1.Pos-p0.Pos).norm();
float dotProd= p1.Normal*p0.Normal;
// With NL_NORMAL_BIAS==1, it return froms d*1(normals aligned) to d*3 (opposite normals)
return d*(NL_LTB_NORMAL_BIAS+1-dotProd);
}
// ***************************************************************************
void CLodTextureBuilder::setLod(const NL3D::CLodCharacterShapeBuild &lod)
{
uint i;
_CLod= lod;
// **** compute the max quality distance. ie where CTUVQ.Q== 255
// build a bbox around the lod.
CAABBox bbox;
bbox.setCenter(lod.Vertices[0]);
for(i=0;i<lod.Vertices.size();i++)
bbox.extend(lod.Vertices[i]);
/* get the opposite vertices/normals and so compute the max qualityPixel "possible" (it is still possible
for the CMesh to be completely out of the bbox of the CLod, but doesn't matter)
*/
CPixelInfo p0, p1;
p0.Pos= bbox.getMin();
p0.Normal.set(1,0,0);
p1.Pos= bbox.getMax();
p1.Normal.set(-1,0,0);
_MaxDistanceQuality= computeQualityPixel(p0, p1);
// apply a user factor.
_MaxDistanceQuality*= NL_LTB_MAX_DISTANCE_QUALITY_FACTOR;
}
// ***************************************************************************
bool CLodTextureBuilder::computeTexture(const CMesh &mesh, NL3D::CLodCharacterTexture &text)
{
// a set to flag if an edge has already been overSampled
TEdgeSet edgeSet;
// **** Over sample the mesh
_Samples.clear();
_Samples.reserve(1000);
// Get vertex info
const CVertexBuffer &VB= mesh.getVertexBuffer();
CVertexBufferRead vba;
VB.lock (vba);
const uint8 *srcPos= (const uint8*)vba.getVertexCoordPointer();
const uint8 *srcNormal= (const uint8*)vba.getNormalCoordPointer();
const uint8 *srcUV= (const uint8*)vba.getTexCoordPointer();
uint vertexSize= VB.getVertexSize();
// For all matrix blocks..
for(uint mb=0; mb<mesh.getNbMatrixBlock();mb++)
{
// for all rdrPass
for(uint rp=0; rp<mesh.getNbRdrPass(mb);rp++)
{
const CIndexBuffer &pb= mesh.getRdrPassPrimitiveBlock(mb, rp);
CIndexBufferRead iba;
pb.lock (iba);
uint matId= mesh.getRdrPassMaterial(mb, rp);
// samples the tris of this pass
if (iba.getFormat() == CIndexBuffer::Indices16)
{
addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint16 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
}
else
{
nlassert(iba.getFormat() == CIndexBuffer::Indices32);
addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint32 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
}
}
}
// **** compute the texture, with the samples
computeTextureFromSamples(text);
return true;
}
// ***************************************************************************
bool CLodTextureBuilder::computeTexture(const CMeshMRM &meshMRM, NL3D::CLodCharacterTexture &text)
{
// a set to flag if an edge has already been overSampled
TEdgeSet edgeSet;
// **** Over sample the mesh
_Samples.clear();
_Samples.reserve(1000);
// Get vertex info
CVertexBuffer &VB= const_cast<CVertexBuffer&>(meshMRM.getVertexBuffer());
CVertexBufferRead vba;
VB.lock (vba);
const uint8 *srcPos= (const uint8*)vba.getVertexCoordPointer();
const uint8 *srcNormal= (const uint8*)vba.getNormalCoordPointer();
const uint8 *srcUV= (const uint8*)vba.getTexCoordPointer();
uint vertexSize = VB.getVertexSize();
// For the more precise lod
uint lodId= meshMRM.getNbLod()-1;
// Resolve Geomoprh problem: copy End to all geomorphs dest. Hence sure that all ids points to good vertex data
const std::vector<CMRMWedgeGeom> &geoms= meshMRM.getMeshGeom().getGeomorphs(lodId);
for(uint gm=0;gm<geoms.size();gm++)
{
uint srcId= geoms[gm].End;
// copy the geom src to the dest VB place.
*(CVector*)(srcPos+gm*vertexSize)= *(CVector*)(srcPos+srcId*vertexSize);
*(CVector*)(srcNormal+gm*vertexSize)= *(CVector*)(srcNormal+srcId*vertexSize);
*(CUV*)(srcUV+gm*vertexSize)= *(CUV*)(srcUV+srcId*vertexSize);
}
// for all rdrPass
for(uint rp=0; rp<meshMRM.getNbRdrPass(lodId);rp++)
{
const CIndexBuffer &pb= meshMRM.getRdrPassPrimitiveBlock(lodId, rp);
CIndexBufferRead iba;
pb.lock (iba);
uint matId= meshMRM.getRdrPassMaterial(lodId, rp);
// samples the tris of this pass
if (iba.getFormat() == CIndexBuffer::Indices16)
{
addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint16 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
}
else
{
nlassert(iba.getFormat() == CIndexBuffer::Indices32);
addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint32 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
}
}
// **** compute the texture, with the samples
computeTextureFromSamples(text);
return true;
}
// ***************************************************************************
bool CLodTextureBuilder::computeTexture(const CMeshMRMSkinned &meshMRM, NL3D::CLodCharacterTexture &text)
{
// a set to flag if an edge has already been overSampled
TEdgeSet edgeSet;
// **** Over sample the mesh
_Samples.clear();
_Samples.reserve(1000);
// Get vertex info
CVertexBuffer tmp;
meshMRM.getVertexBuffer(tmp);
CVertexBufferRead vba;
tmp.lock (vba);
const uint8 *srcPos= (const uint8*)vba.getVertexCoordPointer();
const uint8 *srcNormal= (const uint8*)vba.getNormalCoordPointer();
const uint8 *srcUV= (const uint8*)vba.getTexCoordPointer();
uint vertexSize= tmp.getVertexSize();
// For the more precise lod
uint lodId= meshMRM.getNbLod()-1;
// Resolve Geomoprh problem: copy End to all geomorphs dest. Hence sure that all ids points to good vertex data
const std::vector<CMRMWedgeGeom> &geoms= meshMRM.getMeshGeom().getGeomorphs(lodId);
for(uint gm=0;gm<geoms.size();gm++)
{
uint srcId= geoms[gm].End;
// copy the geom src to the dest VB place.
*(CVector*)(srcPos+gm*vertexSize)= *(CVector*)(srcPos+srcId*vertexSize);
*(CVector*)(srcNormal+gm*vertexSize)= *(CVector*)(srcNormal+srcId*vertexSize);
*(CUV*)(srcUV+gm*vertexSize)= *(CUV*)(srcUV+srcId*vertexSize);
}
// for all rdrPass
for(uint rp=0; rp<meshMRM.getNbRdrPass(lodId);rp++)
{
CIndexBuffer pb;
meshMRM.getRdrPassPrimitiveBlock(lodId, rp, pb);
uint matId= meshMRM.getRdrPassMaterial(lodId, rp);
CIndexBufferRead iba;
pb.lock (iba);
// samples the tris of this pass
if (iba.getFormat() == CIndexBuffer::Indices16)
{
addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint16 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
}
else
{
addSampleTris(srcPos, srcNormal, srcUV, vertexSize, (uint32 *) iba.getPtr(), pb.getNumIndexes()/3, matId, edgeSet);
}
}
// **** compute the texture, with the samples
computeTextureFromSamples(text);
return true;
}
// ***************************************************************************
void CLodTextureBuilder::addSampleTris(const uint8 *srcPos, const uint8 *srcNormal, const uint8 *srcUV, uint vertexSize,
const uint16 *triPointer, uint numTris, uint materialId, TEdgeSet &edgeSet)
{
std::vector<uint32> indices32(triPointer, triPointer + numTris * 3);
addSampleTris(srcPos, srcNormal, srcUV, vertexSize, &indices32[0], numTris, materialId, edgeSet);
}
// ***************************************************************************
void CLodTextureBuilder::addSampleTris(const uint8 *srcPos, const uint8 *srcNormal, const uint8 *srcUV, uint vertexSize,
const uint32 *triPointer, uint numTris, uint materialId, TEdgeSet &edgeSet)
{
nlassert(srcPos);
// ensure that the material is in 0..255
clamp(materialId, 0U, 255U);
for(uint i=0;i<numTris;i++)
{
uint c;
// **** get tri indices and tri values.
uint idx[3];
CVector pos[3];
CVector normal[3];
CUV uv[3];
for(c=0;c<3;c++)
{
idx[c]= *triPointer++;
pos[c]= *(CVector*)(srcPos + idx[c]*vertexSize);
if(srcNormal)
normal[c]= *(CVector*)(srcNormal + idx[c]*vertexSize);
else
normal[c]= CVector::K;
if(srcUV)
uv[c]= *(CUV*)(srcUV + idx[c]*vertexSize);
else
uv[c].set(0,0);
}
// **** Append the 3 vertices in the samples. only if not already done
for(c=0;c<3;c++)
{
// special edgeId: vertStart==vertEnd
TEdge edgeId;
edgeId.first= idx[c];
edgeId.second= idx[c];
// if success to insert in the set, then must add it to samples
if(edgeSet.insert(edgeId).second)
{
CSample sample;
sample.P.Pos= pos[c];
sample.P.Normal= normal[c];
sample.P.Normal.normalize();
sample.UV= uv[c];
sample.MaterialId= materialId;
_Samples.push_back(sample);
}
}
// **** Append the interior of each edges, according to _OverSampleDistance
uint numEdgeOverSamples[3];
for(c=0;c<3;c++)
{
uint cNext= (c+1)%3;
// get the edgeId
TEdge edgeId;
edgeId.first= idx[c];
edgeId.second= idx[cNext];
// compute the number of overSamples to apply to the edge
numEdgeOverSamples[c]= (uint)floor( (pos[cNext]-pos[c]).norm()/_OverSampleDistance );
// if success to insert in the set, then must add it to samples
if(edgeSet.insert(edgeId).second)
{
// Do it only if numOverSamples>=2
if(numEdgeOverSamples[c]>=2)
{
// Sample the edge, exclude endPoints.
for(uint s=1;s<numEdgeOverSamples[c];s++)
{
float f= s/(float)numEdgeOverSamples[c];
// interpoalte the sample
CSample sample;
sample.P.Pos= pos[c]*(1-f) + pos[cNext]*f;
sample.P.Normal= normal[c]*(1-f) + normal[cNext]*f;
sample.P.Normal.normalize();
sample.UV= uv[c]*(1-f) + uv[cNext]*f;
sample.MaterialId= materialId;
// add it
_Samples.push_back(sample);
}
}
}
}
// **** Append the interior of the triangle, if too many overSample
// compute min of over samples
uint triOverSample= min(numEdgeOverSamples[0], numEdgeOverSamples[1]);
triOverSample= min(triOverSample, numEdgeOverSamples[2]);
// If >=3 (at least one vertex in the middle)
if(triOverSample>=2)
{
// Interpolate with barycentric coordinate rules
uint s,t,v;
// Donc't compute on triangles edges.
for(s=1;s<triOverSample;s++)
{
for(t=1;t<triOverSample-s;t++)
{
// barycentric sum==1 guaranteed.
v= triOverSample-s-t;
float fs= s/(float)triOverSample;
float ft= t/(float)triOverSample;
float fv= v/(float)triOverSample;
// compute the sample
CSample sample;
sample.P.Pos= pos[0]*fs + pos[1]*ft + pos[2]*fv;
sample.P.Normal= normal[0]*fs + normal[1]*ft + normal[2]*fv;
sample.P.Normal.normalize();
sample.UV= uv[0]*fs + uv[1]*ft + uv[2]*fv;
sample.MaterialId= materialId;
// add it
_Samples.push_back(sample);
}
}
}
}
}
// ***************************************************************************
bool CLodTextureBuilder::computeTextureFromSamples(NL3D::CLodCharacterTexture &text)
{
// The lod must have correct width/height
if(NL3D_CLOD_TEXT_WIDTH!=_CLod.getTextureInfoWidth() || NL3D_CLOD_TEXT_HEIGHT!=_CLod.getTextureInfoHeight() )
{
nlwarning("ERROR: the _CLod must have a textureInfo size of %d/%d", NL3D_CLOD_TEXT_WIDTH, NL3D_CLOD_TEXT_HEIGHT);
return false;
}
// prepare dest.
text.Texture.resize(NL3D_CLOD_TEXT_SIZE);
CLodCharacterTexture::CTUVQ emptyTUVQ;
emptyTUVQ.T= 0;
emptyTUVQ.U= 0;
emptyTUVQ.V= 0;
emptyTUVQ.Q= 255;
fill(text.Texture.begin(), text.Texture.end(), emptyTUVQ);
// For all pixels.
const CPixelInfo *srcPixel= _CLod.getTextureInfoPtr();
for(uint i=0;i<NL3D_CLOD_TEXT_SIZE;i++)
{
// if the src pixel is not empty
if( !(srcPixel[i]==CPixelInfo::EmptyPixel) )
{
CLodCharacterTexture::CTUVQ &dstTUVQ= text.Texture[i];
// For All samples, search the best.
float minQuality= FLT_MAX;
uint bestIdx= 0;
for(uint j=0;j<_Samples.size();j++)
{
float q= computeQualityPixel(srcPixel[i], _Samples[j].P);
if(q<minQuality)
{
minQuality= q;
bestIdx= j;
}
}
// Then compress the sample info in the TUVQ.
dstTUVQ.T= _Samples[bestIdx].MaterialId;
dstTUVQ.U= (uint8)(sint)(_Samples[bestIdx].UV.U*256);
dstTUVQ.V= (uint8)(sint)(_Samples[bestIdx].UV.V*256);
minQuality= minQuality*255/_MaxDistanceQuality;
clamp(minQuality, 0, 255);
dstTUVQ.Q= (uint8)minQuality;
}
}
return true;
}