khanat-opennel-code/code/nel/src/3d/mesh.cpp
2010-11-13 18:33:01 +01:00

2783 lines
81 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 "std3d.h"
#include "nel/3d/mesh.h"
#include "nel/3d/mesh_instance.h"
#include "nel/3d/scene.h"
#include "nel/3d/skeleton_model.h"
#include "nel/3d/mesh_morpher.h"
#include "nel/misc/bsphere.h"
#include "nel/3d/stripifier.h"
#include "nel/misc/fast_floor.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/3d/mesh_blender.h"
#include "nel/3d/matrix_3x4.h"
#include "nel/3d/render_trav.h"
#include "nel/3d/visual_collision_mesh.h"
#include "nel/3d/meshvp_wind_tree.h"
using namespace std;
using namespace NLMISC;
namespace NL3D
{
// ***************************************************************************
// ***************************************************************************
// MeshGeom Tools.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
static NLMISC::CAABBoxExt makeBBox(const std::vector<CVector> &Vertices)
{
NLMISC::CAABBox ret;
nlassert(Vertices.size());
ret.setCenter(Vertices[0]);
for(sint i=0;i<(sint)Vertices.size();i++)
{
ret.extend(Vertices[i]);
}
return ret;
}
// ***************************************************************************
sint CMeshGeom::CCornerTmp::Flags=0;
// ***************************************************************************
bool CMeshGeom::CCornerTmp::operator<(const CCornerTmp &c) const
{
sint i;
// Vert first.
if(Vertex!=c.Vertex)
return Vertex<c.Vertex;
// Order: normal, uvs, color0, color1, skinning.
if((CCornerTmp::Flags & CVertexBuffer::NormalFlag) && Normal!=c.Normal)
return Normal<c.Normal;
for(i=0; i<CVertexBuffer::MaxStage; i++)
{
if((CCornerTmp::Flags & (CVertexBuffer::TexCoord0Flag<<i)) && Uvws[i]!=c.Uvws[i])
return Uvws[i]<c.Uvws[i];
}
if((CCornerTmp::Flags & CVertexBuffer::PrimaryColorFlag) && Color!=c.Color)
return Color<c.Color;
if((CCornerTmp::Flags & CVertexBuffer::SecondaryColorFlag) && Specular!=c.Specular)
return Specular<c.Specular;
if ((CCornerTmp::Flags & CVertexBuffer::PaletteSkinFlag)==CVertexBuffer::PaletteSkinFlag)
{
for(i=0;i<NL3D_MESH_SKINNING_MAX_MATRIX;i++)
{
if(Palette.MatrixId[i] != c.Palette.MatrixId[i])
return Palette.MatrixId[i] < c.Palette.MatrixId[i];
if(Weights[i] != c.Weights[i])
return Weights[i] < c.Weights[i];
}
}
// All are equal!!
return false;
}
// ***************************************************************************
// ***************************************************************************
// CMeshGeom.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CMeshGeom::CMeshGeom()
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
_Skinned= false;
_OriginalSkinRestored= true;
_MeshMorpher = new CMeshMorpher;
_BoneIdComputed = false;
_BoneIdExtended= false;
_PreciseClipping= false;
}
// ***************************************************************************
CMeshGeom::~CMeshGeom()
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
delete _MeshMorpher;
}
// ***************************************************************************
void CMeshGeom::optimizeTriangleOrder()
{
CStripifier stripifier;
// for all rdrpass of all matrix blocks.
for(uint mb= 0;mb<_MatrixBlocks.size();mb++)
{
for(uint rp=0; rp<_MatrixBlocks[mb].RdrPass.size(); rp++ )
{
// stripify list of triangles of this pass.
CRdrPass &pass= _MatrixBlocks[mb].RdrPass[rp];
stripifier.optimizeTriangles(pass.PBlock, pass.PBlock);
}
}
}
// ***************************************************************************
void CMeshGeom::build (CMesh::CMeshBuild &m, uint numMaxMaterial)
{
sint i;
// Empty geometry?
if(m.Vertices.size()==0 || m.Faces.size()==0)
{
_VBuffer.setNumVertices(0);
_VBuffer.setName("CMeshGeom");
_VBuffer.reserve(0);
_MatrixBlocks.clear();
_BBox.setCenter(CVector::Null);
_BBox.setSize(CVector::Null);
return;
}
nlassert(numMaxMaterial>0);
// Copy the UV routing table
for (i=0; i<CVertexBuffer::MaxStage; i++)
_VBuffer.setUVRouting (i, m.UVRouting[i]);
/// 0. First, make bbox.
//======================
_BBox= makeBBox(m.Vertices);
/// 1. If skinning, group by matrix Block the vertices.
//================================================
// First, copy Face array.
vector<CFaceTmp> tmpFaces;
tmpFaces.resize(m.Faces.size());
for(i=0;i<(sint)tmpFaces.size();i++)
tmpFaces[i]= m.Faces[i];
_Skinned= ((m.VertexFlags & CVertexBuffer::PaletteSkinFlag)==CVertexBuffer::PaletteSkinFlag);
// Skinning is OK only if SkinWeights are of same size as vertices.
_Skinned= _Skinned && (m.Vertices.size()==m.SkinWeights.size());
// If skinning is KO, remove the Skin option.
uint vbFlags= m.VertexFlags;
if(!_Skinned)
vbFlags&= ~CVertexBuffer::PaletteSkinFlag;
// Force presence of vertex.
vbFlags|= CVertexBuffer::PositionFlag;
// If the mesh is not skinned, we have just 1 _MatrixBlocks.
if(!_Skinned)
{
_MatrixBlocks.resize(1);
// For each faces, assign it to the matrix block 0.
for(i=0;i<(sint)tmpFaces.size();i++)
tmpFaces[i].MatrixBlockId= 0;
}
// Else We must group/compute the matrixs blocks.
else
{
// reset matrix blocks.
_MatrixBlocks.clear();
// build matrix blocks, and link faces to good matrix blocks.
buildSkin(m, tmpFaces);
}
/// 2. Then, for all faces, resolve continuities, building VBuffer.
//================================================
// Setup VB.
_VBuffer.setNumVertices(0);
_VBuffer.reserve(0);
bool useFormatExt = false;
/** If all texture coordinates are of dimension 2, we can setup the flags as before.
* If this isn't the case, we must setup a custom format
*/
for (uint k = 0; k < CVertexBuffer::MaxStage; ++k)
{
if (
(vbFlags & (CVertexBuffer::TexCoord0Flag << k))
&& m.NumCoords[k] != 2)
{
useFormatExt = true;
break;
}
}
if (!useFormatExt)
{
// setup standard format
_VBuffer.setVertexFormat(vbFlags);
}
else // setup extended format
{
_VBuffer.clearValueEx();
if (vbFlags & CVertexBuffer::PositionFlag) _VBuffer.addValueEx(CVertexBuffer::Position, CVertexBuffer::Float3);
if (vbFlags & CVertexBuffer::NormalFlag) _VBuffer.addValueEx(CVertexBuffer::Normal, CVertexBuffer::Float3);
if (vbFlags & CVertexBuffer::PrimaryColorFlag) _VBuffer.addValueEx(CVertexBuffer::PrimaryColor, CVertexBuffer::UChar4);
if (vbFlags & CVertexBuffer::SecondaryColorFlag) _VBuffer.addValueEx(CVertexBuffer::SecondaryColor, CVertexBuffer::UChar4);
if (vbFlags & CVertexBuffer::WeightFlag) _VBuffer.addValueEx(CVertexBuffer::Weight, CVertexBuffer::Float4);
if (vbFlags & CVertexBuffer::PaletteSkinFlag) _VBuffer.addValueEx(CVertexBuffer::PaletteSkin, CVertexBuffer::UChar4);
if (vbFlags & CVertexBuffer::FogFlag) _VBuffer.addValueEx(CVertexBuffer::Fog, CVertexBuffer::Float1);
for (uint k = 0; k < CVertexBuffer::MaxStage; ++k)
{
if (vbFlags & (CVertexBuffer::TexCoord0Flag << k))
{
switch(m.NumCoords[k])
{
case 2:
_VBuffer.addValueEx((CVertexBuffer::TValue) (CVertexBuffer::TexCoord0 + k), CVertexBuffer::Float2);
break;
case 3:
_VBuffer.addValueEx((CVertexBuffer::TValue) (CVertexBuffer::TexCoord0 + k), CVertexBuffer::Float3);
break;
default:
nlassert(0);
break;
}
}
}
_VBuffer.initEx();
}
// Set local flags for corner comparison.
CCornerTmp::Flags= vbFlags;
// Setup locals.
TCornerSet corners;
const CFaceTmp *pFace= &(*tmpFaces.begin());
uint32 nFaceMB = 0;
sint N= (sint)tmpFaces.size();
sint currentVBIndex=0;
m.VertLink.clear ();
// process each face, building up the VB.
for(;N>0;N--, pFace++)
{
sint v0= pFace->Corner[0].Vertex;
sint v1= pFace->Corner[1].Vertex;
sint v2= pFace->Corner[2].Vertex;
findVBId(corners, &pFace->Corner[0], currentVBIndex, m.Vertices[v0], m);
findVBId(corners, &pFace->Corner[1], currentVBIndex, m.Vertices[v1], m);
findVBId(corners, &pFace->Corner[2], currentVBIndex, m.Vertices[v2], m);
CMesh::CVertLink vl1(nFaceMB, 0, pFace->Corner[0].VBId);
CMesh::CVertLink vl2(nFaceMB, 1, pFace->Corner[1].VBId);
CMesh::CVertLink vl3(nFaceMB, 2, pFace->Corner[2].VBId);
m.VertLink.push_back(vl1);
m.VertLink.push_back(vl2);
m.VertLink.push_back(vl3);
++nFaceMB;
}
/// 3. build the RdrPass material.
//================================
uint mb;
// For each _MatrixBlocks, point on those materials.
for(mb= 0;mb<_MatrixBlocks.size();mb++)
{
// Build RdrPass ids.
_MatrixBlocks[mb].RdrPass.resize (numMaxMaterial);
for(i=0;i<(sint)_MatrixBlocks[mb].RdrPass.size(); i++)
{
_MatrixBlocks[mb].RdrPass[i].MaterialId= i;
// for build, force 32 bit indices
_MatrixBlocks[mb].RdrPass[i].PBlock.setFormat(CIndexBuffer::Indices32);
}
}
/// 4. Then, for all faces, build the RdrPass PBlock.
//===================================================
pFace= &(*tmpFaces.begin());
N= (sint)tmpFaces.size();
for(;N>0;N--, pFace++)
{
sint mbId= pFace->MatrixBlockId;
nlassert(mbId>=0 && mbId<(sint)_MatrixBlocks.size());
// Insert the face in good MatrixBlock/RdrPass.
CIndexBuffer &ib = _MatrixBlocks[mbId].RdrPass[pFace->MaterialId].PBlock;
uint index = ib.getNumIndexes();
ib.setNumIndexes (index+3);
CIndexBufferReadWrite iba;
ib.lock (iba);
iba.setTri(index, pFace->Corner[0].VBId, pFace->Corner[1].VBId, pFace->Corner[2].VBId);
}
/// 5. Remove empty RdrPasses.
//============================
for(mb= 0;mb<_MatrixBlocks.size();mb++)
{
// NB: slow process (erase from a vector). Doens't matter since made at build.
vector<CRdrPass>::iterator itPass;
for( itPass=_MatrixBlocks[mb].RdrPass.begin(); itPass!=_MatrixBlocks[mb].RdrPass.end(); )
{
// If this pass is empty, remove it.
if( itPass->PBlock.getNumIndexes()==0 )
itPass= _MatrixBlocks[mb].RdrPass.erase(itPass);
else
itPass++;
}
}
/// 6. Misc.
//============================
// BShapes
this->_MeshMorpher->BlendShapes = m.BlendShapes;
// sort triangles for better cache use.
optimizeTriangleOrder();
// SmartPtr Copy VertexProgram effect.
this->_MeshVertexProgram= m.MeshVertexProgram;
/// 7. Compact bones id and build bones name array.
//=================================================
// If skinned
if(_Skinned)
{
// Reserve some space
_BonesName.reserve (m.BonesNames.size ());
// Current local bone
uint currentBone = 0;
// For each matrix block
uint matrixBlock;
for (matrixBlock=0; matrixBlock<_MatrixBlocks.size(); matrixBlock++)
{
// Ref on the matrix block
CMatrixBlock &mb = _MatrixBlocks[matrixBlock];
// Remap the skeleton index in model index
std::map<uint, uint> remap;
// For each matrix
uint matrix;
for (matrix=0; matrix<mb.NumMatrix; matrix++)
{
// Get bone id in the skeleton
std::map<uint, uint>::iterator ite = remap.find (mb.MatrixId[matrix]);
// Not found
if (ite == remap.end())
{
// Insert it
remap.insert (std::map<uint, uint>::value_type (mb.MatrixId[matrix], currentBone));
// Check the matrix id
nlassert (mb.MatrixId[matrix] < m.BonesNames.size());
// Set the bone name
_BonesName.push_back (m.BonesNames[mb.MatrixId[matrix]]);
// Set the id in local
mb.MatrixId[matrix] = currentBone++;
}
else
{
// Set the id in local
mb.MatrixId[matrix] = ite->second;
}
}
}
// Bone id in local
_BoneIdComputed = false;
_BoneIdExtended = false;
}
// Set the vertex buffer preferred memory
bool avoidVBHard= _Skinned || ( _MeshMorpher && _MeshMorpher->BlendShapes.size()>0 );
_VBuffer.setPreferredMemory (avoidVBHard?CVertexBuffer::RAMPreferred:CVertexBuffer::StaticPreferred, false);
// End!!
// Some runtime not serialized compilation
compileRunTime();
}
// ***************************************************************************
void CMeshGeom::setBlendShapes(std::vector<CBlendShape>&bs)
{
_MeshMorpher->BlendShapes = bs;
// must update some RunTime parameters
compileRunTime();
}
// ***************************************************************************
void CMeshGeom::applyMaterialRemap(const std::vector<sint> &remap)
{
for(uint mb=0;mb<getNbMatrixBlock();mb++)
{
for(uint rp=0;rp<getNbRdrPass(mb);rp++)
{
// remap
uint32 &matId= _MatrixBlocks[mb].RdrPass[rp].MaterialId;
nlassert(remap[matId]>=0);
matId= remap[matId];
}
}
}
// ***************************************************************************
void CMeshGeom::initInstance(CMeshBaseInstance *mbi)
{
// init the instance with _MeshVertexProgram infos
if(_MeshVertexProgram)
_MeshVertexProgram->initInstance(mbi);
}
// ***************************************************************************
bool CMeshGeom::clip(const std::vector<CPlane> &pyramid, const CMatrix &worldMatrix)
{
// Speed Clip: clip just the sphere.
CBSphere localSphere(_BBox.getCenter(), _BBox.getRadius());
CBSphere worldSphere;
// transform the sphere in WorldMatrix (with nearly good scale info).
localSphere.applyTransform(worldMatrix, worldSphere);
// if out of only plane, entirely out.
for(sint i=0;i<(sint)pyramid.size();i++)
{
// We are sure that pyramid has normalized plane normals.
// if SpherMax OUT return false.
float d= pyramid[i]*worldSphere.Center;
if(d>worldSphere.Radius)
return false;
}
// test if must do a precise clip, according to mesh size.
if( _PreciseClipping )
{
CPlane localPlane;
// if out of only plane, entirely out.
for(sint i=0;i<(sint)pyramid.size();i++)
{
// Transform the pyramid in Object space.
localPlane= pyramid[i]*worldMatrix;
// localPlane must be normalized, because worldMatrix mya have a scale.
localPlane.normalize();
// if the box is not partially inside the plane, quit
if( !_BBox.clipBack(localPlane) )
return false;
}
}
return true;
}
// ***************************************************************************
void CMeshGeom::render(IDriver *drv, CTransformShape *trans, float polygonCount, uint32 rdrFlags, float globalAlpha)
{
nlassert(drv);
// get the mesh instance.
CMeshBaseInstance *mi= safe_cast<CMeshBaseInstance*>(trans);
// get a ptr on scene
CScene *ownerScene= mi->getOwnerScene();
// get a ptr on renderTrav
CRenderTrav *renderTrav= &ownerScene->getRenderTrav();
// Soft vb if not supported by the driver
if (drv->slowUnlockVertexBufferHard())
_VBuffer.setPreferredMemory (CVertexBuffer::RAMPreferred, false);
// get the skeleton model to which I am binded (else NULL).
CSkeletonModel *skeleton;
skeleton= mi->getSkeletonModel();
// The mesh must not be skinned for render()
nlassert(!(_Skinned && mi->isSkinned() && skeleton));
bool bMorphApplied = _MeshMorpher->BlendShapes.size() > 0;
bool useTangentSpace = _MeshVertexProgram && _MeshVertexProgram->needTangentSpace();
// Profiling
//===========
H_AUTO( NL3D_MeshGeom_RenderNormal );
// Morphing
// ========
if (bMorphApplied)
{
// If _Skinned (NB: the skin is not applied) and if lod.OriginalSkinRestored, then restoreOriginalSkinPart is
// not called but mush morpher write changed vertices into VBHard so its ok. The unchanged vertices
// are written in the preceding call to restoreOriginalSkinPart.
if (_Skinned)
{
_MeshMorpher->initSkinned(&_VBufferOri,
&_VBuffer,
useTangentSpace,
&_OriginalSkinVertices,
&_OriginalSkinNormals,
useTangentSpace ? &_OriginalTGSpace : NULL,
false );
_MeshMorpher->updateSkinned (mi->getBlendShapeFactors());
}
else // Not even skinned so we have to do all the stuff
{
_MeshMorpher->init(&_VBufferOri,
&_VBuffer,
useTangentSpace);
_MeshMorpher->update (mi->getBlendShapeFactors());
}
}
// Skinning
// ========
// else setup instance matrix
drv->setupModelMatrix(trans->getWorldMatrix());
// since instance skin is invalid but mesh is skinned , we must copy vertices/normals from original vertices.
if (_Skinned)
{
// do it for this Lod only, and if cache say it is necessary.
if (!_OriginalSkinRestored)
restoreOriginalSkinVertices();
}
// Setup meshVertexProgram
//===========
// use MeshVertexProgram effect?
bool useMeshVP= _MeshVertexProgram != NULL;
if( useMeshVP )
{
CMatrix invertedObjectMatrix;
invertedObjectMatrix = trans->getWorldMatrix().inverted();
// really ok if success to begin VP
useMeshVP= _MeshVertexProgram->begin(drv, ownerScene, mi, invertedObjectMatrix, renderTrav->CamPos);
if (!useMeshVP && !mi->_VPWindTreeFixed)
{
if (dynamic_cast<CMeshVPWindTree *>(&(*_MeshVertexProgram)))
{
// fix for mesh tree v.p : all material should be lighted
for(uint mb=0;mb<_MatrixBlocks.size();mb++)
{
CMatrixBlock &mBlock= _MatrixBlocks[mb];
for(uint i=0;i<mBlock.RdrPass.size();i++)
{
CMaterial &mat=mi->Materials[mBlock.RdrPass[i].MaterialId];
mat.setLighting(true, mat.getEmissive(), mat.getAmbient(), mat.getDiffuse(), mat.getSpecular());
}
}
}
mi->_VPWindTreeFixed = true;
}
}
// Render the mesh.
//===========
// active VB.
drv->activeVertexBuffer(_VBuffer);
// Global alpha used ?
uint32 globalAlphaUsed= rdrFlags & IMeshGeom::RenderGlobalAlpha;
uint8 globalAlphaInt=(uint8)NLMISC::OptFastFloor(globalAlpha*255);
// For all _MatrixBlocks
for(uint mb=0;mb<_MatrixBlocks.size();mb++)
{
CMatrixBlock &mBlock= _MatrixBlocks[mb];
if(mBlock.RdrPass.size()==0)
continue;
// Global alpha ?
if (globalAlphaUsed)
{
bool gaDisableZWrite= (rdrFlags & IMeshGeom::RenderGADisableZWrite)?true:false;
// Render all pass.
for (uint i=0;i<mBlock.RdrPass.size();i++)
{
CRdrPass &rdrPass= mBlock.RdrPass[i];
// Render with the Materials of the MeshInstance.
if ( ( (mi->Materials[rdrPass.MaterialId].getBlend() == false) && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) ||
( (mi->Materials[rdrPass.MaterialId].getBlend() == true) && (rdrFlags & IMeshGeom::RenderTransparentMaterial) ) )
{
// CMaterial Ref
CMaterial &material=mi->Materials[rdrPass.MaterialId];
// Use a MeshBlender to modify material and driver.
CMeshBlender blender;
blender.prepareRenderForGlobalAlpha(material, drv, globalAlpha, globalAlphaInt, gaDisableZWrite);
// Setup VP material
if (useMeshVP)
{
_MeshVertexProgram->setupForMaterial(material, drv, ownerScene, &_VBuffer);
}
// Render
drv->activeIndexBuffer(rdrPass.PBlock);
drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3);
// Resetup material/driver
blender.restoreRender(material, drv, gaDisableZWrite);
}
}
}
else
{
// Render all pass.
for(uint i=0;i<mBlock.RdrPass.size();i++)
{
CRdrPass &rdrPass= mBlock.RdrPass[i];
// Render with the Materials of the MeshInstance.
if( ( (mi->Materials[rdrPass.MaterialId].getBlend() == false) && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) ||
( (mi->Materials[rdrPass.MaterialId].getBlend() == true) && (rdrFlags & IMeshGeom::RenderTransparentMaterial) ) )
{
// CMaterial Ref
CMaterial &material=mi->Materials[rdrPass.MaterialId];
// Setup VP material
if (useMeshVP)
{
_MeshVertexProgram->setupForMaterial(material, drv, ownerScene, &_VBuffer);
}
// render primitives
drv->activeIndexBuffer(rdrPass.PBlock);
drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3);
}
}
}
}
// End VertexProgram effect
if(useMeshVP)
{
// Apply it.
_MeshVertexProgram->end(drv);
}
}
// ***************************************************************************
void CMeshGeom::renderSkin(CTransformShape *trans, float alphaMRM)
{
// get the mesh instance.
CMeshBaseInstance *mi= safe_cast<CMeshBaseInstance*>(trans);
// get a ptr on scene
CScene *ownerScene= mi->getOwnerScene();
// get a ptr on renderTrav
CRenderTrav *renderTrav= &ownerScene->getRenderTrav();
// get a ptr on the driver
IDriver *drv= renderTrav->getDriver();
nlassert(drv);
// get the skeleton model to which I am binded (else NULL).
CSkeletonModel *skeleton;
skeleton= mi->getSkeletonModel();
// must be skinned for renderSkin()
nlassert(_Skinned && mi->isSkinned() && skeleton);
bool bMorphApplied = _MeshMorpher->BlendShapes.size() > 0;
bool useTangentSpace = _MeshVertexProgram && _MeshVertexProgram->needTangentSpace();
// Profiling
//===========
H_AUTO( NL3D_MeshGeom_RenderSkinned );
// Morphing
// ========
if (bMorphApplied)
{
// Since Skinned we must update original skin vertices and normals because skinning use it
_MeshMorpher->initSkinned(&_VBufferOri,
&_VBuffer,
useTangentSpace,
&_OriginalSkinVertices,
&_OriginalSkinNormals,
useTangentSpace ? &_OriginalTGSpace : NULL,
true );
_MeshMorpher->updateSkinned (mi->getBlendShapeFactors());
}
// Skinning
// ========
// NB: the skeleton matrix has already been setuped by CSkeletonModel
// NB: the normalize flag has already been setuped by CSkeletonModel
// apply the skinning: _VBuffer is modified.
applySkin(skeleton);
// Setup meshVertexProgram
//===========
// use MeshVertexProgram effect?
bool useMeshVP= _MeshVertexProgram != NULL;
if( useMeshVP )
{
CMatrix invertedObjectMatrix;
invertedObjectMatrix = skeleton->getWorldMatrix().inverted();
// really ok if success to begin VP
useMeshVP= _MeshVertexProgram->begin(drv, ownerScene, mi, invertedObjectMatrix, renderTrav->CamPos);
}
// Render the mesh.
//===========
// active VB.
drv->activeVertexBuffer(_VBuffer);
// For all _MatrixBlocks
for(uint mb=0;mb<_MatrixBlocks.size();mb++)
{
CMatrixBlock &mBlock= _MatrixBlocks[mb];
if(mBlock.RdrPass.size()==0)
continue;
// Render all pass.
for(uint i=0;i<mBlock.RdrPass.size();i++)
{
CRdrPass &rdrPass= mBlock.RdrPass[i];
// CMaterial Ref
CMaterial &material=mi->Materials[rdrPass.MaterialId];
// Setup VP material
if (useMeshVP)
{
_MeshVertexProgram->setupForMaterial(material, drv, ownerScene, &_VBuffer);
}
// render primitives
drv->activeIndexBuffer(rdrPass.PBlock);
drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3);
}
}
// End VertexProgram effect
if(useMeshVP)
{
// Apply it.
_MeshVertexProgram->end(drv);
}
}
// ***************************************************************************
void CMeshGeom::renderSimpleWithMaterial(IDriver *drv, const CMatrix &worldMatrix, CMaterial &mat)
{
H_AUTO( NL3D_MeshGeom_RenderSimpleWithMaterial );
nlassert(drv);
// setup matrix
drv->setupModelMatrix(worldMatrix);
// Active simple VB.
drv->activeVertexBuffer(_VBuffer);
// For all _MatrixBlocks
for(uint mb=0;mb<_MatrixBlocks.size();mb++)
{
CMatrixBlock &mBlock= _MatrixBlocks[mb];
if(mBlock.RdrPass.size()==0)
continue;
// Render all pass.
for(uint i=0;i<mBlock.RdrPass.size();i++)
{
CRdrPass &rdrPass= mBlock.RdrPass[i];
// render primitives
drv->activeIndexBuffer(rdrPass.PBlock);
drv->renderTriangles(mat, 0, rdrPass.PBlock.getNumIndexes()/3);
}
}
}
// ***************************************************************************
void CMeshGeom::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
/*
Version 5:
- Preferred memory.
Version 4:
- BonesName.
Version 3:
- MeshVertexProgram.
Version 2:
- precompute of triangle order. (nothing more to load).
Version 1:
- added blend shapes
Version 0:
- separate serialisation CMesh / CMeshGeom.
*/
sint ver = f.serialVersion (4);
// must have good original Skinned Vertex before writing.
if( !f.isReading() && _Skinned && !_OriginalSkinRestored )
{
restoreOriginalSkinVertices();
}
// Version 4+: Array of bone name
if (ver >= 4)
{
f.serialCont (_BonesName);
}
if (f.isReading())
{
// Version3-: Bones index are in skeleton model id list
_BoneIdComputed = (ver < 4);
// In all case, must recompute usage of parents.
_BoneIdExtended= false;
}
else
{
// Warning, if you have skinned this shape, you can't write it anymore because skinning id have been changed!
nlassert (_BoneIdComputed==false);
}
// Version3+: MeshVertexProgram.
if (ver >= 3)
{
IMeshVertexProgram *mvp= NULL;
if(f.isReading())
{
f.serialPolyPtr(mvp);
_MeshVertexProgram= mvp;
}
else
{
mvp= _MeshVertexProgram;
f.serialPolyPtr(mvp);
}
}
else if(f.isReading())
{
// release vp
_MeshVertexProgram= NULL;
}
// TestYoyo
//_MeshVertexProgram= NULL;
// Version1+: _MeshMorpher.
if (ver >= 1)
f.serial (*_MeshMorpher);
// serial geometry.
f.serial (_VBuffer);
f.serialCont (_MatrixBlocks);
f.serial (_BBox);
f.serial (_Skinned);
// If _VertexBuffer changed, flag the VertexBufferHard.
if(f.isReading())
{
// if >= version 2, reorder of triangles is precomputed, else compute it now.
if(ver < 2 )
{
optimizeTriangleOrder();
}
}
// Skinning: If Version < 4, _BonesName are not present, must compute _BonesId from localId
// Else it is computed at first computeBonesId().
if(ver < 4)
buildBoneUsageVer3();
// TestYoyo
//_MeshVertexProgram= NULL;
/*{
uint numTris= 0;
for(uint i=0;i<_MatrixBlocks.size();i++)
{
for(uint j=0;j<_MatrixBlocks[i].RdrPass.size();j++)
numTris+= _MatrixBlocks[i].RdrPass[j].PBlock.getNumTri();
}
nlinfo("YOYO: %d Vertices. %d Triangles.", _VBuffer.getNumVertices(), numTris);
}*/
// Some runtime not serialized compilation
if(f.isReading())
compileRunTime();
}
// ***************************************************************************
void CMeshGeom::compileRunTime()
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
// if skinned, prepare skinning
if(_Skinned)
{
// bkup vertices
bkupOriginalSkinVertices();
// build the shadow skin
buildShadowSkin();
}
// Do precise clipping for big object??
_PreciseClipping= _BBox.getRadius() >= NL3D_MESH_PRECISE_CLIP_THRESHOLD;
// Support MeshBlockRendering only if not skinned/meshMorphed.
bool supportMeshBlockRendering= !_Skinned && _MeshMorpher->BlendShapes.size()==0;
// true only if one matrix block, and at least one rdrPass.
supportMeshBlockRendering= supportMeshBlockRendering && _MatrixBlocks.size()==1 && _MatrixBlocks[0].RdrPass.size()>0;
if (supportMeshBlockRendering && _MeshVertexProgram)
{
supportMeshBlockRendering = supportMeshBlockRendering && _MeshVertexProgram->supportMeshBlockRendering();
}
// TestYoyo
//supportMeshBlockRendering= false;
// support MeshVertexProgram, but no material sorting...
bool supportMBRPerMaterial= supportMeshBlockRendering && _MeshVertexProgram==NULL;
// setup flags
_SupportMBRFlags= 0;
if(supportMeshBlockRendering)
_SupportMBRFlags|= MBROk;
if(supportMBRPerMaterial)
_SupportMBRFlags|= MBRSortPerMaterial;
bool avoidVBHard= _Skinned || ( _MeshMorpher && _MeshMorpher->BlendShapes.size()>0 );
_VBuffer.setPreferredMemory (avoidVBHard?CVertexBuffer::RAMPreferred:CVertexBuffer::StaticPreferred, false);
}
// ***************************************************************************
bool CMeshGeom::retrieveVertices(std::vector<NLMISC::CVector> &vertices) const
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
uint i;
// if resident, fails!!! cannot read!
const CVertexBuffer &vb= getVertexBuffer();
if(vb.isResident())
return false;
vertices.clear();
vertices.resize(vb.getNumVertices());
{
CVertexBufferRead vba;
vb.lock (vba);
const uint8 *pVert= (const uint8*)vba.getVertexCoordPointer(0);
uint vSize= vb.getVertexSize();
for(i=0;i<vertices.size();i++)
{
vertices[i]= *(const CVector*)pVert;
pVert+= vSize;
}
}
return true;
}
// ***************************************************************************
bool CMeshGeom::retrieveTriangles(std::vector<uint32> &indices) const
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
uint i;
indices.clear();
// count numTris
uint numTris= 0;
for(i=0;i<getNbMatrixBlock();i++)
{
for(uint rp=0;rp<getNbRdrPass(i);rp++)
{
// if resident, fails!!! cannot read!
const CIndexBuffer &pb= getRdrPassPrimitiveBlock(i, rp);
if(pb.isResident())
return false;
numTris+= getRdrPassPrimitiveBlock(i, rp).getNumIndexes()/3;
}
}
indices.resize(numTris*3);
// build indices
uint triIdx= 0;
for(i=0;i<getNbMatrixBlock();i++)
{
for(uint rp=0;rp<getNbRdrPass(i);rp++)
{
const CIndexBuffer &pb= getRdrPassPrimitiveBlock(i, rp);
CIndexBufferRead iba;
pb.lock (iba);
// copy
if (pb.getFormat() == CIndexBuffer::Indices32)
{
memcpy(&indices[triIdx*3], iba.getPtr(), pb.getNumIndexes()*sizeof(uint32));
}
else
{
// std::copy will convert from 16 bits index to 32 bit index
std::copy((uint16 *) iba.getPtr(), ((uint16 *) iba.getPtr()) + pb.getNumIndexes(), &indices[triIdx*3]);
}
// next
triIdx+= pb.getNumIndexes()/3;
}
}
return true;
}
// ***************************************************************************
// ***************************************************************************
// Skinning.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
void CMeshGeom::buildSkin(CMesh::CMeshBuild &m, std::vector<CFaceTmp> &tmpFaces)
{
sint i,j,k;
TBoneMap remainingBones;
list<uint> remainingFaces;
// 0. normalize SkinWeights: for all weights at 0, copy the matrixId from 0th matrix => no random/bad use of matrix.
//================================
for(i=0;i<(sint)m.SkinWeights.size();i++)
{
CMesh::CSkinWeight &sw= m.SkinWeights[i];
// 0th weight must not be 0.
nlassert(sw.Weights[0]!=0);
// Begin at 1, tests all other weights.
for(j=1;j<NL3D_MESH_SKINNING_MAX_MATRIX;j++)
{
// We don't use this entry??
if(sw.Weights[j]==0)
{
// Setup MatrixId so that this vertex do no use more matrix than it really wants.
sw.MatrixId[j]= sw.MatrixId[0];
}
}
}
// 1. build the list of used/remaining bones, in ascending order. (so we use the depth-first topolgy of hierarchy).
//================================
for(i=0;i<(sint)tmpFaces.size();i++)
{
CFaceTmp &face= tmpFaces[i];
for(j=0;j<3;j++)
{
CMesh::CSkinWeight &sw= m.SkinWeights[face.Corner[j].Vertex];
for(k=0;k<NL3D_MESH_SKINNING_MAX_MATRIX;k++)
{
// insert (if not already here) the used bone in the set.
// and insert his refcount. (NB: ctor() init it to 0 :) ).
remainingBones[sw.MatrixId[k]].RefCount++;
}
}
}
// 2. Create the list of un-inserted faces.
//================================
for(i=0;i<(sint)tmpFaces.size();i++)
{
remainingFaces.push_back(i);
}
// 3. Create as many Blocks as necessary.
//================================
// Which bones a face use (up to 12).
vector<uint> boneUse;
boneUse.reserve(NL3D_MESH_SKINNING_MAX_MATRIX*3);
// While still exist faces.
while(!remainingFaces.empty())
{
// create a new matrix block.
_MatrixBlocks.push_back(CMatrixBlock());
CMatrixBlock &matrixBlock= _MatrixBlocks[_MatrixBlocks.size()-1];
matrixBlock.NumMatrix=0;
// a. reset remainingBones as not inserted in the current matrixBlock.
//============================
ItBoneMap itBone;
for(itBone= remainingBones.begin();itBone!=remainingBones.end();itBone++)
{
itBone->second.Inserted= false;
}
// b. while still exist bones, try to insert faces which use them in matrixBlock.
//============================
while(!remainingBones.empty())
{
// get the first bone from the map. (remind: depth-first order).
uint currentBoneId= remainingBones.begin()->first;
// If no more faces in the remainingFace list use this bone, remove it, and continue.
if(remainingBones.begin()->second.RefCount==0)
{
remainingBones.erase(remainingBones.begin());
continue;
}
// this is a marker, to know if a face insertion will occurs.
bool faceAdded= false;
// traverse all faces, trying to insert them in current MatrixBlock processed.
list<uint>::iterator itFace;
for(itFace= remainingFaces.begin(); itFace!=remainingFaces.end();)
{
bool useCurrentBoneId;
uint newBoneAdded;
// i/ Get info on current face.
//-----------------------------
// build which bones this face use.
tmpFaces[*itFace].buildBoneUse(boneUse, m.SkinWeights);
// test if this face use the currentBoneId.
useCurrentBoneId= false;
for(i=0;i<(sint)boneUse.size();i++)
{
// if this face use the currentBoneId
if(boneUse[i]==currentBoneId)
{
useCurrentBoneId= true;
break;
}
}
// compute how many bones that are not in the current matrixblock this face use.
newBoneAdded=0;
for(i=0;i<(sint)boneUse.size();i++)
{
// if this bone is not inserted in the current matrix block, inform it.
if(!remainingBones[boneUse[i]].Inserted)
newBoneAdded++;
}
// ii/ insert/reject face.
//------------------------
// If this face do not add any more bone, we can insert it into the current matrixblock.
// If it use the currentBoneId, and do not explode max count, we allow insert it too in the current matrixblock.
if( newBoneAdded==0 ||
(useCurrentBoneId && newBoneAdded+matrixBlock.NumMatrix < IDriver::MaxModelMatrix) )
{
// Insert this face in the current matrix block
CFaceTmp &face= tmpFaces[*itFace];
// for all vertices of this face.
for(j=0;j<3;j++)
{
CMesh::CSkinWeight &sw= m.SkinWeights[face.Corner[j].Vertex];
// for each corner weight (4)
for(k=0;k<NL3D_MESH_SKINNING_MAX_MATRIX;k++)
{
// get the global boneId this corner weight use.
uint boneId= sw.MatrixId[k];
// get the CBoneTmp this corner weight use.
CBoneTmp &bone= remainingBones[boneId];
// decRef the bone .
bone.RefCount--;
// Is this bone already inserted in the MatrixBlock ?
if( !bone.Inserted )
{
// No, insert it.
bone.Inserted= true;
// link it to the MatrixId in the current matrixBlock.
bone.MatrixIdInMB= matrixBlock.NumMatrix;
// modify the matrixBlock
matrixBlock.MatrixId[matrixBlock.NumMatrix]= boneId;
// increment the number of matrix in the matrixBlock.
matrixBlock.NumMatrix++;
}
// Copy Weight info for this Corner.
// Set what matrix in the current matrix block this corner use.
face.Corner[j].Palette.MatrixId[k]= bone.MatrixIdInMB;
// Set weight.
face.Corner[j].Weights[k]= sw.Weights[k];
}
}
// to Which matrixblock this face is inserted.
face.MatrixBlockId= (sint)_MatrixBlocks.size()-1;
// remove the face from remain face list.
itFace= remainingFaces.erase(itFace);
// inform the algorithm that a face has been added.
faceAdded= true;
}
else
{
// do not append this face to the current matrix block, skip to the next
itFace++;
}
}
// If no faces have been added during this pass, we are blocked, and either the MatrixBlock may be full,
// or there is no more face. So quit this block and process a new one.
if(!faceAdded)
break;
}
}
// NB: at the end of this loop, remainingBones may not be empty(), but all remainingBones should have RefCount==0.
// 4. Re-order matrix use in MatrixBlocks, for minimum matrix change between MatrixBlocks.
//================================
vector<CMatrixBlockRemap> blockRemaps;
blockRemaps.resize(_MatrixBlocks.size());
// For all MatrixBlocks > first, try to "mirror" bones from previous.
for(i=1;i<(sint)_MatrixBlocks.size();i++)
{
CMatrixBlock &mBlock= _MatrixBlocks[i];
CMatrixBlock &mPrevBlock= _MatrixBlocks[i-1];
CMatrixBlockRemap &remap= blockRemaps[i];
// First bkup the bone ids in remap table.
for(j=0;j<(sint)mBlock.NumMatrix;j++)
{
remap.Remap[j]= mBlock.MatrixId[j];
}
// For all ids of this blocks, try to mirror them.
for(j=0;j<(sint)mBlock.NumMatrix;j++)
{
// get the location of this bone in the prev bone.
sint idLoc= mPrevBlock.getMatrixIdLocation(mBlock.MatrixId[j]);
// If not found, or if bigger than current array, fails (cant be mirrored).
// Or if already mirrored.
if(idLoc==-1 || idLoc>=(sint)mBlock.NumMatrix || idLoc==j)
{
// next id.
j++;
}
else
{
// puts me on my mirrored location. and swap with the current one at this mirrored location.
swap(mBlock.MatrixId[j], mBlock.MatrixId[idLoc]);
// mBlock.MatrixId[j] is now a candidate for mirror.
}
}
// Then build the Remap table, to re-order faces matrixId which use this matrix block.
for(j=0;j<(sint)mBlock.NumMatrix;j++)
{
// get the boneid which was at this position j before.
uint boneId= remap.Remap[j];
// search his new position, and store the result in the remap table.
remap.Remap[j]= mBlock.getMatrixIdLocation(boneId);
}
// NB: this matrixBlock is re-ordered. next matrixBlock use this state.
}
// For all faces/corners/weights, remap MatrixIds.
for(i=0;i<(sint)tmpFaces.size();i++)
{
CFaceTmp &face= tmpFaces[i];
// do it but for matrixblock0.
if(face.MatrixBlockId!=0)
{
CMatrixBlockRemap &remap= blockRemaps[face.MatrixBlockId];
// For all corners.
for(j=0;j<3;j++)
{
for(k=0;k<NL3D_MESH_SKINNING_MAX_MATRIX;k++)
{
uint oldId= face.Corner[j].Palette.MatrixId[k];
face.Corner[j].Palette.MatrixId[k]= (uint8)remap.Remap[oldId];
}
}
}
}
}
// ***************************************************************************
void CMeshGeom::CFaceTmp::buildBoneUse(vector<uint> &boneUse, vector<CMesh::CSkinWeight> &skinWeights)
{
boneUse.clear();
// For the 3 corners of the face.
for(sint i=0;i<3;i++)
{
// get the CSkinWeight of this vertex.
CMesh::CSkinWeight &sw= skinWeights[Corner[i].Vertex];
// For all skin weights of this vertex,
for(sint j=0;j<NL3D_MESH_SKINNING_MAX_MATRIX;j++)
{
uint boneId= sw.MatrixId[j];
// insert (if not in the array) this bone.
if( find(boneUse.begin(), boneUse.end(), boneId)==boneUse.end() )
boneUse.push_back(boneId);
}
}
}
// ***************************************************************************
sint CMeshGeom::CMatrixBlock::getMatrixIdLocation(uint32 boneId) const
{
for(uint i=0;i<NumMatrix;i++)
{
if(MatrixId[i]==boneId)
return i;
}
// not found.
return -1;
}
// ***************************************************************************
float CMeshGeom::getNumTriangles (float distance)
{
// Sum of triangles
uint32 triCount=0;
// For each matrix block
uint mbCount=(uint)_MatrixBlocks.size();
for (uint mb=0; mb<mbCount; mb++)
{
CMatrixBlock &block=_MatrixBlocks[mb];
// Count of primitive block
uint pCount=(uint)block.RdrPass.size();
for (uint pb=0; pb<pCount; pb++)
{
// Ref on the primitive block
CRdrPass &pass=block.RdrPass[pb];
// Sum tri
triCount+=pass.PBlock.getNumIndexes ()/3;
}
}
return (float)triCount;
}
// ***************************************************************************
void CMeshGeom::computeBonesId (CSkeletonModel *skeleton)
{
// Already computed ?
if (!_BoneIdComputed)
{
// Get a pointer on the skeleton
nlassert (skeleton);
if (skeleton)
{
// **** For each bones, compute remap
std::vector<uint> remap;
skeleton->remapSkinBones(_BonesName, _BonesId, remap);
// **** Remap matrix blocks
for (uint matrixBlock=0; matrixBlock<_MatrixBlocks.size(); matrixBlock++)
{
// Ref on the matrix block
CMatrixBlock &mb = _MatrixBlocks[matrixBlock];
// For each matrix
uint matrix;
for (matrix=0; matrix<mb.NumMatrix; matrix++)
{
// Get bone id in the skeleton
nlassert (mb.MatrixId[matrix]<remap.size());
mb.MatrixId[matrix] = remap[mb.MatrixId[matrix]];
}
}
// **** Remap ShadowSkin, and compute Bone Sphere
// Prepare Sphere compute
static std::vector<CAABBox> boneBBoxes;
static std::vector<bool> boneBBEmpty;
boneBBoxes.clear();
boneBBEmpty.clear();
boneBBoxes.resize(_BonesId.size());
boneBBEmpty.resize(_BonesId.size(), true);
// For simplicity, use the shadow skin info only, to compute bone sphere
for(uint vert=0;vert<_ShadowSkin.Vertices.size();vert++)
{
CShadowVertex &v= _ShadowSkin.Vertices[vert];
// Check id
uint srcId= v.MatrixId;
nlassert ( srcId < remap.size());
// remap
v.MatrixId= remap[srcId];
// if the boneId is valid (ie found)
if(_BonesId[srcId]>=0)
{
// transform the vertex pos in BoneSpace
CVector p= skeleton->Bones[_BonesId[srcId]].getBoneBase().InvBindPos * v.Vertex;
// extend the bone bbox.
if(boneBBEmpty[srcId])
{
boneBBoxes[srcId].setCenter(p);
boneBBEmpty[srcId]= false;
}
else
{
boneBBoxes[srcId].extend(p);
}
}
}
// Compile spheres
_BonesSphere.resize(_BonesId.size());
for(uint bone=0;bone<_BonesSphere.size();bone++)
{
// If the bone is empty, mark with -1 in the radius.
if(boneBBEmpty[bone])
{
_BonesSphere[bone].Radius= -1;
}
else
{
_BonesSphere[bone].Center= boneBBoxes[bone].getCenter();
_BonesSphere[bone].Radius= boneBBoxes[bone].getRadius();
}
}
// Computed
_BoneIdComputed = true;
}
}
// Already extended ?
if (!_BoneIdExtended)
{
nlassert (skeleton);
if (skeleton)
{
// the total bone Usage of the mesh.
vector<bool> boneUsage;
boneUsage.resize(skeleton->Bones.size(), false);
// for all Bones marked as valid.
uint i;
for(i=0; i<_BonesId.size(); i++)
{
// if not a valid boneId, skip it.
if(_BonesId[i]<0)
continue;
// mark him and his father in boneUsage.
skeleton->flagBoneAndParents(_BonesId[i], boneUsage);
}
// fill _BonesIdExt with bones of _BonesId and their parents.
_BonesIdExt.clear();
for(i=0; i<boneUsage.size();i++)
{
// if the bone is used by the mesh, add it to BoneIdExt.
if(boneUsage[i])
_BonesIdExt.push_back(i);
}
}
// Extended
_BoneIdExtended= true;
}
}
// ***************************************************************************
void CMeshGeom::buildBoneUsageVer3 ()
{
if(_Skinned)
{
// parse all matrixBlocks, couting MaxBoneId used.
uint32 maxBoneId= 0;
// For each matrix block
uint matrixBlock;
for (matrixBlock=0; matrixBlock<_MatrixBlocks.size(); matrixBlock++)
{
CMatrixBlock &mb = _MatrixBlocks[matrixBlock];
// For each matrix
for (uint matrix=0; matrix<mb.NumMatrix; matrix++)
{
maxBoneId= max(mb.MatrixId[matrix], maxBoneId);
}
}
// alloc an array of maxBoneId+1, reset to 0.
std::vector<uint8> boneUsage;
boneUsage.resize(maxBoneId+1, 0);
// reparse all matrixBlocks, counting usage for each bone.
for (matrixBlock=0; matrixBlock<_MatrixBlocks.size(); matrixBlock++)
{
CMatrixBlock &mb = _MatrixBlocks[matrixBlock];
// For each matrix
for (uint matrix=0; matrix<mb.NumMatrix; matrix++)
{
// mark this bone as used.
boneUsage[mb.MatrixId[matrix]]= 1;
}
}
// For each bone used
_BonesId.clear();
for(uint i=0; i<boneUsage.size();i++)
{
// if the bone is used by the mesh, add it to BoneId.
if(boneUsage[i])
_BonesId.push_back(i);
}
// Must also compute _BonesSphere
// Cannot do it easily, deprecated data format => suppose radius 0 sphere
CBSphere sphere(CVector::Null, 0.f);
_BonesSphere.clear();
_BonesSphere.resize(_BonesId.size(), sphere);
}
}
// ***************************************************************************
void CMeshGeom::updateSkeletonUsage(CSkeletonModel *sm, bool increment)
{
// For all Bones used by this mesh.
for(uint i=0; i<_BonesIdExt.size();i++)
{
uint boneId= _BonesIdExt[i];
// Some explicit Error.
if(boneId>=sm->Bones.size())
nlerror(" Skin is incompatible with Skeleton: tries to use bone %d", boneId);
// increment or decrement not Forced, because CMeshGeom use getActiveBoneSkinMatrix().
if(increment)
sm->incBoneUsage(boneId, CSkeletonModel::UsageNormal);
else
sm->decBoneUsage(boneId, CSkeletonModel::UsageNormal);
}
}
// ***************************************************************************
void CMeshGeom::bkupOriginalSkinVertices()
{
nlassert(_Skinned);
// reset
contReset(_OriginalSkinVertices);
contReset(_OriginalSkinNormals);
contReset(_OriginalTGSpace);
// get num of vertices
uint numVertices= _VBuffer.getNumVertices();
CVertexBufferRead vba;
_VBuffer.lock (vba);
// Copy VBuffer content into Original vertices normals.
if(_VBuffer.getVertexFormat() & CVertexBuffer::PositionFlag)
{
// copy vertices from VBuffer. (NB: useless geomorphed vertices are still copied, but doesn't matter).
_OriginalSkinVertices.resize(numVertices);
for(uint i=0; i<numVertices;i++)
{
_OriginalSkinVertices[i]= *vba.getVertexCoordPointer(i);
}
}
if(_VBuffer.getVertexFormat() & CVertexBuffer::NormalFlag)
{
// copy normals from VBuffer. (NB: useless geomorphed normals are still copied, but doesn't matter).
_OriginalSkinNormals.resize(numVertices);
for(uint i=0; i<numVertices;i++)
{
_OriginalSkinNormals[i]= *vba.getNormalCoordPointer(i);
}
}
// is there tangent space added ?
if (_MeshVertexProgram && _MeshVertexProgram->needTangentSpace())
{
// yes, backup it
nlassert(_VBuffer.getNumTexCoordUsed() > 0);
uint tgSpaceStage = _VBuffer.getNumTexCoordUsed() - 1;
_OriginalTGSpace.resize(numVertices);
for(uint i=0; i<numVertices;i++)
{
_OriginalTGSpace[i]= *(CVector*)vba.getTexCoordPointer(i, tgSpaceStage);
}
}
}
// ***************************************************************************
void CMeshGeom::restoreOriginalSkinVertices()
{
nlassert(_Skinned);
// get num of vertices
uint numVertices= _VBuffer.getNumVertices();
CVertexBufferReadWrite vba;
_VBuffer.lock (vba);
// Copy VBuffer content into Original vertices normals.
if(_VBuffer.getVertexFormat() & CVertexBuffer::PositionFlag)
{
// copy vertices from VBuffer. (NB: useless geomorphed vertices are still copied, but doesn't matter).
for(uint i=0; i<numVertices;i++)
{
*vba.getVertexCoordPointer(i)= _OriginalSkinVertices[i];
}
}
if(_VBuffer.getVertexFormat() & CVertexBuffer::NormalFlag)
{
// copy normals from VBuffer. (NB: useless geomorphed normals are still copied, but doesn't matter).
for(uint i=0; i<numVertices;i++)
{
*vba.getNormalCoordPointer(i)= _OriginalSkinNormals[i];
}
}
if (_MeshVertexProgram && _MeshVertexProgram->needTangentSpace())
{
uint numTexCoords = _VBuffer.getNumTexCoordUsed();
nlassert(numTexCoords >= 2);
nlassert(_OriginalTGSpace.size() == numVertices);
// copy tangent space vectors
for(uint i = 0; i < numVertices; ++i)
{
*(CVector*)vba.getTexCoordPointer(i, numTexCoords - 1)= _OriginalTGSpace[i];
}
}
// cleared
_OriginalSkinRestored= true;
}
// ***************************************************************************
// Flags for software vertex skinning.
#define NL3D_SOFTSKIN_VNEEDCOMPUTE 3
#define NL3D_SOFTSKIN_VMUSTCOMPUTE 1
#define NL3D_SOFTSKIN_VCOMPUTED 0
// 3 means "vertex may need compute".
// 1 means "Primitive say vertex must be computed".
// 0 means "vertex is computed".
// ***************************************************************************
void CMeshGeom::applySkin(CSkeletonModel *skeleton)
{
// init.
//===================
if(_OriginalSkinVertices.empty())
return;
// Use correct skinning
TSkinType skinType;
if( _OriginalSkinNormals.empty() )
skinType= SkinPosOnly;
else if( _OriginalTGSpace.empty() )
skinType= SkinWithNormal;
else
skinType= SkinWithTgSpace;
// Get VB src/dst info/ptrs.
uint numVertices= (uint)_OriginalSkinVertices.size();
uint dstStride= _VBuffer.getVertexSize();
// Get dst TgSpace.
uint tgSpaceStage = 0;
if( skinType>= SkinWithTgSpace)
{
nlassert(_VBuffer.getNumTexCoordUsed() > 0);
tgSpaceStage= _VBuffer.getNumTexCoordUsed() - 1;
}
// Mark all vertices flag to not computed.
static vector<uint8> skinFlags;
skinFlags.resize(numVertices);
// reset all flags
memset(&skinFlags[0], NL3D_SOFTSKIN_VNEEDCOMPUTE, numVertices );
CVertexBufferRead vba;
_VBuffer.lock (vba);
// For all MatrixBlocks
//===================
for(uint mb= 0; mb<_MatrixBlocks.size();mb++)
{
// compute matrixes for this block.
static CMatrix3x4 matrixes[IDriver::MaxModelMatrix];
computeSkinMatrixes(skeleton, matrixes, mb==0?NULL:&_MatrixBlocks[mb-1], _MatrixBlocks[mb]);
// check what vertex to skin for this PB.
flagSkinVerticesForMatrixBlock(&skinFlags[0], _MatrixBlocks[mb]);
// Get VB src/dst ptrs.
uint8 *pFlag= &skinFlags[0];
CVector *srcVector= &_OriginalSkinVertices[0];
uint8 *srcPal= (uint8*)vba.getPaletteSkinPointer(0);
uint8 *srcWgt= (uint8*)vba.getWeightPointer(0);
uint8 *dstVector= (uint8*)vba.getVertexCoordPointer(0);
// Normal.
CVector *srcNormal= NULL;
uint8 *dstNormal= NULL;
if(skinType>=SkinWithNormal)
{
srcNormal= &_OriginalSkinNormals[0];
dstNormal= (uint8*)vba.getNormalCoordPointer(0);
}
// TgSpace.
CVector *srcTgSpace= NULL;
uint8 *dstTgSpace= NULL;
if(skinType>=SkinWithTgSpace)
{
srcTgSpace= &_OriginalTGSpace[0];
dstTgSpace= (uint8*)vba.getTexCoordPointer(0, tgSpaceStage);
}
// For all vertices that need to be computed.
uint size= numVertices;
for(;size>0;size--)
{
// If we must compute this vertex.
if(*pFlag==NL3D_SOFTSKIN_VMUSTCOMPUTE)
{
// Flag this vertex as computed.
*pFlag=NL3D_SOFTSKIN_VCOMPUTED;
CPaletteSkin *psPal= (CPaletteSkin*)srcPal;
// checks indices.
nlassert(psPal->MatrixId[0]<IDriver::MaxModelMatrix);
nlassert(psPal->MatrixId[1]<IDriver::MaxModelMatrix);
nlassert(psPal->MatrixId[2]<IDriver::MaxModelMatrix);
nlassert(psPal->MatrixId[3]<IDriver::MaxModelMatrix);
// compute vertex part.
computeSoftwarePointSkinning(matrixes, srcVector, psPal, (float*)srcWgt, (CVector*)dstVector);
// compute normal part.
if(skinType>=SkinWithNormal)
computeSoftwareVectorSkinning(matrixes, srcNormal, psPal, (float*)srcWgt, (CVector*)dstNormal);
// compute tg part.
if(skinType>=SkinWithTgSpace)
computeSoftwareVectorSkinning(matrixes, srcTgSpace, psPal, (float*)srcWgt, (CVector*)dstTgSpace);
}
// inc flags.
pFlag++;
// inc src (all whatever skin type used...)
srcVector++;
srcNormal++;
srcTgSpace++;
// inc paletteSkin and dst (all whatever skin type used...)
srcPal+= dstStride;
srcWgt+= dstStride;
dstVector+= dstStride;
dstNormal+= dstStride;
dstTgSpace+= dstStride;
}
}
// dirt
_OriginalSkinRestored= false;
}
// ***************************************************************************
void CMeshGeom::flagSkinVerticesForMatrixBlock(uint8 *skinFlags, CMatrixBlock &mb)
{
for(uint i=0; i<mb.RdrPass.size(); i++)
{
CIndexBuffer &PB= mb.RdrPass[i].PBlock;
uint nIndex;
// This may be better to flags in 2 pass (first traverse primitives, then test vertices).
// Better sol for BTB..., because number of tests are divided by 6 (for triangles).
// for all prims, indicate which vertex we must compute.
// nothing if not already computed (ie 0), because 0&1==0.
// Lines.
// Tris.
CIndexBufferRead iba;
PB.lock (iba);
nIndex= PB.getNumIndexes();
if (iba.getFormat() == CIndexBuffer::Indices32)
{
uint32 *pIndex= (uint32*)iba.getPtr();
for(;nIndex>0;nIndex--, pIndex++)
skinFlags[*pIndex]&= NL3D_SOFTSKIN_VMUSTCOMPUTE;
}
else
{
uint16 *pIndex= (uint16*)iba.getPtr();
for(;nIndex>0;nIndex--, pIndex++)
skinFlags[*pIndex]&= NL3D_SOFTSKIN_VMUSTCOMPUTE;
}
}
}
// ***************************************************************************
void CMeshGeom::computeSoftwarePointSkinning(CMatrix3x4 *matrixes, CVector *srcVec, CPaletteSkin *srcPal, float *srcWgt, CVector *pDst)
{
CMatrix3x4 *pMat;
// 0th matrix influence.
pMat= matrixes + srcPal->MatrixId[0];
pMat->mulSetPoint(*srcVec, srcWgt[0], *pDst);
// 1th matrix influence.
pMat= matrixes + srcPal->MatrixId[1];
pMat->mulAddPoint(*srcVec, srcWgt[1], *pDst);
// 2th matrix influence.
pMat= matrixes + srcPal->MatrixId[2];
pMat->mulAddPoint(*srcVec, srcWgt[2], *pDst);
// 3th matrix influence.
pMat= matrixes + srcPal->MatrixId[3];
pMat->mulAddPoint(*srcVec, srcWgt[3], *pDst);
}
// ***************************************************************************
void CMeshGeom::computeSoftwareVectorSkinning(CMatrix3x4 *matrixes, CVector *srcVec, CPaletteSkin *srcPal, float *srcWgt, CVector *pDst)
{
CMatrix3x4 *pMat;
// 0th matrix influence.
pMat= matrixes + srcPal->MatrixId[0];
pMat->mulSetVector(*srcVec, srcWgt[0], *pDst);
// 1th matrix influence.
pMat= matrixes + srcPal->MatrixId[1];
pMat->mulAddVector(*srcVec, srcWgt[1], *pDst);
// 2th matrix influence.
pMat= matrixes + srcPal->MatrixId[2];
pMat->mulAddVector(*srcVec, srcWgt[2], *pDst);
// 3th matrix influence.
pMat= matrixes + srcPal->MatrixId[3];
pMat->mulAddVector(*srcVec, srcWgt[3], *pDst);
}
// ***************************************************************************
void CMeshGeom::computeSkinMatrixes(CSkeletonModel *skeleton, CMatrix3x4 *matrixes, CMatrixBlock *prevBlock, CMatrixBlock &mBlock)
{
// For all matrix of this mBlock.
for(uint idMat=0;idMat<mBlock.NumMatrix;idMat++)
{
uint curBoneId= mBlock.MatrixId[idMat];
// If same matrix binded as previous block, no need to bind!!
if(prevBlock && idMat<prevBlock->NumMatrix && prevBlock->MatrixId[idMat]== curBoneId)
continue;
// Else, we must setup the matrix
matrixes[idMat].set(skeleton->getActiveBoneSkinMatrix(curBoneId));
}
}
// ***************************************************************************
void CMeshGeom::profileSceneRender(CRenderTrav *rdrTrav, CTransformShape *trans, float polygonCount, uint32 rdrFlags)
{
// get the mesh instance.
CMeshBaseInstance *mi= safe_cast<CMeshBaseInstance*>(trans);
// For all _MatrixBlocks
uint triCount= 0;
for(uint mb=0;mb<_MatrixBlocks.size();mb++)
{
CMatrixBlock &mBlock= _MatrixBlocks[mb];
// Profile all pass.
for (uint i=0;i<mBlock.RdrPass.size();i++)
{
CRdrPass &rdrPass= mBlock.RdrPass[i];
// Profile with the Materials of the MeshInstance.
if ( ( (mi->Materials[rdrPass.MaterialId].getBlend() == false) && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) ||
( (mi->Materials[rdrPass.MaterialId].getBlend() == true) && (rdrFlags & IMeshGeom::RenderTransparentMaterial) ) )
{
triCount+= rdrPass.PBlock.getNumIndexes()/3;
}
}
}
// Profile
if(triCount)
{
// tri per VBFormat
rdrTrav->Scene->incrementProfileTriVBFormat(rdrTrav->Scene->BenchRes.MeshProfileTriVBFormat,
_VBuffer.getVertexFormat(), triCount);
// VBHard
if(_VBuffer.getPreferredMemory()!=CVertexBuffer::RAMPreferred)
rdrTrav->Scene->BenchRes.NumMeshVBufferHard++;
else
rdrTrav->Scene->BenchRes.NumMeshVBufferStd++;
// rendered in BlockRendering, only if not transparent pass (known it if RenderTransparentMaterial is set)
if(supportMeshBlockRendering() && (rdrFlags & IMeshGeom::RenderTransparentMaterial)==0 )
{
if(isMeshInVBHeap())
{
rdrTrav->Scene->BenchRes.NumMeshRdrBlockWithVBHeap++;
rdrTrav->Scene->BenchRes.NumMeshTriRdrBlockWithVBHeap+= triCount;
}
else
{
rdrTrav->Scene->BenchRes.NumMeshRdrBlock++;
rdrTrav->Scene->BenchRes.NumMeshTriRdrBlock+= triCount;
}
}
else
{
rdrTrav->Scene->BenchRes.NumMeshRdrNormal++;
rdrTrav->Scene->BenchRes.NumMeshTriRdrNormal+= triCount;
}
}
}
// ***************************************************************************
bool CMeshGeom::intersectSkin(CTransformShape *mi, const CMatrix &toRaySpace, float &dist2D, float &distZ, bool computeDist2D)
{
// for Mesh, Use the Shadow Skinning (simple version).
// get skeleton
if(!mi || _OriginalSkinVertices.empty())
return false;
CSkeletonModel *skeleton= mi->getSkeletonModel();
if(!skeleton)
return false;
// Compute skinning with all matrix this Mesh use. (the shadow geometry cannot use other Matrix than the mesh use).
static std::vector<uint32> matInfs;
matInfs.resize(_BonesId.size());
for(uint i=0;i<matInfs.size();i++)
{
// treat any "missing bone" as the root one.
matInfs[i]= max(_BonesId[i], (sint32)0);
}
return _ShadowSkin.getRayIntersection(toRaySpace, *skeleton, matInfs, dist2D, distZ, computeDist2D);
}
// ***************************************************************************
void CMeshGeom::buildShadowSkin()
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
// reset
contReset(_ShadowSkin.Vertices);
contReset(_ShadowSkin.Triangles);
nlassert(_Skinned && (_VBuffer.getVertexFormat() & CVertexBuffer::PaletteSkinFlag)
&& (_VBuffer.getVertexFormat() & CVertexBuffer::PositionFlag) );
// *** Copy VBuffer content
{
// get num of vertices
uint numVertices= _VBuffer.getNumVertices();
// lock VB
CVertexBufferRead vba;
_VBuffer.lock (vba);
uint8 *srcPal= (uint8*)vba.getPaletteSkinPointer(0);
uint8 *srcVert= (uint8*)vba.getVertexCoordPointer(0);
uint32 srcVertSize= _VBuffer.getVertexSize();
// copy vertices from VBuffer
_ShadowSkin.Vertices.resize(numVertices);
for(uint i=0; i<numVertices;i++)
{
// Copy Vertex
_ShadowSkin.Vertices[i].Vertex= *((CVector*)srcVert);
// Suppose the 0 matrix inf is the highest (we are at least sure it is not 0)
// And SkinWeight Export show the 0th is the highest one...
_ShadowSkin.Vertices[i].MatrixId= ((CPaletteSkin*)srcPal)->MatrixId[0];
// Next
srcVert+= srcVertSize;
srcPal+= srcVertSize;
}
}
// But _ShadowSkin.Vertices[i].MatrixId is incorrect, since < IDriver::MaxModelMatrix
// *** Count number of triangles, and get start index of each matrix block in the final Tri list
uint numIndices= 0;
uint mb;
// can't be static cause of ThreadSafe
vector<pair<uint32,uint32> > mbIndexRange;
mbIndexRange.resize(_MatrixBlocks.size());
for(mb=0;mb<_MatrixBlocks.size();mb++)
{
// this matrix block start here
mbIndexRange[mb].first= numIndices;
// count tris rendered for this matrix block
const CMatrixBlock &mBlock= _MatrixBlocks[mb];
for(uint rp=0;rp<mBlock.RdrPass.size();rp++)
{
const CRdrPass &rPass= mBlock.RdrPass[rp];
numIndices+= rPass.PBlock.getNumIndexes();
}
// this matrix block end here
mbIndexRange[mb].second= numIndices;
}
// *** Fill Triangles
nlverify(retrieveTriangles(_ShadowSkin.Triangles));
nlassert(numIndices==_ShadowSkin.Triangles.size());
// *** Reindex correctly MatrixId, (ie unpack matrix blocks)
// can't be static cause of ThreadSafe
vector<bool> vertReIndexed;
vertReIndexed.resize(_ShadowSkin.Vertices.size(), false);
// for all matrix blocks
for(mb=0;mb<mbIndexRange.size();mb++)
{
const CMatrixBlock &mBlock= _MatrixBlocks[mb];
uint iStart= mbIndexRange[mb].first;
uint iEnd= mbIndexRange[mb].second;
nlassert(iStart <= iEnd && iEnd<=_ShadowSkin.Triangles.size());
// For all indices in this range
uint32 *pIndex= &_ShadowSkin.Triangles[iStart];
uint nbIndex= iEnd - iStart;
for(;nbIndex>0;--nbIndex, pIndex++)
{
uint index= *pIndex;
// if not already reindexed
if(!vertReIndexed[index])
{
vertReIndexed[index]= true;
// reindex
uint matId= _ShadowSkin.Vertices[index].MatrixId;
nlassert(matId<mBlock.NumMatrix);
_ShadowSkin.Vertices[index].MatrixId= mBlock.MatrixId[matId];
}
}
}
}
// ***************************************************************************
// ***************************************************************************
// Mesh Block Render Interface
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
bool CMeshGeom::supportMeshBlockRendering () const
{
return _SupportMBRFlags!=0;
}
// ***************************************************************************
bool CMeshGeom::sortPerMaterial() const
{
return (_SupportMBRFlags & MBRSortPerMaterial)!=0;
}
// ***************************************************************************
uint CMeshGeom::getNumRdrPassesForMesh() const
{
return (uint)_MatrixBlocks[0].RdrPass.size();
}
// ***************************************************************************
uint CMeshGeom::getNumRdrPassesForInstance(CMeshBaseInstance *inst) const
{
return (uint)_MatrixBlocks[0].RdrPass.size();
}
// ***************************************************************************
void CMeshGeom::beginMesh(CMeshGeomRenderContext &rdrCtx)
{
if(rdrCtx.RenderThroughVBHeap)
{
// Don't setup VB in this case, since use the VBHeap setuped one.
// NB: no VertexProgram test since VBHeap not possible with it...
nlassert( (_SupportMBRFlags & MBRCurrentUseVP)==0 );
}
else
{
// use MeshVertexProgram effect?
if( _MeshVertexProgram != NULL && _MeshVertexProgram->isMBRVpOk(rdrCtx.Driver) )
{
// Ok will use it.
_SupportMBRFlags|= MBRCurrentUseVP;
// Before VB activation
_MeshVertexProgram->beginMBRMesh(rdrCtx.Driver, rdrCtx.Scene );
}
// active VB. SoftwareSkinning: reset flags for skinning.
rdrCtx.Driver->activeVertexBuffer(_VBuffer);
}
}
// ***************************************************************************
void CMeshGeom::activeInstance(CMeshGeomRenderContext &rdrCtx, CMeshBaseInstance *inst, float polygonCount, void *vbDst)
{
// setup instance matrix
rdrCtx.Driver->setupModelMatrix(inst->getWorldMatrix());
// setupLighting.
inst->changeLightSetup(rdrCtx.RenderTrav);
// MeshVertexProgram ?
if( _SupportMBRFlags & MBRCurrentUseVP )
{
CMatrix invertedObjectMatrix;
invertedObjectMatrix = inst->getWorldMatrix().inverted();
_MeshVertexProgram->beginMBRInstance(rdrCtx.Driver, rdrCtx.Scene, inst, invertedObjectMatrix);
}
}
// ***************************************************************************
void CMeshGeom::renderPass(CMeshGeomRenderContext &rdrCtx, CMeshBaseInstance *mi, float polygonCount, uint rdrPassId)
{
CMatrixBlock &mBlock= _MatrixBlocks[0];
CRdrPass &rdrPass= mBlock.RdrPass[rdrPassId];
// Render with the Materials of the MeshInstance, only if not blended.
if( ( (mi->Materials[rdrPass.MaterialId].getBlend() == false) ) )
{
CMaterial &material= mi->Materials[rdrPass.MaterialId];
// MeshVertexProgram ?
if( _SupportMBRFlags & MBRCurrentUseVP )
{
rdrCtx.RenderTrav->changeVPLightSetupMaterial(material, false);
}
if(rdrCtx.RenderThroughVBHeap)
{
// render shifted primitives
rdrCtx.Driver->activeIndexBuffer(rdrPass.VBHeapPBlock);
rdrCtx.Driver->renderTriangles(material, 0, rdrPass.VBHeapPBlock.getNumIndexes()/3);
}
else
{
// render primitives
rdrCtx.Driver->activeIndexBuffer(rdrPass.PBlock);
rdrCtx.Driver->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3);
}
}
}
// ***************************************************************************
void CMeshGeom::endMesh(CMeshGeomRenderContext &rdrCtx)
{
// MeshVertexProgram ?
if( _SupportMBRFlags & MBRCurrentUseVP )
{
// End Mesh
_MeshVertexProgram->endMBRMesh( rdrCtx.Driver );
// and remove Current Flag.
_SupportMBRFlags&= ~MBRCurrentUseVP;
}
}
// ***************************************************************************
bool CMeshGeom::getVBHeapInfo(uint &vertexFormat, uint &numVertices)
{
// CMeshGeom support VBHeap rendering, assuming supportMeshBlockRendering is true.
if( _SupportMBRFlags )
/* Yoyo: If VertexProgram, DON'T SUPPORT!! because VB need to be activated AFTER meshVP activation
NB: still possible with complex code to do it (sort per VP type (with or not)...), but tests in Ryzom
shows that VBHeap is not really important (not so much different shapes...)
*/
if( _MeshVertexProgram==NULL )
{
vertexFormat= _VBuffer.getVertexFormat();
numVertices= _VBuffer.getNumVertices();
return true;
}
return false;
}
// ***************************************************************************
void CMeshGeom::computeMeshVBHeap(void *dst, uint indexStart)
{
// Fill dst with Buffer content.
CVertexBufferRead vba;
_VBuffer.lock (vba);
memcpy(dst, vba.getVertexCoordPointer(), _VBuffer.getNumVertices()*_VBuffer.getVertexSize() );
// NB: only 1 MB is possible ...
nlassert(_MatrixBlocks.size()==1);
CMatrixBlock &mBlock= _MatrixBlocks[0];
// For all rdrPass.
for(uint i=0;i<mBlock.RdrPass.size();i++)
{
// shift the PB
CIndexBuffer &srcPb= mBlock.RdrPass[i].PBlock;
CIndexBuffer &dstPb= mBlock.RdrPass[i].VBHeapPBlock;
uint j;
// Tris.
dstPb.setNumIndexes(srcPb.getNumIndexes());
CIndexBufferRead ibaRead;
srcPb.lock (ibaRead);
CIndexBufferReadWrite ibaWrite;
dstPb.lock (ibaWrite);
// nico : apparently not used, so don't manage 16 bits index here
nlassert(ibaRead.getFormat() == CIndexBuffer::Indices32);
nlassert(ibaWrite.getFormat() == CIndexBuffer::Indices32);
const uint32 *srcTriPtr= (const uint32 *) ibaRead.getPtr();
uint32 *dstTriPtr= (uint32 *) ibaWrite.getPtr();
for(j=0; j<dstPb.getNumIndexes();j++)
{
dstTriPtr[j]= srcTriPtr[j]+indexStart;
}
}
}
// ***************************************************************************
// ***************************************************************************
// CMeshBuild components.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CMesh::CCorner::CCorner()
{
sint i;
Vertex= 0;
Normal= CVector::Null;
for(i=0;i<CVertexBuffer::MaxStage;i++)
{
Uvws[i]= CUVW(0, 0, 0);
}
Color.set(255,255,255,255);
Specular.set(0,0,0,0);
}
// ***************************************************************************
void CMesh::CCorner::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
nlassert(0); // not used
f.serial(Vertex);
f.serial(Normal);
for(int i=0;i<CVertexBuffer::MaxStage;++i) f.serial(Uvws[i]);
f.serial(Color);
f.serial(Specular);
}
// ***************************************************************************
void CMesh::CFace::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
for(int i=0;i<3;++i)
f.serial(Corner[i]);
f.serial(MaterialId);
f.serial(SmoothGroup);
}
// ***************************************************************************
void CMesh::CSkinWeight::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
for(int i=0;i<NL3D_MESH_SKINNING_MAX_MATRIX;++i)
{
f.serial(MatrixId[i]);
f.serial(Weights[i]);
}
}
// ***************************************************************************
/* Serialization is not used.
void CMesh::CMeshBuild::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
sint ver= f.serialVersion(0);
// Serial mesh base (material info).
CMeshBaseBuild::serial(f);
// Serial Geometry.
f.serial( VertexFlags );
f.serialCont( Vertices );
f.serialCont( SkinWeights );
f.serialCont( Faces );
}*/
// ************************************
CMesh::CMeshBuild::CMeshBuild()
{
for (uint k = 0; k < CVertexBuffer::MaxStage; ++k)
{
NumCoords[k] = 2;
UVRouting[k] = k;
}
}
// ***************************************************************************
// ***************************************************************************
// CMesh.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CMesh::CMesh()
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
// create the MeshGeom
_MeshGeom= new CMeshGeom;
}
// ***************************************************************************
CMesh::~CMesh()
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
// delete the MeshGeom
delete _MeshGeom;
}
// ***************************************************************************
CMesh::CMesh(const CMesh &mesh) : CMeshBase()
{
// create the MeshGeom
_MeshGeom= new CMeshGeom(*mesh._MeshGeom);
}
// ***************************************************************************
CMesh &CMesh::operator=(const CMesh &mesh)
{
// Copy CMeshBase part
(CMeshBase&)*this= (CMeshBase&)mesh;
// copy content of meshGeom.
*_MeshGeom= *mesh._MeshGeom;
return *this;
}
// ***************************************************************************
void CMesh::build (CMeshBase::CMeshBaseBuild &mbase, CMeshBuild &m)
{
/// copy MeshBase info: materials ....
CMeshBase::buildMeshBase (mbase);
// build the geometry.
_MeshGeom->build (m, (uint)mbase.Materials.size());
// compile some stuff
compileRunTime();
}
// ***************************************************************************
void CMesh::optimizeMaterialUsage(std::vector<sint> &remap)
{
// For each material, count usage.
vector<bool> materialUsed;
materialUsed.resize(CMeshBase::_Materials.size(), false);
for(uint mb=0;mb<getNbMatrixBlock();mb++)
{
for(uint rp=0;rp<getNbRdrPass(mb);rp++)
{
uint matId= getRdrPassMaterial(mb, rp);
// flag as used.
materialUsed[matId]= true;
}
}
// Apply it to meshBase
CMeshBase::applyMaterialUsageOptim(materialUsed, remap);
// Apply lut to meshGeom.
_MeshGeom->applyMaterialRemap(remap);
}
// ***************************************************************************
void CMesh::setBlendShapes(std::vector<CBlendShape>&bs)
{
_MeshGeom->setBlendShapes (bs);
}
// ***************************************************************************
void CMesh::build(CMeshBase::CMeshBaseBuild &mbuild, CMeshGeom &meshGeom)
{
/// copy MeshBase info: materials ....
CMeshBase::buildMeshBase(mbuild);
// build the geometry.
*_MeshGeom= meshGeom;
// compile some stuff
compileRunTime();
}
// ***************************************************************************
CTransformShape *CMesh::createInstance(CScene &scene)
{
// Create a CMeshInstance, an instance of a mesh.
//===============================================
CMeshInstance *mi= (CMeshInstance*)scene.createModel(NL3D::MeshInstanceId);
mi->Shape= this;
// instanciate the material part of the Mesh, ie the CMeshBase.
CMeshBase::instanciateMeshBase(mi, &scene);
// do some instance init for MeshGeom
_MeshGeom->initInstance(mi);
// init render Filter
mi->initRenderFilterType();
return mi;
}
// ***************************************************************************
bool CMesh::clip(const std::vector<CPlane> &pyramid, const CMatrix &worldMatrix)
{
return _MeshGeom->clip(pyramid, worldMatrix);
}
// ***************************************************************************
void CMesh::render(IDriver *drv, CTransformShape *trans, bool passOpaque)
{
// 0 or 0xFFFFFFFF
uint32 mask= (0-(uint32)passOpaque);
uint32 rdrFlags;
// select rdrFlags, without ifs.
rdrFlags= mask & (IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderPassOpaque);
rdrFlags|= ~mask & (IMeshGeom::RenderTransparentMaterial);
// render the mesh
_MeshGeom->render(drv, trans, 0, rdrFlags, 1);
}
// ***************************************************************************
void CMesh::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
/*
Version 6:
- cut in serialisation, because of:
- bad ITexture serialisation (with no version....) => must cut. (see CMeshBase serial).
- because of this and to simplify, make a cut too in CMesh serialisation.
NB : all old version code is dropped.
*/
sint ver= f.serialVersion(6);
if(ver<6)
throw NLMISC::EStream(f, "Mesh in Stream is too old (Mesh version < 6)");
// serial Materials infos contained in CMeshBase.
CMeshBase::serialMeshBase(f);
// serial geometry.
_MeshGeom->serial(f);
// if reading, compile some stuff
if(f.isReading())
compileRunTime();
}
// ***************************************************************************
const NLMISC::CAABBoxExt& CMesh::getBoundingBox() const
{
return _MeshGeom->getBoundingBox();
}
// ***************************************************************************
const CVertexBuffer &CMesh::getVertexBuffer() const
{
return _MeshGeom->getVertexBuffer() ;
}
// ***************************************************************************
uint CMesh::getNbMatrixBlock() const
{
return _MeshGeom->getNbMatrixBlock();
}
// ***************************************************************************
uint CMesh::getNbRdrPass(uint matrixBlockIndex) const
{
return _MeshGeom->getNbRdrPass(matrixBlockIndex) ;
}
// ***************************************************************************
const CIndexBuffer &CMesh::getRdrPassPrimitiveBlock(uint matrixBlockIndex, uint renderingPassIndex) const
{
return _MeshGeom->getRdrPassPrimitiveBlock(matrixBlockIndex, renderingPassIndex) ;
}
// ***************************************************************************
uint32 CMesh::getRdrPassMaterial(uint matrixBlockIndex, uint renderingPassIndex) const
{
return _MeshGeom->getRdrPassMaterial(matrixBlockIndex, renderingPassIndex) ;
}
// ***************************************************************************
float CMesh::getNumTriangles (float distance)
{
// A CMesh do not degrad himself, so return 0, to not be taken into account.
return 0;
}
// ***************************************************************************
const CMeshGeom& CMesh::getMeshGeom () const
{
return *_MeshGeom;
}
// ***************************************************************************
void CMesh::computeBonesId (CSkeletonModel *skeleton)
{
nlassert (_MeshGeom);
_MeshGeom->computeBonesId (skeleton);
}
// ***************************************************************************
void CMesh::updateSkeletonUsage(CSkeletonModel *sm, bool increment)
{
nlassert (_MeshGeom);
_MeshGeom->updateSkeletonUsage(sm, increment);
}
// ***************************************************************************
IMeshGeom *CMesh::supportMeshBlockRendering (CTransformShape *trans, float &polygonCount ) const
{
// Ok if meshGeom is ok.
if(_MeshGeom->supportMeshBlockRendering())
{
polygonCount= 0;
return _MeshGeom;
}
else
return NULL;
}
// ***************************************************************************
void CMesh::profileSceneRender(CRenderTrav *rdrTrav, CTransformShape *trans, bool passOpaque)
{
// 0 or 0xFFFFFFFF
uint32 mask= (0-(uint32)passOpaque);
uint32 rdrFlags;
// select rdrFlags, without ifs.
rdrFlags= mask & (IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderPassOpaque);
rdrFlags|= ~mask & (IMeshGeom::RenderTransparentMaterial);
// render the mesh
_MeshGeom->profileSceneRender(rdrTrav, trans, 0, rdrFlags);
}
// ***************************************************************************
void CMesh::compileRunTime()
{
/* ***********************************************
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
* It can be loaded/called through CAsyncFileManager for instance
* ***********************************************/
// **** try to build a Visual Collision Mesh
// clear first
if(_VisualCollisionMesh)
{
delete _VisualCollisionMesh;
_VisualCollisionMesh= NULL;
}
// build only if wanted
if( (_CollisionMeshGeneration==AutoCameraCol && !_LightInfos.empty()) ||
_CollisionMeshGeneration==ForceCameraCol )
{
vector<CVector> vertices;
vector<uint32> indices;
if(_MeshGeom->retrieveVertices(vertices) && _MeshGeom->retrieveTriangles(indices))
{
// ok, can build!
_VisualCollisionMesh= new CVisualCollisionMesh;
// if fails to build cause of too many vertices/indices for instance
if( !_VisualCollisionMesh->build(vertices, indices,const_cast<CVertexBuffer&>(_MeshGeom->getVertexBuffer())) )
{
// delete
delete _VisualCollisionMesh;
_VisualCollisionMesh= NULL;
}
}
}
}
// ***************************************************************************
void CMesh::buildSystemGeometry()
{
// clear any
_SystemGeometry.clear();
// don't build a system copy if skinned. In this case, ray intersection is done through CSkeletonModel
// and intersectSkin() scheme
if(_MeshGeom->isSkinned())
return;
// retrieve geometry (if VB/IB not resident)
if( !_MeshGeom->retrieveVertices(_SystemGeometry.Vertices) ||
!_MeshGeom->retrieveTriangles(_SystemGeometry.Triangles))
{
_SystemGeometry.clear();
}
// TestYoyo
/*static uint32 totalMem= 0;
totalMem+= _SystemGeometry.Vertices.size()*sizeof(CVector);
totalMem+= _SystemGeometry.Triangles.size()*sizeof(uint32);
nlinfo("CMesh: TotalMem: %d", totalMem);*/
}
} // NL3D