// 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/misc/bsphere.h"
#include "nel/misc/system_info.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/misc/fast_mem.h"
#include "nel/3d/mesh_mrm_skinned.h"
#include "nel/3d/mrm_builder.h"
#include "nel/3d/mrm_parameters.h"
#include "nel/3d/mesh_mrm_skinned_instance.h"
#include "nel/3d/scene.h"
#include "nel/3d/skeleton_model.h"
#include "nel/3d/stripifier.h"
#include "nel/3d/mesh_blender.h"
#include "nel/3d/render_trav.h"
#include "nel/misc/fast_floor.h"
#include "nel/3d/raw_skinned.h"
#include "nel/3d/shifted_triangle_cache.h"
#include "nel/3d/texture_file.h"
#include "nel/3d/matrix_3x4.h"
using namespace NLMISC;
using namespace std;
namespace NL3D
{
H_AUTO_DECL( NL3D_MeshMRMGeom_RenderShadow )
// ***************************************************************************
// ***************************************************************************
// CMeshMRMSkinnedGeom::CLod
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
void CMeshMRMSkinnedGeom::CLod::serial(NLMISC::IStream &f)
{
/*
Version 0:
- base vdrsion.
*/
f.serialVersion(0);
uint i;
f.serial(NWedges);
f.serialCont(RdrPass);
f.serialCont(Geomorphs);
f.serialCont(MatrixInfluences);
// Serial array of InfluencedVertices. NB: code written so far for NL3D_MESH_SKINNING_MAX_MATRIX==4 only.
nlassert(NL3D_MESH_SKINNING_MAX_MATRIX==4);
for(i= 0; i &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;
}
// ***************************************************************************
CMeshMRMSkinnedGeom::CMeshMRMSkinnedGeom()
{
_BoneIdComputed = false;
_BoneIdExtended = false;
_PreciseClipping= false;
_MeshDataId= 0;
_SupportShadowSkinGrouping= false;
}
// ***************************************************************************
CMeshMRMSkinnedGeom::~CMeshMRMSkinnedGeom()
{
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::changeMRMDistanceSetup(float distanceFinest, float distanceMiddle, float distanceCoarsest)
{
// check input.
if(distanceFinest<0) return;
if(distanceMiddle<=distanceFinest) return;
if(distanceCoarsest<=distanceMiddle) return;
// Change.
_LevelDetail.DistanceFinest= distanceFinest;
_LevelDetail.DistanceMiddle= distanceMiddle;
_LevelDetail.DistanceCoarsest= distanceCoarsest;
// compile
_LevelDetail.compileDistanceSetup();
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::build(CMesh::CMeshBuild &m,
uint numMaxMaterial, const CMRMParameters ¶ms)
{
// Empty geometry?
if(m.Vertices.size()==0 || m.Faces.size()==0)
{
_VBufferFinal.clear();
_Lods.clear();
_BBox.setCenter(CVector::Null);
_BBox.setSize(CVector::Null);
return;
}
nlassert(numMaxMaterial>0);
/// 0. First, make bbox.
//======================
// NB: this is equivalent as building BBox from MRM VBuffer, because CMRMBuilder create new vertices
// which are just interpolation of original vertices.
_BBox= makeBBox(m.Vertices);
/// 1. Launch the MRM build process.
//================================================
CMRMBuilder mrmBuilder;
CMeshBuildMRM meshBuildMRM;
std::vector bsList;
mrmBuilder.compileMRM(m, bsList, params, meshBuildMRM, numMaxMaterial);
nlassert (meshBuildMRM.Skinned);
// Then build the packed vertex buffer
//================================================
_VBufferFinal.build (meshBuildMRM.VBuffer, meshBuildMRM.SkinWeights);
_Lods= meshBuildMRM.Lods;
// Compute degradation control.
//================================================
_LevelDetail.DistanceFinest= meshBuildMRM.DistanceFinest;
_LevelDetail.DistanceMiddle= meshBuildMRM.DistanceMiddle;
_LevelDetail.DistanceCoarsest= meshBuildMRM.DistanceCoarsest;
nlassert(_LevelDetail.DistanceFinest>=0);
nlassert(_LevelDetail.DistanceMiddle > _LevelDetail.DistanceFinest);
nlassert(_LevelDetail.DistanceCoarsest > _LevelDetail.DistanceMiddle);
// Compute OODistDelta and DistancePow
_LevelDetail.compileDistanceSetup();
// For load balancing.
//================================================
// compute Max Face Used
_LevelDetail.MaxFaceUsed= 0;
_LevelDetail.MinFaceUsed= 0;
// Count of primitive block
if(_Lods.size()>0)
{
uint pb;
// Compute MinFaces.
CLod &firstLod= _Lods[0];
for (pb=0; pb remap;
// Current bone
uint currentBone = 0;
// Reserve memory
_BonesName.reserve (m.BonesNames.size());
// For each vertices
uint vert;
CPackedVertexBuffer::CPackedVertex *vertices = _VBufferFinal.getPackedVertices();
for (vert=0; vert<_VBufferFinal.getNumVertices(); vert++)
{
// Found one ?
bool found=false;
// For each weight
uint weight;
for (weight=0; weight0)||(weight==0))
{
// Look for it
std::map::iterator ite = remap.find (vertices[vert].Matrices[weight]);
// Find ?
if (ite == remap.end())
{
// Insert it
remap.insert (std::map::value_type (vertices[vert].Matrices[weight], currentBone));
// Check the id
nlassert (vertices[vert].Matrices[weight]second;
}
// Found one
found = true;
}
}
// Found one ?
nlassert (found);
}
// Remap the vertex influence by lods
uint lod;
for (lod=0; lod<_Lods.size(); lod++)
{
// For each matrix used
uint matrix;
for (matrix=0; matrix<_Lods[lod].MatrixInfluences.size(); matrix++)
{
// Remap
std::map::iterator ite = remap.find (_Lods[lod].MatrixInfluences[matrix]);
// Not find ?
if (ite == remap.end())
{
// Remove it
_Lods[lod].MatrixInfluences.erase (_Lods[lod].MatrixInfluences.begin()+matrix);
matrix--;
continue;
}
// Remap
_Lods[lod].MatrixInfluences[matrix] = ite->second;
}
}
// Misc.
//===================
// Some runtime not serialized compilation
compileRunTime();
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::applyMaterialRemap(const std::vector &remap)
{
for(uint lod=0;lod=0);
matId= remap[matId];
}
}
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::applyGeomorph(std::vector &geoms, float alphaLod)
{
applyGeomorphWithVBHardPtr(geoms, alphaLod);
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::applyGeomorphWithVBHardPtr(std::vector &geoms, float alphaLod)
{
// no geomorphs? quit.
if(geoms.size()==0)
return;
clamp(alphaLod, 0.f, 1.f);
sint a= (uint)floor((256.f*alphaLod)+0.5f);
sint a1= 256 - a;
// info from VBuffer.
uint8 *vertexPtr= (uint8*)_VBufferFinal.getPackedVertices();
sint32 vertexSize= sizeof(CPackedVertexBuffer::CPackedVertex);
// use a faster method
applyGeomorphPosNormalUV0Int(geoms, vertexPtr, vertexPtr, vertexSize, a, a1);
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::applyGeomorphPosNormalUV0(std::vector &geoms, uint8 *vertexPtr, uint8 *vertexDestPtr, sint32 vertexSize, float a, float a1)
{
nlassert(vertexSize==32);
// For all geomorphs.
uint nGeoms= (uint)geoms.size();
CMRMWedgeGeom *ptrGeom= &(geoms[0]);
uint8 *destPtr= vertexDestPtr;
for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize )
{
// Consider the Pos/Normal/UV as an array of 8 float to interpolate.
float *start= (float*)(vertexPtr + (ptrGeom->Start<<5));
float *end= (float*)(vertexPtr + (ptrGeom->End<<5));
float *dst= (float*)(destPtr);
// unrolled
dst[0]= start[0] * a + end[0]* a1;
dst[1]= start[1] * a + end[1]* a1;
dst[2]= start[2] * a + end[2]* a1;
dst[3]= start[3] * a + end[3]* a1;
dst[4]= start[4] * a + end[4]* a1;
dst[5]= start[5] * a + end[5]* a1;
dst[6]= start[6] * a + end[6]* a1;
dst[7]= start[7] * a + end[7]* a1;
}
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::applyGeomorphPosNormalUV0Int(std::vector &geoms, uint8 *vertexPtr, uint8 *vertexDestPtr, sint32 vertexSize, sint a, sint a1)
{
// For all geomorphs.
uint nGeoms= (uint)geoms.size();
CMRMWedgeGeom *ptrGeom= &(geoms[0]);
uint8 *destPtr= vertexDestPtr;
for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize )
{
// Consider the Pos/Normal/UV as an array of 8 float to interpolate.
sint16 *start= (sint16*)(vertexPtr + (ptrGeom->Start*vertexSize));
sint16 *end= (sint16*)(vertexPtr + (ptrGeom->End*vertexSize));
sint16 *dst= (sint16*)(destPtr);
/* Hulud
* This is slow but, we don't care because this method is called for debug purpose only (watch skin without skinning)
*/
// unrolled
dst[0]= (sint16)(((sint)(start[0]) * a + (sint)(end[0]) * a1)>>8);
dst[1]= (sint16)(((sint)(start[1]) * a + (sint)(end[1]) * a1)>>8);
dst[2]= (sint16)(((sint)(start[2]) * a + (sint)(end[2]) * a1)>>8);
dst[3]= (sint16)(((sint)(start[3]) * a + (sint)(end[3]) * a1)>>8);
dst[4]= (sint16)(((sint)(start[4]) * a + (sint)(end[4]) * a1)>>8);
dst[5]= (sint16)(((sint)(start[5]) * a + (sint)(end[5]) * a1)>>8);
dst[6]= (sint16)(((sint)(start[6]) * a + (sint)(end[6]) * a1)>>8);
dst[7]= (sint16)(((sint)(start[7]) * a + (sint)(end[7]) * a1)>>8);
}
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::initInstance(CMeshBaseInstance *mbi)
{
}
// ***************************************************************************
bool CMeshMRMSkinnedGeom::clip(const std::vector &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;
}
// ***************************************************************************
inline sint CMeshMRMSkinnedGeom::chooseLod(float alphaMRM, float &alphaLod)
{
// Choose what Lod to draw.
alphaMRM*= _Lods.size()-1;
sint numLod= (sint)ceil(alphaMRM);
if(numLod==0)
{
numLod= 0;
alphaLod= 0;
}
else
{
// Lerp beetween lod i-1 and lod i.
alphaLod= alphaMRM-(numLod-1);
}
/// Ensure numLod is correct
if(numLod>=(sint)_Lods.size())
{
numLod= (sint)_Lods.size()-1;
alphaLod= 1;
}
return numLod;
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::render(IDriver *drv, CTransformShape *trans, float polygonCount, uint32 rdrFlags, float globalAlpha)
{
nlassert(drv);
if(_Lods.size()==0)
return;
// get the meshMRM instance.
CMeshBaseInstance *mi= safe_cast(trans);
// get the result of the Load Balancing.
float alphaMRM= _LevelDetail.getLevelDetailFromPolyCount(polygonCount);
// choose the lod.
float alphaLod;
sint numLod= chooseLod(alphaMRM, alphaLod);
// Render the choosen Lod.
CLod &lod= _Lods[numLod];
if(lod.RdrPass.size()==0)
return;
// 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(!(mi->isSkinned() && skeleton));
// Profiling
//===========
H_AUTO( NL3D_MeshMRMGeom_RenderNormal );
// Skinning.
//===========
// set the instance worldmatrix.
drv->setupModelMatrix(trans->getWorldMatrix());
// Geomorph.
//===========
// Geomorph the choosen Lod (if not the coarser mesh).
if(numLod>0)
{
applyGeomorph(lod.Geomorphs, alphaLod);
}
// force normalisation of normals..
bool bkupNorm= drv->isForceNormalize();
drv->forceNormalize(true);
// Setup meshVertexProgram
//===========
// Render the lod.
//===========
// active VB.
/* Hulud
* This is slow but, we don't care because this method is called for debug purpose only (watch skin without skinning)
*/
CVertexBuffer tmp;
getVertexBuffer (tmp);
drv->activeVertexBuffer(tmp);
// Global alpha used ?
uint32 globalAlphaUsed= rdrFlags & IMeshGeom::RenderGlobalAlpha;
uint8 globalAlphaInt=(uint8)NLMISC::OptFastFloor(globalAlpha*255);
// Render all pass.
if (globalAlphaUsed)
{
bool gaDisableZWrite= (rdrFlags & IMeshGeom::RenderGADisableZWrite)?true:false;
// for all passes
for(uint i=0;iMaterials[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);
/* Hulud
* This is slow but, we don't care because this method is called for debug purpose only (watch skin without skinning)
*/
CIndexBuffer block;
lod.getRdrPassPrimitiveBlock (i, block);
// Render
drv->activeIndexBuffer(block);
drv->renderTriangles(material, 0, block.getNumIndexes()/3);
// Resetup material/driver
blender.restoreRender(material, drv, gaDisableZWrite);
}
}
}
else
{
for(uint i=0;iMaterials[rdrPass.MaterialId].getBlend() == false) && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) ||
( (mi->Materials[rdrPass.MaterialId].getBlend() == true) && (rdrFlags & IMeshGeom::RenderTransparentMaterial) ) )
{
// CMaterial Ref
CMaterial &material=mi->Materials[rdrPass.MaterialId];
/* Hulud
* This is slow but, we don't care because this method is called for debug purpose only (watch skin without skinning)
*/
CIndexBuffer block;
lod.getRdrPassPrimitiveBlock (i, block);
// Render with the Materials of the MeshInstance.
drv->activeIndexBuffer(block);
drv->renderTriangles(material, 0, block.getNumIndexes()/3);
}
}
}
// bkup force normalisation.
drv->forceNormalize(bkupNorm);
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::renderSkin(CTransformShape *trans, float alphaMRM)
{
}
// ***************************************************************************
bool CMeshMRMSkinnedGeom::supportSkinGrouping() const
{
return true;
}
// ***************************************************************************
sint CMeshMRMSkinnedGeom::renderSkinGroupGeom(CMeshMRMSkinnedInstance *mi, float alphaMRM, uint remainingVertices, uint8 *vbDest)
{
H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpGeom )
// since not tested in supportSkinGrouping(), must test _Lods.empty(): no lod, no draw
if(_Lods.empty())
return 0;
// 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);
// choose the lod.
float alphaLod;
sint numLod= chooseLod(alphaMRM, alphaLod);
_LastLodComputed= numLod;
// Render the choosen Lod.
CLod &lod= _Lods[numLod];
if(lod.RdrPass.size()==0)
// return no vertices added
return 0;
// If the Lod is too big to render in the VBufferHard
if(lod.NWedges>remainingVertices)
// return Failure
return -1;
// get the skeleton model to which I am skinned
CSkeletonModel *skeleton;
skeleton = mi->getSkeletonModel();
// must be skinned for renderSkin()
nlassert(mi->isSkinned() && skeleton);
// Profiling
//===========
H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpGeom_go );
// Skinning.
//===========
// Use RawSkin if possible: only if no morph, and only Vertex/Normal
updateRawSkinNormal(true, mi, numLod);
// NB: the skeleton matrix has already been setuped by CSkeletonModel
// NB: the normalize flag has already been setuped by CSkeletonModel
// applySkin with RawSkin.
//--------
nlassert(mi->_RawSkinCache);
H_AUTO( NL3D_RawSkinning );
// RawSkin do all the job in optimized way: Skinning, copy to VBHard and Geomorph.
// skinning with normal, but no tangent space
applyRawSkinWithNormal (lod, *(mi->_RawSkinCache), skeleton, vbDest, alphaLod);
// Vertices are packed in RawSkin mode (ie no holes due to MRM!)
return (sint)mi->_RawSkinCache->Geomorphs.size() +
mi->_RawSkinCache->TotalSoftVertices +
mi->_RawSkinCache->TotalHardVertices;
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::renderSkinGroupPrimitives(CMeshMRMSkinnedInstance *mi, uint baseVertex, std::vector &specularRdrPasses, uint skinIndex)
{
H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpPrimitives );
// 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 lod choosen in renderSkinGroupGeom()
CLod &lod= _Lods[_LastLodComputed];
// must update primitive cache
updateShiftedTriangleCache(mi, _LastLodComputed, baseVertex);
nlassert(mi->_ShiftedTriangleCache);
// Render Triangles with cache
//===========
for(uint i=0;iMaterials[rdrPass.MaterialId];
// TestYoyo. Material Speed Test
/*if( material.getDiffuse()!=CRGBA(250, 251, 252) )
{
material.setDiffuse(CRGBA(250, 251, 252));
// Set all texture the same.
static CSmartPtr pTexFile= new CTextureFile("fy_hom_visage_c1_fy_e1.tga");
material.setTexture(0, pTexFile );
// Remove Specular.
if(material.getShader()==CMaterial::Specular)
{
CSmartPtr tex= material.getTexture(0);
material.setShader(CMaterial::Normal);
material.setTexture(0, tex );
}
// Remove MakeUp
material.setTexture(1, NULL);
}*/
// If the material is a specular material, don't render it now!
if(material.getShader()==CMaterial::Specular)
{
// Add it to the rdrPass to sort!
CSkinSpecularRdrPass specRdrPass;
specRdrPass.SkinIndex= skinIndex;
specRdrPass.RdrPassIndex= i;
// Get the handle of the specular Map as the sort Key
ITexture *specTex= material.getTexture(1);
if(!specTex)
specRdrPass.SpecId= 0;
else
specRdrPass.SpecId= drv->getTextureHandle( *specTex );
// Append it to the list
specularRdrPasses.push_back(specRdrPass);
}
else
{
// Get the shifted triangles.
CShiftedTriangleCache::CRdrPass &shiftedRdrPass= mi->_ShiftedTriangleCache->RdrPass[i];
// Render with the Materials of the MeshInstance.
drv->activeIndexBuffer(mi->_ShiftedTriangleCache->RawIndices);
drv->renderTriangles(material, shiftedRdrPass.Triangles, shiftedRdrPass.NumTriangles);
}
}
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::renderSkinGroupSpecularRdrPass(CMeshMRMSkinnedInstance *mi, uint rdrPassId)
{
H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpSpecularRdrPass );
// 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 lod choosen in renderSkinGroupGeom()
CLod &lod= _Lods[_LastLodComputed];
// _ShiftedTriangleCache must have been computed in renderSkinGroupPrimitives
nlassert(mi->_ShiftedTriangleCache);
// Render Triangles with cache
//===========
CRdrPass &rdrPass= lod.RdrPass[rdrPassId];
// CMaterial Ref
CMaterial &material=mi->Materials[rdrPass.MaterialId];
// Get the shifted triangles.
CShiftedTriangleCache::CRdrPass &shiftedRdrPass= mi->_ShiftedTriangleCache->RdrPass[rdrPassId];
// Render with the Materials of the MeshInstance.
drv->activeIndexBuffer(mi->_ShiftedTriangleCache->RawIndices);
drv->renderTriangles(material, shiftedRdrPass.Triangles, shiftedRdrPass.NumTriangles);
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::updateShiftedTriangleCache(CMeshMRMSkinnedInstance *mi, sint curLodId, uint baseVertex)
{
// if the instance has a cache, but not sync to us, delete it.
if( mi->_ShiftedTriangleCache && (
mi->_ShiftedTriangleCache->MeshDataId != _MeshDataId ||
mi->_ShiftedTriangleCache->LodId != curLodId ||
mi->_ShiftedTriangleCache->BaseVertex != baseVertex) )
{
mi->clearShiftedTriangleCache();
}
// If the instance has not a valid cache, must create it.
if( !mi->_ShiftedTriangleCache )
{
mi->_ShiftedTriangleCache= new CShiftedTriangleCache;
// Fill the cache Key.
mi->_ShiftedTriangleCache->MeshDataId= _MeshDataId;
mi->_ShiftedTriangleCache->LodId= curLodId;
mi->_ShiftedTriangleCache->BaseVertex= baseVertex;
// Build list of PBlock. From Lod, or from RawSkin cache.
static vector pbList;
pbList.clear();
nlassert(mi->_RawSkinCache);
pbList.resize(mi->_RawSkinCache->RdrPass.size());
for(uint i=0;i_RawSkinCache->RdrPass[i];
}
// Build RdrPass
mi->_ShiftedTriangleCache->RdrPass.resize((uint32)pbList.size());
// First pass, count number of triangles, and fill header info
uint totalTri= 0;
uint i;
for(i=0;i_ShiftedTriangleCache->RdrPass[i].NumTriangles= pbList[i]->getNumIndexes()/3;
totalTri+= pbList[i]->getNumIndexes()/3;
}
// Allocate triangles indices.
mi->_ShiftedTriangleCache->RawIndices.setFormat(NL_SKINNED_MESH_MRM_INDEX_FORMAT);
mi->_ShiftedTriangleCache->RawIndices.setNumIndexes(totalTri*3);
CIndexBufferReadWrite iba;
mi->_ShiftedTriangleCache->RawIndices.lock(iba);
// Second pass, fill ptrs, and fill Arrays
uint indexTri= 0;
for(i=0;i_ShiftedTriangleCache->RdrPass[i];
dstRdrPass.Triangles= indexTri*3;
// Fill the array
uint numTris= pbList[i]->getNumIndexes()/3;
if(numTris)
{
uint nIds= numTris*3;
// index, and fill
CIndexBufferRead ibaRead;
pbList[i]->lock (ibaRead);
#ifndef NL_SKINNED_MESH_MRM_INDEX16
nlassert(iba.getFormat() == CIndexBuffer::Indices32);
const uint32 *pSrcTri= (const uint32 *) ibaRead.getPtr();
uint32 *pDstTri= (uint32 *) iba.getPtr() + dstRdrPass.Triangles;
for(;nIds>0;nIds--,pSrcTri++,pDstTri++)
*pDstTri= *pSrcTri + baseVertex;
#else
nlassert(iba.getFormat() == CIndexBuffer::Indices16);
const uint16 *pSrcTri= (const uint16 *) ibaRead.getPtr();
uint16 *pDstTri= (uint16 *) iba.getPtr() + dstRdrPass.Triangles;
for(;nIds>0;nIds--,pSrcTri++,pDstTri++)
*pDstTri= *pSrcTri + baseVertex;
#endif
}
// Next
indexTri+= dstRdrPass.NumTriangles;
}
}
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::serial(NLMISC::IStream &f)
{
// because of complexity, serial is separated in save / load.
/*
Version 0:
- base version.
*/
f.serialVersion(0);
// serial bones names
f.serialCont (_BonesName);
if (f.isReading())
{
// Bones index are in skeleton model id list
_BoneIdComputed = false;
// Must always recompute usage of parents of bones used.
_BoneIdExtended = false;
}
// serial Basic info.
// ==================
f.serial(_BBox);
f.serial(_LevelDetail.MaxFaceUsed);
f.serial(_LevelDetail.MinFaceUsed);
f.serial(_LevelDetail.DistanceFinest);
f.serial(_LevelDetail.DistanceMiddle);
f.serial(_LevelDetail.DistanceCoarsest);
f.serial(_LevelDetail.OODistanceDelta);
f.serial(_LevelDetail.DistancePow);
// Prepare the VBuffer.
if (f.isReading())
_VBufferFinal.contReset();
_VBufferFinal.serial(f);
// serial Shadow Skin Information
f.serialCont (_ShadowSkin.Vertices);
f.serialCont (_ShadowSkin.Triangles);
// resest the Lod arrays. NB: each Lod is empty, and ready to receive Lod data.
// ==================
if (f.isReading())
{
contReset(_Lods);
}
f.serialCont (_Lods);
if (f.isReading())
{
// Inform that the mesh data has changed
dirtMeshDataId();
// Some runtime not serialized compilation
compileRunTime();
}
}
// ***************************************************************************
float CMeshMRMSkinnedGeom::getNumTriangles (float distance)
{
// NB: this is an approximation, but this is continious.
return _LevelDetail.getNumTriangles(distance);
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::computeBonesId (CSkeletonModel *skeleton)
{
// Already computed ?
if (!_BoneIdComputed)
{
// Get a pointer on the skeleton
nlassert (skeleton);
if (skeleton)
{
// **** For each bones, compute remap
std::vector remap;
skeleton->remapSkinBones(_BonesName, _BonesId, remap);
// **** Remap the vertices, and compute Bone Spheres.
// Find the Geomorph space: to process only real vertices, not geomorphed ones.
uint nGeomSpace= 0;
uint lod;
for (lod=0; lod<_Lods.size(); lod++)
{
nGeomSpace= max(nGeomSpace, (uint)_Lods[lod].Geomorphs.size());
}
// Prepare Sphere compute
static std::vector boneBBoxes;
static std::vector boneBBEmpty;
boneBBoxes.clear();
boneBBEmpty.clear();
boneBBoxes.resize(_BonesId.size());
boneBBEmpty.resize(_BonesId.size(), true);
// Remap the vertex, and compute the bone spheres. see CTransform::getSkinBoneSphere() doc.
// for true vertices
uint vert;
const uint vertexCount = _VBufferFinal.getNumVertices();
CPackedVertexBuffer::CPackedVertex *vertices = _VBufferFinal.getPackedVertices();
for (vert=nGeomSpace; vert0)||(weight==0))
{
// Check id
uint srcId= vertices[vert].Matrices[weight];
nlassert (srcId < remap.size());
// remap
vertices[vert].Matrices[weight] = 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 * vertex;
// extend the bone bbox.
if(boneBBEmpty[srcId])
{
boneBBoxes[srcId].setCenter(p);
boneBBEmpty[srcId]= false;
}
else
{
boneBBoxes[srcId].extend(p);
}
}
}
else
break;
}
}
// 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();
}
}
// **** Remap the vertex influence by lods
for (lod=0; lod<_Lods.size(); lod++)
{
// For each matrix used
uint matrix;
for (matrix=0; matrix<_Lods[lod].MatrixInfluences.size(); matrix++)
{
// Check
nlassert (_Lods[lod].MatrixInfluences[matrix] 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=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 CMeshMRMSkinnedGeom::compileRunTime()
{
_PreciseClipping= _BBox.getRadius() >= NL3D_MESH_PRECISE_CLIP_THRESHOLD;
// The Mesh must follow those restrictions, to support group skinning
nlassert (_VBufferFinal.getNumVertices() < NL3D_MESH_SKIN_MANAGER_MAXVERTICES);
// Support Shadow SkinGrouping if Shadow setuped, and if not too many vertices.
_SupportShadowSkinGrouping= !_ShadowSkin.Vertices.empty() &&
NL3D_SHADOW_MESH_SKIN_MANAGER_VERTEXFORMAT==CVertexBuffer::PositionFlag &&
_ShadowSkin.Vertices.size() <= NL3D_SHADOW_MESH_SKIN_MANAGER_MAXVERTICES;
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::profileSceneRender(CRenderTrav *rdrTrav, CTransformShape *trans, float polygonCount, uint32 rdrFlags)
{
// if no _Lods, no draw
if(_Lods.empty())
return;
// get the result of the Load Balancing.
float alphaMRM= _LevelDetail.getLevelDetailFromPolyCount(polygonCount);
// choose the lod.
float alphaLod;
sint numLod= chooseLod(alphaMRM, alphaLod);
// Render the choosen Lod.
CLod &lod= _Lods[numLod];
// get the mesh instance.
CMeshBaseInstance *mi= safe_cast(trans);
// Profile all pass.
uint triCount= 0;
for (uint i=0;iMaterials[rdrPass.MaterialId].getBlend() == false) && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) ||
( (mi->Materials[rdrPass.MaterialId].getBlend() == true) && (rdrFlags & IMeshGeom::RenderTransparentMaterial) ) )
{
triCount+= rdrPass.getNumTriangle();
}
}
// Profile
if(triCount)
{
// tri per VBFormat
rdrTrav->Scene->incrementProfileTriVBFormat(rdrTrav->Scene->BenchRes.MeshMRMProfileTriVBFormat,
NL3D_MESH_SKIN_MANAGER_VERTEXFORMAT, triCount);
rdrTrav->Scene->BenchRes.NumMeshMRMVBufferStd++;
rdrTrav->Scene->BenchRes.NumMeshMRMRdrNormal++;
rdrTrav->Scene->BenchRes.NumMeshMRMTriRdrNormal+= triCount;
}
}
// ***************************************************************************
bool CMeshMRMSkinnedGeom::getSkinBoneBBox(CSkeletonModel *skeleton, NLMISC::CAABBox &bbox, uint boneId) const
{
bbox.setCenter(CVector::Null);
bbox.setHalfSize(CVector::Null);
if(!skeleton)
return false;
// get the bindpos of the wanted bone
nlassert(boneIdBones.size());
const CMatrix &invBindPos= skeleton->Bones[boneId].getBoneBase().InvBindPos;
// Find the Geomorph space: to process only real vertices, not geomorphed ones.
uint nGeomSpace= 0;
uint lod;
for (lod=0; lod<_Lods.size(); lod++)
{
nGeomSpace= max(nGeomSpace, (uint)_Lods[lod].Geomorphs.size());
}
// Prepare BBox compute
bool bbEmpty= true;
// Remap the vertex, and compute the wanted bone bbox
// for true vertices
const uint vertexCount = _VBufferFinal.getNumVertices();
const CPackedVertexBuffer::CPackedVertex *vertices = _VBufferFinal.getPackedVertices();
for (uint vert=nGeomSpace; vert0)||(weight==0))
{
// Check id is the wanted one
if(vertices[vert].Matrices[weight]==boneId)
{
// transform the vertex pos in BoneSpace
CVector p= invBindPos * vertex;
// extend the bone bbox.
if(bbEmpty)
{
bbox.setCenter(p);
bbEmpty= false;
}
else
{
bbox.extend(p);
}
}
}
else
break;
}
}
// return true if some influence found
return !bbEmpty;
}
// ***************************************************************************
// ***************************************************************************
// Mesh Block Render Interface
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
bool CMeshMRMSkinnedGeom::supportMeshBlockRendering () const
{
/*
Yoyo: Don't Support It for MRM because too Slow!!
The problem is that lock() unlock() on each instance, on the same VBHeap IS AS SLOWER AS
VB switching.
TODO_OPTIMIZE: find a way to optimize MRM.
*/
return false;
}
// ***************************************************************************
// ***************************************************************************
// CMeshMRMSkinned.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CMeshMRMSkinned::CMeshMRMSkinned()
{
}
// ***************************************************************************
bool CMeshMRMSkinned::isCompatible(const CMesh::CMeshBuild &m)
{
/* Optimised shape for skinned object with MRM, 1 UV coordinates, 1 to 4 skinning weight and 256 matrices
Tangeant space, vertex program, mesh block rendering and vertex buffer hard are not available. */
// Vertex format
if (m.VertexFlags != NL3D_MESH_MRM_SKINNED_VERTEX_FORMAT)
return false;
// No W
if (m.NumCoords[0] != 2)
return false;
// No blend shape
if (!m.BlendShapes.empty())
return false;
// Check number of vertices
if (m.Vertices.size() > NL3D_MESH_SKIN_MANAGER_MAXVERTICES)
return false;
// Ok
return true;
}
// ***************************************************************************
void CMeshMRMSkinned::build (CMeshBase::CMeshBaseBuild &mBase, CMesh::CMeshBuild &m,
const CMRMParameters ¶ms)
{
nlassert (isCompatible(m));
/// copy MeshBase info: materials ....
CMeshBase::buildMeshBase (mBase);
// Then build the geom.
_MeshMRMGeom.build (m, (uint)mBase.Materials.size(), params);
}
// ***************************************************************************
void CMeshMRMSkinned::build (CMeshBase::CMeshBaseBuild &m, const CMeshMRMSkinnedGeom &mgeom)
{
/// copy MeshBase info: materials ....
CMeshBase::buildMeshBase(m);
// Then copy the geom.
_MeshMRMGeom= mgeom;
}
// ***************************************************************************
void CMeshMRMSkinned::optimizeMaterialUsage(std::vector &remap)
{
// For each material, count usage.
vector materialUsed;
materialUsed.resize(CMeshBase::_Materials.size(), false);
for(uint lod=0;lodShape= this;
// instanciate the material part of the MeshMRM, ie the CMeshBase.
CMeshBase::instanciateMeshBase(mi, &scene);
// do some instance init for MeshGeom
_MeshMRMGeom.initInstance(mi);
// init the FilterType
mi->initRenderFilterType();
return mi;
}
// ***************************************************************************
bool CMeshMRMSkinned::clip(const std::vector &pyramid, const CMatrix &worldMatrix)
{
return _MeshMRMGeom.clip(pyramid, worldMatrix);
}
// ***************************************************************************
void CMeshMRMSkinned::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
_MeshMRMGeom.render(drv, trans, trans->getNumTrianglesAfterLoadBalancing(), rdrFlags, 1);
}
// ***************************************************************************
void CMeshMRMSkinned::serial(NLMISC::IStream &f)
{
/*
Version 0:
- base version.
*/
(void)f.serialVersion(0);
// serial Materials infos contained in CMeshBase.
CMeshBase::serialMeshBase(f);
// serial the geometry.
_MeshMRMGeom.serial(f);
}
// ***************************************************************************
float CMeshMRMSkinned::getNumTriangles (float distance)
{
return _MeshMRMGeom.getNumTriangles (distance);
}
// ***************************************************************************
const CMeshMRMSkinnedGeom& CMeshMRMSkinned::getMeshGeom () const
{
return _MeshMRMGeom;
}
// ***************************************************************************
void CMeshMRMSkinned::computeBonesId (CSkeletonModel *skeleton)
{
_MeshMRMGeom.computeBonesId (skeleton);
}
// ***************************************************************************
void CMeshMRMSkinned::updateSkeletonUsage (CSkeletonModel *skeleton, bool increment)
{
_MeshMRMGeom.updateSkeletonUsage(skeleton, increment);
}
// ***************************************************************************
void CMeshMRMSkinned::changeMRMDistanceSetup(float distanceFinest, float distanceMiddle, float distanceCoarsest)
{
_MeshMRMGeom.changeMRMDistanceSetup(distanceFinest, distanceMiddle, distanceCoarsest);
}
// ***************************************************************************
IMeshGeom *CMeshMRMSkinned::supportMeshBlockRendering (CTransformShape *trans, float &polygonCount ) const
{
return NULL;
}
// ***************************************************************************
void CMeshMRMSkinned::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);
// profile the mesh
_MeshMRMGeom.profileSceneRender(rdrTrav, trans, trans->getNumTrianglesAfterLoadBalancing(), rdrFlags);
}
// ***************************************************************************
// ***************************************************************************
// CMeshMRMSkinnedGeom RawSkin optimisation
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
void CMeshMRMSkinnedGeom::dirtMeshDataId()
{
// see updateRawSkinNormal()
_MeshDataId++;
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::updateRawSkinNormal(bool enabled, CMeshMRMSkinnedInstance *mi, sint curLodId)
{
if(!enabled)
{
// if the instance cache is not cleared, must clear.
mi->clearRawSkinCache();
}
else
{
// If the instance has no RawSkin, or has a too old RawSkin cache, must delete it, and recreate
if ((mi->_RawSkinCache == NULL) || (mi->_RawSkinCache->MeshDataId!=_MeshDataId))
{
// first delete if too old.
mi->clearRawSkinCache();
// Then recreate, and use _MeshDataId to verify that the instance works with same data.
mi->_RawSkinCache= new CRawSkinnedNormalCache;
mi->_RawSkinCache->MeshDataId= _MeshDataId;
mi->_RawSkinCache->LodId= -1;
}
/* If the instance rawSkin has a different Lod (or if -1), then must recreate it.
NB: The lod may change each frame per instance, but suppose not so many change, so we can cache those data.
*/
if( mi->_RawSkinCache->LodId != curLodId )
{
H_AUTO( NL3D_CMeshMRMGeom_updateRawSkinNormal );
CRawSkinnedNormalCache &skinLod= *mi->_RawSkinCache;
CLod &lod= _Lods[curLodId];
uint i;
sint rawIdx;
// Clear the raw skin mesh.
skinLod.clearArrays();
// Cache this lod
mi->_RawSkinCache->LodId= curLodId;
// For each matrix influence.
nlassert(NL3D_MESH_SKINNING_MAX_MATRIX==4);
// For each vertex, acknowledge if it is a src for geomorph.
static vector softVertices;
softVertices.clear();
softVertices.resize( _VBufferFinal.getNumVertices(), 0 );
for(i=0;i vertexRemap;
vertexRemap.resize( _VBufferFinal.getNumVertices() );
sint softSize[4];
sint hardSize[4];
sint softStart[4];
sint hardStart[4];
// count vertices
skinLod.TotalSoftVertices= 0;
skinLod.TotalHardVertices= 0;
for(i=0;i<4;i++)
{
softSize[i]= 0;
hardSize[i]= 0;
// Count.
for(uint j=0;j &shadowVertices, const std::vector &triangles)
{
_ShadowSkin.Vertices= shadowVertices;
_ShadowSkin.Triangles= triangles;
// update flag. Support Shadow SkinGrouping if Shadow setuped, and if not too many vertices.
_SupportShadowSkinGrouping= !_ShadowSkin.Vertices.empty() &&
NL3D_SHADOW_MESH_SKIN_MANAGER_VERTEXFORMAT==CVertexBuffer::PositionFlag &&
_ShadowSkin.Vertices.size() <= NL3D_SHADOW_MESH_SKIN_MANAGER_MAXVERTICES;
}
// ***************************************************************************
uint CMeshMRMSkinnedGeom::getNumShadowSkinVertices() const
{
return (uint)_ShadowSkin.Vertices.size();
}
// ***************************************************************************
sint CMeshMRMSkinnedGeom::renderShadowSkinGeom(CMeshMRMSkinnedInstance *mi, uint remainingVertices, uint8 *vbDest)
{
uint numVerts= (uint)_ShadowSkin.Vertices.size();
// if no verts, no draw
if(numVerts==0)
return 0;
// if no lods, there should be no verts, but still ensure no bug in skinning
if(_Lods.empty())
return 0;
// If the Lod is too big to render in the VBufferHard
if(numVerts>remainingVertices)
// return Failure
return -1;
// get the skeleton model to which I am skinned
CSkeletonModel *skeleton;
skeleton = mi->getSkeletonModel();
// must be skinned for renderSkin()
nlassert(skeleton);
// Profiling
//===========
H_AUTO_USE( NL3D_MeshMRMGeom_RenderShadow );
// Skinning.
//===========
// For all matrix this Mesh use. (the shadow geometry cannot use other Matrix than the mesh use).
// NB: take the best lod since the lower lods cannot use other Matrix than the higher one.
static vector boneMat3x4;
CLod &lod= _Lods[_Lods.size()-1];
computeBoneMatrixes3x4(boneMat3x4, lod.MatrixInfluences, skeleton);
_ShadowSkin.applySkin((CVector*)vbDest, boneMat3x4);
// How many vertices are added to the VBuffer ???
return numVerts;
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::renderShadowSkinPrimitives(CMeshMRMSkinnedInstance *mi, CMaterial &castMat, IDriver *drv, uint baseVertex)
{
nlassert(drv);
if(_ShadowSkin.Triangles.empty())
return;
// Profiling
//===========
H_AUTO_USE( NL3D_MeshMRMGeom_RenderShadow );
// NB: the skeleton matrix has already been setuped by CSkeletonModel
// NB: the normalize flag has already been setuped by CSkeletonModel
// TODO_SHADOW: optim: Special triangle cache for shadow!
static CIndexBuffer shiftedTris;
shiftedTris.setPreferredMemory(CIndexBuffer::RAMVolatile, false);
if (shiftedTris.getName().empty()) NL_SET_IB_NAME(shiftedTris, "CMeshMRMSkinnedGeom::renderShadowSkinPrimitives::shiftedTris");
//if(shiftedTris.getNumIndexes()<_ShadowSkin.Triangles.size())
//{
shiftedTris.setFormat(NL_SKINNED_MESH_MRM_INDEX_FORMAT);
shiftedTris.setNumIndexes((uint32)_ShadowSkin.Triangles.size());
//}
{
CIndexBufferReadWrite iba;
shiftedTris.lock(iba);
const uint32 *src= &_ShadowSkin.Triangles[0];
TSkinnedMeshMRMIndexType *dst= (TSkinnedMeshMRMIndexType*) iba.getPtr();
for(uint n= (uint)_ShadowSkin.Triangles.size();n>0;n--, src++, dst++)
{
*dst= (TSkinnedMeshMRMIndexType)(*src + baseVertex);
}
}
// Render Triangles with cache
//===========
uint numTris= (uint)_ShadowSkin.Triangles.size()/3;
// Render with the Materials of the MeshInstance.
drv->activeIndexBuffer(shiftedTris);
drv->renderTriangles(castMat, 0, numTris);
}
// ***************************************************************************
bool CMeshMRMSkinnedGeom::intersectSkin(CMeshMRMSkinnedInstance *mi, const CMatrix &toRaySpace, float &dist2D, float &distZ, bool computeDist2D)
{
// no inst/verts/lod => no intersection
if(!mi || _ShadowSkin.Vertices.empty() || _Lods.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).
// NB: take the best lod (_Lods.back()) since the lower lods cannot use other Matrix than the higher one.
return _ShadowSkin.getRayIntersection(toRaySpace, *skeleton, _Lods.back().MatrixInfluences, dist2D, distZ, computeDist2D);
}
// ***************************************************************************
// ***************************************************************************
// CMeshMRMSkinnedGeom::CPackedVertexBuffer
// ***************************************************************************
// ***************************************************************************
void CMeshMRMSkinnedGeom::CPackedVertexBuffer::serial(NLMISC::IStream &f)
{
// Version
f.serialVersion(0);
f.serialCont (_PackedBuffer);
f.serial (_DecompactScale);
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::CPackedVertexBuffer::CPackedVertex::serial(NLMISC::IStream &f)
{
// Version
f.serialVersion(0);
f.serial (X);
f.serial (Y);
f.serial (Z);
f.serial (Nx);
f.serial (Ny);
f.serial (Nz);
f.serial (U);
f.serial (V);
uint i;
for (i=0; i &skinWeight)
{
const uint numVertices = buffer.getNumVertices();
nlassert (numVertices == skinWeight.size());
nlassert ((buffer.getVertexFormat() & (CVertexBuffer::PositionFlag|CVertexBuffer::NormalFlag|CVertexBuffer::TexCoord0Flag)) == (CVertexBuffer::PositionFlag|CVertexBuffer::NormalFlag|CVertexBuffer::TexCoord0Flag));
_PackedBuffer.resize (numVertices);
// default scale
_DecompactScale = NL3D_MESH_MRM_SKINNED_DEFAULT_POS_SCALE;
CVertexBufferRead vba;
buffer.lock (vba);
if (numVertices)
{
// Get the min max of the bbox
CVector _min = *vba.getVertexCoordPointer(0);
CVector _max = _min;
// For each vertex
uint i;
for (i=1; i 0);
vertex.Weights[0] += weightSum;
}
}
}
// ***************************************************************************
void CMeshMRMSkinnedGeom::getVertexBuffer(CVertexBuffer &output) const
{
output.setVertexFormat (CVertexBuffer::PositionFlag|CVertexBuffer::NormalFlag|CVertexBuffer::TexCoord0Flag);
const uint numVertices = _VBufferFinal.getNumVertices();
output.setNumVertices (numVertices);
// Set all UV routing on 0
uint i;
for (i=0; i &skinWeights) const
{
const uint vertexCount = _VBufferFinal.getNumVertices();
skinWeights.resize (vertexCount);
const CPackedVertexBuffer::CPackedVertex *vertices = _VBufferFinal.getPackedVertices();
uint i;
for (i=0; i