3610 lines
99 KiB
C++
3610 lines
99 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/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.h"
|
|
#include "nel/3d/mrm_builder.h"
|
|
#include "nel/3d/mrm_parameters.h"
|
|
#include "nel/3d/mesh_mrm_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_skin.h"
|
|
#include "nel/3d/shifted_triangle_cache.h"
|
|
#include "nel/3d/texture_file.h"
|
|
|
|
|
|
using namespace NLMISC;
|
|
using namespace std;
|
|
|
|
|
|
namespace NL3D
|
|
{
|
|
|
|
|
|
H_AUTO_DECL( NL3D_MeshMRMGeom_RenderShadow )
|
|
|
|
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
// CMeshMRMGeom::CLod
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::CLod::serial(NLMISC::IStream &f)
|
|
{
|
|
/*
|
|
Version 2:
|
|
- precompute of triangle order. (nothing more to load).
|
|
Version 1:
|
|
- add VertexBlocks;
|
|
Version 0:
|
|
- base vdrsion.
|
|
*/
|
|
|
|
sint ver= f.serialVersion(2);
|
|
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<NL3D_MESH_SKINNING_MAX_MATRIX; i++)
|
|
{
|
|
f.serialCont(InfluencedVertices[i]);
|
|
}
|
|
|
|
if(ver>=1)
|
|
f.serialCont(SkinVertexBlocks);
|
|
else
|
|
buildSkinVertexBlocks();
|
|
|
|
// if >= version 2, reorder of triangles is precomputed, else compute it now.
|
|
if(ver<2)
|
|
optimizeTriangleOrder();
|
|
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::CLod::buildSkinVertexBlocks()
|
|
{
|
|
contReset(SkinVertexBlocks);
|
|
|
|
|
|
// The list of vertices. true if used by this lod.
|
|
vector<bool> vertexMap;
|
|
vertexMap.resize(NWedges, false);
|
|
|
|
|
|
// from InfluencedVertices, aknoledge what vertices are used.
|
|
uint i;
|
|
for(i=0;i<NL3D_MESH_SKINNING_MAX_MATRIX;i++)
|
|
{
|
|
uint nInf= (uint)InfluencedVertices[i].size();
|
|
if( nInf==0 )
|
|
continue;
|
|
uint32 *infPtr= &(InfluencedVertices[i][0]);
|
|
|
|
// for all InfluencedVertices only.
|
|
for(;nInf>0;nInf--, infPtr++)
|
|
{
|
|
uint index= *infPtr;
|
|
vertexMap[index]= true;
|
|
}
|
|
}
|
|
|
|
// For all vertices, test if they are used, and build the according SkinVertexBlocks;
|
|
CVertexBlock *vBlock= NULL;
|
|
for(i=0; i<vertexMap.size();i++)
|
|
{
|
|
if(vertexMap[i])
|
|
{
|
|
// preceding block?
|
|
if(vBlock)
|
|
{
|
|
// yes, extend it.
|
|
vBlock->NVertices++;
|
|
}
|
|
else
|
|
{
|
|
// no, append a new one.
|
|
SkinVertexBlocks.push_back(CVertexBlock());
|
|
vBlock= &SkinVertexBlocks[SkinVertexBlocks.size()-1];
|
|
vBlock->VertexStart= i;
|
|
vBlock->NVertices= 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Finish the preceding block (if any).
|
|
vBlock= NULL;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::CLod::optimizeTriangleOrder()
|
|
{
|
|
CStripifier stripifier;
|
|
|
|
// for all rdrpass
|
|
for(uint rp=0; rp<RdrPass.size(); rp++ )
|
|
{
|
|
// stripify list of triangles of this pass.
|
|
CRdrPass &pass= RdrPass[rp];
|
|
stripifier.optimizeTriangles(pass.PBlock, pass.PBlock);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
// CMeshMRMGeom.
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
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;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
CMeshMRMGeom::CMeshMRMGeom()
|
|
{
|
|
_Skinned= false;
|
|
_NbLodLoaded= 0;
|
|
_BoneIdComputed = false;
|
|
_BoneIdExtended = false;
|
|
_PreciseClipping= false;
|
|
_SupportSkinGrouping= false;
|
|
_MeshDataId= 0;
|
|
_SupportMeshBlockRendering= false;
|
|
_MBRCurrentLodId= 0;
|
|
_SupportShadowSkinGrouping= false;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
CMeshMRMGeom::~CMeshMRMGeom()
|
|
{
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::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 CMeshMRMGeom::build(CMesh::CMeshBuild &m, std::vector<CMesh::CMeshBuild*> &bsList,
|
|
uint numMaxMaterial, const CMRMParameters ¶ms)
|
|
{
|
|
// Empty geometry?
|
|
if(m.Vertices.size()==0 || m.Faces.size()==0)
|
|
{
|
|
_VBufferFinal.setNumVertices(0);
|
|
_VBufferFinal.reserve(0);
|
|
_Lods.clear();
|
|
_BBox.setCenter(CVector::Null);
|
|
_BBox.setSize(CVector::Null);
|
|
return;
|
|
}
|
|
nlassert(numMaxMaterial>0);
|
|
|
|
|
|
// SmartPtr Copy VertexProgram effect.
|
|
//================================================
|
|
this->_MeshVertexProgram= m.MeshVertexProgram;
|
|
|
|
|
|
/// 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;
|
|
|
|
mrmBuilder.compileMRM(m, bsList, params, meshBuildMRM, numMaxMaterial);
|
|
|
|
// Then just copy result!
|
|
//================================================
|
|
_VBufferFinal= meshBuildMRM.VBuffer;
|
|
_Lods= meshBuildMRM.Lods;
|
|
_Skinned= meshBuildMRM.Skinned;
|
|
_SkinWeights= meshBuildMRM.SkinWeights;
|
|
|
|
// 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();
|
|
|
|
|
|
// Build the _LodInfos.
|
|
//================================================
|
|
_LodInfos.resize(_Lods.size());
|
|
uint32 precNWedges= 0;
|
|
uint i;
|
|
for(i=0;i<_Lods.size();i++)
|
|
{
|
|
_LodInfos[i].StartAddWedge= precNWedges;
|
|
_LodInfos[i].EndAddWedges= _Lods[i].NWedges;
|
|
precNWedges= _Lods[i].NWedges;
|
|
// LodOffset is filled in serial() when stream is input.
|
|
}
|
|
// After build, all lods are present in memory.
|
|
_NbLodLoaded= (uint)_Lods.size();
|
|
|
|
|
|
// 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<firstLod.RdrPass.size(); pb++)
|
|
{
|
|
CRdrPass &pass= firstLod.RdrPass[pb];
|
|
// Sum tri
|
|
_LevelDetail.MinFaceUsed+= pass.PBlock.getNumIndexes ()/3;
|
|
}
|
|
// Compute MaxFaces.
|
|
CLod &lastLod= _Lods[_Lods.size()-1];
|
|
for (pb=0; pb<lastLod.RdrPass.size(); pb++)
|
|
{
|
|
CRdrPass &pass= lastLod.RdrPass[pb];
|
|
// Sum tri
|
|
_LevelDetail.MaxFaceUsed+= pass.PBlock.getNumIndexes ()/3;
|
|
}
|
|
}
|
|
|
|
|
|
// For skinning.
|
|
//================================================
|
|
if( _Skinned )
|
|
{
|
|
bkupOriginalSkinVertices();
|
|
}
|
|
// Inform that the mesh data has changed
|
|
dirtMeshDataId();
|
|
|
|
|
|
// For AGP SKinning optim, and for Render optim
|
|
//================================================
|
|
for(i=0;i<_Lods.size();i++)
|
|
{
|
|
_Lods[i].buildSkinVertexBlocks();
|
|
// sort triangles for better cache use.
|
|
_Lods[i].optimizeTriangleOrder();
|
|
}
|
|
|
|
// Copy Blend Shapes
|
|
//================================================
|
|
_MeshMorpher.BlendShapes = meshBuildMRM.BlendShapes;
|
|
|
|
|
|
// Compact bone id and build a bone id names
|
|
//================================================
|
|
|
|
// Skinned ?
|
|
if (_Skinned)
|
|
{
|
|
// Remap
|
|
std::map<uint, uint> remap;
|
|
|
|
// Current bone
|
|
uint currentBone = 0;
|
|
|
|
// Reserve memory
|
|
_BonesName.reserve (m.BonesNames.size());
|
|
|
|
// For each vertices
|
|
uint vert;
|
|
for (vert=0; vert<_SkinWeights.size(); vert++)
|
|
{
|
|
// Found one ?
|
|
bool found=false;
|
|
|
|
// For each weight
|
|
uint weight;
|
|
for (weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++)
|
|
{
|
|
// Active ?
|
|
if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0))
|
|
{
|
|
// Look for it
|
|
std::map<uint, uint>::iterator ite = remap.find (_SkinWeights[vert].MatrixId[weight]);
|
|
|
|
// Find ?
|
|
if (ite == remap.end())
|
|
{
|
|
// Insert it
|
|
remap.insert (std::map<uint, uint>::value_type (_SkinWeights[vert].MatrixId[weight], currentBone));
|
|
|
|
// Check the id
|
|
nlassert (_SkinWeights[vert].MatrixId[weight]<m.BonesNames.size());
|
|
|
|
// Set the bone name
|
|
_BonesName.push_back (m.BonesNames[_SkinWeights[vert].MatrixId[weight]]);
|
|
|
|
// Set the local bone id
|
|
_SkinWeights[vert].MatrixId[weight] = currentBone++;
|
|
}
|
|
else
|
|
{
|
|
// Set the local bone id
|
|
_SkinWeights[vert].MatrixId[weight] = ite->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<uint, uint>::iterator ite = remap.find (_Lods[lod].MatrixInfluences[matrix]);
|
|
|
|
// Find ?
|
|
nlassert (ite != remap.end());
|
|
|
|
// Remap
|
|
_Lods[lod].MatrixInfluences[matrix] = ite->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Misc.
|
|
//===================
|
|
// Some runtime not serialized compilation
|
|
compileRunTime();
|
|
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::applyMaterialRemap(const std::vector<sint> &remap)
|
|
{
|
|
for(uint lod=0;lod<getNbLod();lod++)
|
|
{
|
|
for(uint rp=0;rp<getNbRdrPass(lod);rp++)
|
|
{
|
|
// remap
|
|
uint32 &matId= _Lods[lod].RdrPass[rp].MaterialId;
|
|
nlassert(remap[matId]>=0);
|
|
matId= remap[matId];
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::applyGeomorph(std::vector<CMRMWedgeGeom> &geoms, float alphaLod)
|
|
{
|
|
applyGeomorphWithVBHardPtr(geoms, alphaLod, NULL);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::applyGeomorphWithVBHardPtr(std::vector<CMRMWedgeGeom> &geoms, float alphaLod, uint8 *vertexDestPtr)
|
|
{
|
|
// no geomorphs? quit.
|
|
if(geoms.size()==0)
|
|
return;
|
|
|
|
clamp(alphaLod, 0.f, 1.f);
|
|
float a= alphaLod;
|
|
float a1= 1 - alphaLod;
|
|
|
|
|
|
// info from VBuffer.
|
|
CVertexBufferReadWrite vba;
|
|
_VBufferFinal.lock (vba);
|
|
uint8 *vertexPtr= (uint8*)vba.getVertexCoordPointer();
|
|
uint flags= _VBufferFinal.getVertexFormat();
|
|
sint32 vertexSize= _VBufferFinal.getVertexSize();
|
|
// because of the unrolled code for 4 first UV, must assert this.
|
|
nlassert(CVertexBuffer::MaxStage>=4);
|
|
// must have XYZ.
|
|
nlassert(flags & CVertexBuffer::PositionFlag);
|
|
|
|
|
|
// If VBuffer Hard disabled
|
|
if(vertexDestPtr==NULL)
|
|
{
|
|
// write into vertexPtr.
|
|
vertexDestPtr= vertexPtr;
|
|
}
|
|
|
|
|
|
// if it is a common format
|
|
if( flags== (CVertexBuffer::PositionFlag | CVertexBuffer::NormalFlag | CVertexBuffer::TexCoord0Flag) &&
|
|
_VBufferFinal.getValueType(CVertexBuffer::TexCoord0) == CVertexBuffer::Float2 )
|
|
{
|
|
// use a faster method
|
|
applyGeomorphPosNormalUV0(geoms, vertexPtr, vertexDestPtr, vertexSize, a, a1);
|
|
}
|
|
else
|
|
{
|
|
// for color interp
|
|
uint i;
|
|
uint ua= (uint)(a*256);
|
|
clamp(ua, (uint)0, (uint)256);
|
|
uint ua1= 256 - ua;
|
|
|
|
// if an offset is 0, it means that the component is not in the VBuffer.
|
|
sint32 normalOff;
|
|
sint32 colorOff;
|
|
sint32 specularOff;
|
|
sint32 uvOff[CVertexBuffer::MaxStage];
|
|
bool has3Coords[CVertexBuffer::MaxStage];
|
|
|
|
|
|
// Compute offset of each component of the VB.
|
|
if(flags & CVertexBuffer::NormalFlag)
|
|
normalOff= _VBufferFinal.getNormalOff();
|
|
else
|
|
normalOff= 0;
|
|
if(flags & CVertexBuffer::PrimaryColorFlag)
|
|
colorOff= _VBufferFinal.getColorOff();
|
|
else
|
|
colorOff= 0;
|
|
if(flags & CVertexBuffer::SecondaryColorFlag)
|
|
specularOff= _VBufferFinal.getSpecularOff();
|
|
else
|
|
specularOff= 0;
|
|
|
|
for(i= 0; i<CVertexBuffer::MaxStage;i++)
|
|
{
|
|
if(flags & (CVertexBuffer::TexCoord0Flag<<i))
|
|
{
|
|
uvOff[i]= _VBufferFinal.getTexCoordOff(i);
|
|
has3Coords[i] = (_VBufferFinal.getValueType(i + CVertexBuffer::TexCoord0) == CVertexBuffer::Float3);
|
|
}
|
|
else
|
|
{
|
|
uvOff[i]= 0;
|
|
}
|
|
}
|
|
|
|
|
|
// For all geomorphs.
|
|
uint nGeoms= (uint)geoms.size();
|
|
CMRMWedgeGeom *ptrGeom= &(geoms[0]);
|
|
uint8 *destPtr= vertexDestPtr;
|
|
/* NB: optimisation: lot of "if" in this Loop, but because of BTB, they always cost nothing (prediction is good).
|
|
NB: this also is why we unroll the 4 1st Uv. The other (if any), are done in the other loop.
|
|
NB: optimisation for AGP write cominers: the order of write (vertex, normal, uvs...) is important for good
|
|
use of AGP write combiners.
|
|
We have 2 version : one that tests for 3 coordinates texture coords, and one that doesn't
|
|
*/
|
|
|
|
if (!has3Coords[0] && !has3Coords[1] && !has3Coords[2] && !has3Coords[3])
|
|
{
|
|
// there are no texture coordinate of dimension 3
|
|
for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize )
|
|
{
|
|
uint8 *startPtr= vertexPtr + ptrGeom->Start*vertexSize;
|
|
uint8 *endPtr= vertexPtr + ptrGeom->End*vertexSize;
|
|
|
|
// Vertex.
|
|
{
|
|
CVector *start= (CVector*)startPtr;
|
|
CVector *end= (CVector*)endPtr;
|
|
CVector *dst= (CVector*)destPtr;
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
|
|
// Normal.
|
|
if(normalOff)
|
|
{
|
|
CVector *start= (CVector*)(startPtr + normalOff);
|
|
CVector *end= (CVector*)(endPtr + normalOff);
|
|
CVector *dst= (CVector*)(destPtr + normalOff);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
|
|
|
|
// Uvs.
|
|
// uv[0].
|
|
if(uvOff[0])
|
|
{
|
|
// Uv.
|
|
CUV *start= (CUV*)(startPtr + uvOff[0]);
|
|
CUV *end= (CUV*)(endPtr + uvOff[0]);
|
|
CUV *dst= (CUV*)(destPtr + uvOff[0]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
// uv[1].
|
|
if(uvOff[1])
|
|
{
|
|
// Uv.
|
|
CUV *start= (CUV*)(startPtr + uvOff[1]);
|
|
CUV *end= (CUV*)(endPtr + uvOff[1]);
|
|
CUV *dst= (CUV*)(destPtr + uvOff[1]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
// uv[2].
|
|
if(uvOff[2])
|
|
{
|
|
CUV *start= (CUV*)(startPtr + uvOff[2]);
|
|
CUV *end= (CUV*)(endPtr + uvOff[2]);
|
|
CUV *dst= (CUV*)(destPtr + uvOff[2]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
// uv[3].
|
|
if(uvOff[3])
|
|
{
|
|
// Uv.
|
|
CUV *start= (CUV*)(startPtr + uvOff[3]);
|
|
CUV *end= (CUV*)(endPtr + uvOff[3]);
|
|
CUV *dst= (CUV*)(destPtr + uvOff[3]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
}
|
|
}
|
|
else // THERE ARE TEXTURE COORDINATES OF DIMENSION 3
|
|
{
|
|
for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize )
|
|
{
|
|
uint8 *startPtr= vertexPtr + ptrGeom->Start*vertexSize;
|
|
uint8 *endPtr= vertexPtr + ptrGeom->End*vertexSize;
|
|
|
|
// Vertex.
|
|
{
|
|
CVector *start= (CVector*)startPtr;
|
|
CVector *end= (CVector*)endPtr;
|
|
CVector *dst= (CVector*)destPtr;
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
|
|
// Normal.
|
|
if(normalOff)
|
|
{
|
|
CVector *start= (CVector*)(startPtr + normalOff);
|
|
CVector *end= (CVector*)(endPtr + normalOff);
|
|
CVector *dst= (CVector*)(destPtr + normalOff);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
// Uvs.
|
|
// uv[0].
|
|
if(uvOff[0])
|
|
{
|
|
if (!has3Coords[0])
|
|
{
|
|
// Uv.
|
|
CUV *start= (CUV*)(startPtr + uvOff[0]);
|
|
CUV *end= (CUV*)(endPtr + uvOff[0]);
|
|
CUV *dst= (CUV*)(destPtr + uvOff[0]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
else
|
|
{
|
|
// Uv.
|
|
CUVW *start= (CUVW*)(startPtr + uvOff[0]);
|
|
CUVW *end= (CUVW*)(endPtr + uvOff[0]);
|
|
CUVW *dst= (CUVW*)(destPtr + uvOff[0]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
}
|
|
// uv[1].
|
|
if(uvOff[1])
|
|
{
|
|
if (!has3Coords[1])
|
|
{
|
|
// Uv.
|
|
CUV *start= (CUV*)(startPtr + uvOff[1]);
|
|
CUV *end= (CUV*)(endPtr + uvOff[1]);
|
|
CUV *dst= (CUV*)(destPtr + uvOff[1]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
else
|
|
{
|
|
// Uv.
|
|
CUVW *start= (CUVW*)(startPtr + uvOff[1]);
|
|
CUVW *end= (CUVW*)(endPtr + uvOff[1]);
|
|
CUVW *dst= (CUVW*)(destPtr + uvOff[1]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
}
|
|
// uv[2].
|
|
if(uvOff[2])
|
|
{
|
|
if (!has3Coords[2])
|
|
{
|
|
// Uv.
|
|
CUV *start= (CUV*)(startPtr + uvOff[2]);
|
|
CUV *end= (CUV*)(endPtr + uvOff[2]);
|
|
CUV *dst= (CUV*)(destPtr + uvOff[2]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
else
|
|
{
|
|
// Uv.
|
|
CUVW *start= (CUVW*)(startPtr + uvOff[2]);
|
|
CUVW *end= (CUVW*)(endPtr + uvOff[2]);
|
|
CUVW *dst= (CUVW*)(destPtr + uvOff[2]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
}
|
|
// uv[3].
|
|
if(uvOff[3])
|
|
{
|
|
if (!has3Coords[3])
|
|
{
|
|
// Uv.
|
|
CUV *start= (CUV*)(startPtr + uvOff[3]);
|
|
CUV *end= (CUV*)(endPtr + uvOff[3]);
|
|
CUV *dst= (CUV*)(destPtr + uvOff[3]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
else
|
|
{
|
|
// Uv.
|
|
CUVW *start= (CUVW*)(startPtr + uvOff[3]);
|
|
CUVW *end= (CUVW*)(endPtr + uvOff[3]);
|
|
CUVW *dst= (CUVW*)(destPtr + uvOff[3]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
}
|
|
// color.
|
|
if(colorOff)
|
|
{
|
|
CRGBA *start= (CRGBA*)(startPtr + colorOff);
|
|
CRGBA *end= (CRGBA*)(endPtr + colorOff);
|
|
CRGBA *dst= (CRGBA*)(destPtr + colorOff);
|
|
dst->blendFromui(*start, *end, ua1);
|
|
}
|
|
// specular.
|
|
if(specularOff)
|
|
{
|
|
CRGBA *start= (CRGBA*)(startPtr + specularOff);
|
|
CRGBA *end= (CRGBA*)(endPtr + specularOff);
|
|
CRGBA *dst= (CRGBA*)(destPtr + specularOff);
|
|
dst->blendFromui(*start, *end, ua1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Process extra UVs (maybe never, so don't bother optims :)).
|
|
// For all stages after 4.
|
|
for(i=4;i<CVertexBuffer::MaxStage;i++)
|
|
{
|
|
uint nGeoms= (uint)geoms.size();
|
|
CMRMWedgeGeom *ptrGeom= &(geoms[0]);
|
|
uint8 *destPtr= vertexDestPtr;
|
|
|
|
if(uvOff[i]==0)
|
|
continue;
|
|
|
|
// For all geomorphs.
|
|
for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize )
|
|
{
|
|
uint8 *startPtr= vertexPtr + ptrGeom->Start*vertexSize;
|
|
uint8 *endPtr= vertexPtr + ptrGeom->End*vertexSize;
|
|
|
|
// uv[i].
|
|
// Uv.
|
|
if (!has3Coords[i])
|
|
{
|
|
CUV *start= (CUV*)(startPtr + uvOff[i]);
|
|
CUV *end= (CUV*)(endPtr + uvOff[i]);
|
|
CUV *dst= (CUV*)(destPtr + uvOff[i]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
else
|
|
{
|
|
CUVW *start= (CUVW*)(startPtr + uvOff[i]);
|
|
CUVW *end= (CUVW*)(endPtr + uvOff[i]);
|
|
CUVW *dst= (CUVW*)(destPtr + uvOff[i]);
|
|
*dst= *start * a + *end * a1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::applyGeomorphPosNormalUV0(std::vector<CMRMWedgeGeom> &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 CMeshMRMGeom::initInstance(CMeshBaseInstance *mbi)
|
|
{
|
|
// init the instance with _MeshVertexProgram infos
|
|
if(_MeshVertexProgram)
|
|
_MeshVertexProgram->initInstance(mbi);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CMeshMRMGeom::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;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
inline sint CMeshMRMGeom::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);
|
|
}
|
|
|
|
|
|
// If lod chosen is not loaded, take the best loaded.
|
|
if(numLod>=(sint)_NbLodLoaded)
|
|
{
|
|
numLod= _NbLodLoaded-1;
|
|
alphaLod= 1;
|
|
}
|
|
|
|
return numLod;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::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<CMeshBaseInstance*>(trans);
|
|
// get a ptr on scene
|
|
CScene *ownerScene= mi->getOwnerScene();
|
|
// get a ptr on renderTrav
|
|
CRenderTrav *renderTrav= &ownerScene->getRenderTrav();
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
// Update the vertexBufferHard (if possible).
|
|
// \toto yoyo: TODO_OPTIMIZE: allocate only what is needed for the current Lod (Max of all instances, like
|
|
// the loading....) (see loadHeader()).
|
|
|
|
// 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_MeshMRMGeom_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(&_VBufferOriginal,
|
|
&_VBufferFinal,
|
|
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(&_VBufferOriginal,
|
|
&_VBufferFinal,
|
|
useTangentSpace);
|
|
_MeshMorpher.update (mi->getBlendShapeFactors());
|
|
}
|
|
}
|
|
|
|
// Skinning.
|
|
//===========
|
|
// if mesh is skinned (but here skin not applied), we must copy vertices/normals from original vertices.
|
|
if (_Skinned)
|
|
{
|
|
// do it for this Lod only, and if cache say it is necessary.
|
|
if (!lod.OriginalSkinRestored)
|
|
restoreOriginalSkinPart(lod);
|
|
}
|
|
|
|
|
|
// 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
|
|
//===========
|
|
|
|
// 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, mi->getOwnerScene(), mi, invertedObjectMatrix, renderTrav->CamPos);
|
|
}
|
|
|
|
|
|
// Render the lod.
|
|
//===========
|
|
// active VB.
|
|
drv->activeVertexBuffer(_VBufferFinal);
|
|
|
|
|
|
// 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;i<lod.RdrPass.size();i++)
|
|
{
|
|
CRdrPass &rdrPass= lod.RdrPass[i];
|
|
|
|
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, &_VBufferFinal);
|
|
}
|
|
|
|
// Render
|
|
drv->activeIndexBuffer(rdrPass.PBlock);
|
|
drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3);
|
|
|
|
// Resetup material/driver
|
|
blender.restoreRender(material, drv, gaDisableZWrite);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(uint i=0;i<lod.RdrPass.size();i++)
|
|
{
|
|
CRdrPass &rdrPass= lod.RdrPass[i];
|
|
|
|
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, &_VBufferFinal);
|
|
}
|
|
|
|
// Render with the Materials of the MeshInstance.
|
|
drv->activeIndexBuffer(rdrPass.PBlock);
|
|
drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// End VertexProgram effect
|
|
if(useMeshVP)
|
|
{
|
|
// end it.
|
|
_MeshVertexProgram->end(drv);
|
|
}
|
|
|
|
|
|
// bkup force normalisation.
|
|
drv->forceNormalize(bkupNorm);
|
|
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::renderSkin(CTransformShape *trans, float alphaMRM)
|
|
{
|
|
H_AUTO( NL3D_MeshMRMGeom_renderSkin );
|
|
|
|
if(_Lods.size()==0)
|
|
return;
|
|
|
|
|
|
// get the meshMRM instance. only CMeshMRMInstance is possible when skinned (not MultiLod)
|
|
CMeshMRMInstance *mi= safe_cast<CMeshMRMInstance*>(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);
|
|
|
|
|
|
// choose the lod.
|
|
float alphaLod;
|
|
sint numLod= chooseLod(alphaMRM, alphaLod);
|
|
|
|
|
|
// Render the choosen Lod.
|
|
CLod &lod= _Lods[numLod];
|
|
if(lod.RdrPass.size()==0)
|
|
return;
|
|
|
|
|
|
/*
|
|
YOYO: renderSkin() no more support vertexBufferHard()!!! for AGP Memory optimisation concern.
|
|
AGP Skin rendering is made when supportSkinGrouping() is true
|
|
Hence if a skin is to be rendered here, because it doesn't have a good vertex format, or it has
|
|
MeshVertexProgram etc..., it will be rendered WITHOUT VBHard => slower.
|
|
*/
|
|
|
|
|
|
// get the skeleton model to which I am skinned
|
|
CSkeletonModel *skeleton;
|
|
skeleton = mi->getSkeletonModel();
|
|
// must be skinned for renderSkin()
|
|
nlassert(_Skinned && mi->isSkinned() && skeleton);
|
|
bool bMorphApplied = _MeshMorpher.BlendShapes.size() > 0;
|
|
bool useNormal= (_VBufferFinal.getVertexFormat() & CVertexBuffer::NormalFlag)!=0;
|
|
bool useTangentSpace = _MeshVertexProgram && _MeshVertexProgram->needTangentSpace();
|
|
|
|
|
|
// Profiling
|
|
//===========
|
|
H_AUTO( NL3D_MeshMRMGeom_renderSkin_go );
|
|
|
|
|
|
// Morphing
|
|
// ========
|
|
if (bMorphApplied)
|
|
{
|
|
// Since Skinned we must update original skin vertices and normals because skinning use it
|
|
_MeshMorpher.initSkinned(&_VBufferOriginal,
|
|
&_VBufferFinal,
|
|
useTangentSpace,
|
|
&_OriginalSkinVertices,
|
|
&_OriginalSkinNormals,
|
|
useTangentSpace ? &_OriginalTGSpace : NULL,
|
|
true );
|
|
_MeshMorpher.updateSkinned (mi->getBlendShapeFactors());
|
|
}
|
|
|
|
// Skinning.
|
|
//===========
|
|
|
|
// Never use RawSkin. Actually used in skinGrouping.
|
|
updateRawSkinNormal(false, mi, numLod);
|
|
|
|
// applySkin.
|
|
//--------
|
|
|
|
// If skin without normal (rare/useful?) always simple (slow) case.
|
|
if(!useNormal)
|
|
{
|
|
// skinning with just position
|
|
applySkin (lod, skeleton);
|
|
}
|
|
else
|
|
{
|
|
// apply skin for this Lod only.
|
|
if (!useTangentSpace)
|
|
{
|
|
// skinning with normal, but no tangent space
|
|
applySkinWithNormal (lod, skeleton);
|
|
}
|
|
else
|
|
{
|
|
// Tangent space stored in the last texture coordinate
|
|
applySkinWithTangentSpace(lod, skeleton, _VBufferFinal.getNumTexCoordUsed() - 1);
|
|
}
|
|
}
|
|
|
|
// endSkin.
|
|
//--------
|
|
// dirt this lod part. (NB: this is not optimal, but sufficient :) ).
|
|
lod.OriginalSkinRestored= false;
|
|
|
|
|
|
// NB: the skeleton matrix has already been setuped by CSkeletonModel
|
|
// NB: the normalize flag has already been setuped by CSkeletonModel
|
|
|
|
|
|
// Geomorph.
|
|
//===========
|
|
// Geomorph the choosen Lod (if not the coarser mesh).
|
|
if(numLod>0)
|
|
{
|
|
applyGeomorph(lod.Geomorphs, alphaLod);
|
|
}
|
|
|
|
|
|
// 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, mi->getOwnerScene(), mi, invertedObjectMatrix, renderTrav->CamPos);
|
|
}
|
|
|
|
|
|
// Render the lod.
|
|
//===========
|
|
// active VB.
|
|
drv->activeVertexBuffer(_VBufferFinal);
|
|
|
|
|
|
// Render all pass.
|
|
for(uint i=0;i<lod.RdrPass.size();i++)
|
|
{
|
|
CRdrPass &rdrPass= lod.RdrPass[i];
|
|
|
|
// CMaterial Ref
|
|
CMaterial &material=mi->Materials[rdrPass.MaterialId];
|
|
|
|
// Setup VP material
|
|
if (useMeshVP)
|
|
{
|
|
_MeshVertexProgram->setupForMaterial(material, drv, ownerScene, &_VBufferFinal);
|
|
}
|
|
|
|
// Render with the Materials of the MeshInstance.
|
|
drv->activeIndexBuffer(rdrPass.PBlock);
|
|
drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3);
|
|
}
|
|
|
|
// End VertexProgram effect
|
|
if(useMeshVP)
|
|
{
|
|
// end it.
|
|
_MeshVertexProgram->end(drv);
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CMeshMRMGeom::supportSkinGrouping() const
|
|
{
|
|
return _SupportSkinGrouping;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
sint CMeshMRMGeom::renderSkinGroupGeom(CMeshMRMInstance *mi, float alphaMRM, uint remainingVertices, uint8 *vbDest)
|
|
{
|
|
H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpGeom )
|
|
|
|
// NB: not need to test if _Lods.empty(), because already done through supportSkinGrouping()
|
|
|
|
// 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(_Skinned && mi->isSkinned() && skeleton);
|
|
bool bMorphApplied = _MeshMorpher.BlendShapes.size() > 0;
|
|
bool useNormal= (_VBufferFinal.getVertexFormat() & CVertexBuffer::NormalFlag)!=0;
|
|
nlassert(useNormal);
|
|
|
|
|
|
// Profiling
|
|
//===========
|
|
H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpGeom_go );
|
|
|
|
|
|
// Morphing
|
|
// ========
|
|
|
|
// Use RawSkin?. compute before morphing, cause of updateRawSkin
|
|
updateRawSkinNormal(true, mi, numLod);
|
|
nlassert(mi->_RawSkinCache);
|
|
|
|
// Apply morph
|
|
if (bMorphApplied)
|
|
{
|
|
// No need to manage lod.OriginalSkinRestored, since in case of SkinGroupGeom, the VBuffer final is not modified.
|
|
|
|
// copy directly from the original VB, and apply BlendShapes. Dest is directly the RawSkin
|
|
_MeshMorpher.updateRawSkin(&_VBufferFinal,
|
|
mi->_RawSkinCache->VertexRemap,
|
|
mi->getBlendShapeFactors());
|
|
}
|
|
|
|
// Skinning.
|
|
//===========
|
|
|
|
// NB: the skeleton matrix has already been setuped by CSkeletonModel
|
|
// NB: the normalize flag has already been setuped by CSkeletonModel
|
|
|
|
// applySkin with RawSkin.
|
|
//--------
|
|
// always RawSkin now in SkinGrouping, even with MeshMorpher
|
|
{
|
|
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 CMeshMRMGeom::renderSkinGroupPrimitives(CMeshMRMInstance *mi, uint baseVertex, std::vector<CSkinSpecularRdrPass> &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;i<lod.RdrPass.size();i++)
|
|
{
|
|
CRdrPass &rdrPass= lod.RdrPass[i];
|
|
|
|
// CMaterial Ref
|
|
CMaterial &material=mi->Materials[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<ITexture> pTexFile= new CTextureFile("fy_hom_visage_c1_fy_e1.tga");
|
|
material.setTexture(0, pTexFile );
|
|
// Remove Specular.
|
|
if(material.getShader()==CMaterial::Specular)
|
|
{
|
|
CSmartPtr<ITexture> 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 CMeshMRMGeom::renderSkinGroupSpecularRdrPass(CMeshMRMInstance *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 CMeshMRMGeom::updateShiftedTriangleCache(CMeshMRMInstance *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<CIndexBuffer*> pbList;
|
|
pbList.clear();
|
|
if(mi->_RawSkinCache)
|
|
{
|
|
pbList.resize(mi->_RawSkinCache->RdrPass.size());
|
|
for(uint i=0;i<pbList.size();i++)
|
|
{
|
|
pbList[i]= &mi->_RawSkinCache->RdrPass[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CLod &lod= _Lods[curLodId];
|
|
pbList.resize(lod.RdrPass.size());
|
|
for(uint i=0;i<pbList.size();i++)
|
|
{
|
|
pbList[i]= &lod.RdrPass[i].PBlock;
|
|
}
|
|
}
|
|
|
|
// 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<pbList.size();i++)
|
|
{
|
|
mi->_ShiftedTriangleCache->RdrPass[i].NumTriangles= pbList[i]->getNumIndexes()/3;
|
|
totalTri+= pbList[i]->getNumIndexes()/3;
|
|
}
|
|
|
|
// Allocate triangles indices.
|
|
mi->_ShiftedTriangleCache->RawIndices.setFormat(NL_MESH_MRM_INDEX_FORMAT);
|
|
mi->_ShiftedTriangleCache->RawIndices.setNumIndexes(totalTri*3);
|
|
|
|
// Lock the index buffer
|
|
CIndexBufferReadWrite ibaWrite;
|
|
mi->_ShiftedTriangleCache->RawIndices.lock (ibaWrite);
|
|
if (ibaWrite.getFormat() == CIndexBuffer::Indices32)
|
|
{
|
|
uint32 *dstPtr = (uint32 *) ibaWrite.getPtr();
|
|
// Second pass, fill ptrs, and fill Arrays
|
|
uint indexTri= 0;
|
|
for(i=0;i<pbList.size();i++)
|
|
{
|
|
CShiftedTriangleCache::CRdrPass &dstRdrPass= mi->_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);
|
|
nlassert(ibaRead.getFormat() == CIndexBuffer::Indices32);
|
|
const uint32 *pSrcTri= (const uint32 *) ibaRead.getPtr();
|
|
uint32 *pDstTri= dstPtr+dstRdrPass.Triangles;
|
|
for(;nIds>0;nIds--,pSrcTri++,pDstTri++)
|
|
*pDstTri= *pSrcTri + baseVertex;
|
|
}
|
|
|
|
// Next
|
|
indexTri+= dstRdrPass.NumTriangles;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nlassert(ibaWrite.getFormat() == CIndexBuffer::Indices16);
|
|
uint16 *dstPtr = (uint16 *) ibaWrite.getPtr();
|
|
// Second pass, fill ptrs, and fill Arrays
|
|
uint indexTri= 0;
|
|
for(i=0;i<pbList.size();i++)
|
|
{
|
|
CShiftedTriangleCache::CRdrPass &dstRdrPass= mi->_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);
|
|
nlassert(ibaRead.getFormat() == CIndexBuffer::Indices16);
|
|
const uint16 *pSrcTri= (const uint16 *) ibaRead.getPtr();
|
|
uint16 *pDstTri= (uint16 *) dstPtr+dstRdrPass.Triangles;
|
|
for(;nIds>0;nIds--,pSrcTri++,pDstTri++)
|
|
*pDstTri= *pSrcTri + baseVertex;
|
|
}
|
|
|
|
// Next
|
|
indexTri+= dstRdrPass.NumTriangles;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
|
|
{
|
|
// because of complexity, serial is separated in save / load.
|
|
|
|
if(f.isReading())
|
|
load(f);
|
|
else
|
|
save(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
sint CMeshMRMGeom::loadHeader(NLMISC::IStream &f) throw(NLMISC::EStream)
|
|
{
|
|
/*
|
|
Version 5:
|
|
- Shadow Skinning
|
|
Version 4:
|
|
- serial SkinWeights per MRM, not per Lod
|
|
Version 3:
|
|
- Bones names.
|
|
Version 2:
|
|
- Mesh Vertex Program.
|
|
Version 1:
|
|
- added blend shapes
|
|
Version 0:
|
|
- base version.
|
|
*/
|
|
sint ver= f.serialVersion(5);
|
|
|
|
|
|
// if >= version 3, serial boens names
|
|
if(ver>=3)
|
|
{
|
|
f.serialCont (_BonesName);
|
|
}
|
|
|
|
// Version3-: Bones index are in skeleton model id list
|
|
_BoneIdComputed = (ver < 3);
|
|
// Must always recompute usage of parents of bones used.
|
|
_BoneIdExtended = false;
|
|
|
|
// Mesh Vertex Program.
|
|
if (ver >= 2)
|
|
{
|
|
IMeshVertexProgram *mvp= NULL;
|
|
f.serialPolyPtr(mvp);
|
|
_MeshVertexProgram= mvp;
|
|
}
|
|
else
|
|
{
|
|
// release vp
|
|
_MeshVertexProgram= NULL;
|
|
}
|
|
|
|
// blend shapes
|
|
if (ver >= 1)
|
|
f.serial (_MeshMorpher);
|
|
|
|
// serial Basic info.
|
|
// ==================
|
|
f.serial(_Skinned);
|
|
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);
|
|
// preload the Lods.
|
|
f.serialCont(_LodInfos);
|
|
|
|
// read/save number of wedges.
|
|
/* NB: prepare memory space too for vertices.
|
|
\todo yoyo: TODO_OPTIMIZE. for now there is no Lod memory profit with vertices / skinWeights.
|
|
But resizing arrays is a problem because of reallocation...
|
|
*/
|
|
uint32 nWedges;
|
|
f.serial(nWedges);
|
|
// Prepare the VBuffer.
|
|
_VBufferFinal.serialHeader(f);
|
|
// If skinned, must allocate skinWeights.
|
|
contReset(_SkinWeights);
|
|
if(_Skinned)
|
|
{
|
|
_SkinWeights.resize(nWedges);
|
|
}
|
|
|
|
|
|
// If new version, serial SkinWeights in header, not in lods.
|
|
if(ver >= 4)
|
|
{
|
|
f.serialCont(_SkinWeights);
|
|
}
|
|
|
|
|
|
// if >= version 5, serial Shadow Skin Information
|
|
if(ver>=5)
|
|
{
|
|
f.serialCont (_ShadowSkin.Vertices);
|
|
f.serialCont (_ShadowSkin.Triangles);
|
|
}
|
|
|
|
|
|
// Serial lod offsets.
|
|
// ==================
|
|
// This is the reference pos, to load / save relative offsets.
|
|
sint32 startPos = f.getPos();
|
|
// Those are the lodOffsets, relative to startPos.
|
|
vector<sint32> lodOffsets;
|
|
lodOffsets.resize(_LodInfos.size(), 0);
|
|
|
|
// read all relative offsets, and build the absolute offset of LodInfos.
|
|
for(uint i=0;i<_LodInfos.size(); i++)
|
|
{
|
|
f.serial(lodOffsets[i]);
|
|
_LodInfos[i].LodOffset= startPos + lodOffsets[i];
|
|
}
|
|
|
|
|
|
// resest the Lod arrays. NB: each Lod is empty, and ready to receive Lod data.
|
|
// ==================
|
|
contReset(_Lods);
|
|
_Lods.resize(_LodInfos.size());
|
|
|
|
// Flag the fact that no lod is loaded for now.
|
|
_NbLodLoaded= 0;
|
|
|
|
// Inform that the mesh data has changed
|
|
dirtMeshDataId();
|
|
|
|
|
|
// Some runtime not serialized compilation
|
|
compileRunTime();
|
|
|
|
// return version of the header
|
|
return ver;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::load(NLMISC::IStream &f) throw(NLMISC::EStream)
|
|
{
|
|
// Load the header of the stream.
|
|
// ==================
|
|
sint verHeader= loadHeader(f);
|
|
|
|
// Read All lod subsets.
|
|
// ==================
|
|
for(uint i=0;i<_LodInfos.size(); i++)
|
|
{
|
|
// read the lod face data.
|
|
f.serial(_Lods[i]);
|
|
// read the lod vertex data.
|
|
serialLodVertexData(f, _LodInfos[i].StartAddWedge, _LodInfos[i].EndAddWedges);
|
|
// if reading, must bkup all original vertices from VB.
|
|
// this is done in serialLodVertexData(). by subset
|
|
}
|
|
|
|
// Now, all lods are loaded.
|
|
_NbLodLoaded= (uint)_Lods.size();
|
|
|
|
// If version doen't have boneNames, must build BoneId now.
|
|
if(verHeader <= 2)
|
|
{
|
|
buildBoneUsageVer2 ();
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::save(NLMISC::IStream &f) throw(NLMISC::EStream)
|
|
{
|
|
/*
|
|
Version 5:
|
|
- Shadow Skinning
|
|
Version 4:
|
|
- serial SkinWeights per MRM, not per Lod
|
|
Version 3:
|
|
- Bones names.
|
|
Version 2:
|
|
- Mesh Vertex Program.
|
|
Version 1:
|
|
- added blend shapes
|
|
Version 0:
|
|
- base version.
|
|
*/
|
|
sint ver= f.serialVersion(5);
|
|
uint i;
|
|
|
|
// if >= version 3, serial bones names
|
|
f.serialCont (_BonesName);
|
|
|
|
// Warning, if you have skinned this shape, you can't write it anymore because skinning id have been changed!
|
|
nlassert (_BoneIdComputed==false);
|
|
|
|
// Mesh Vertex Program.
|
|
if (ver >= 2)
|
|
{
|
|
IMeshVertexProgram *mvp= NULL;
|
|
mvp= _MeshVertexProgram;
|
|
f.serialPolyPtr(mvp);
|
|
}
|
|
|
|
// blend shapes
|
|
if (ver >= 1)
|
|
f.serial (_MeshMorpher);
|
|
|
|
// must have good original Skinned Vertex before writing.
|
|
if( _Skinned )
|
|
{
|
|
restoreOriginalSkinVertices();
|
|
}
|
|
|
|
|
|
// serial Basic info.
|
|
// ==================
|
|
f.serial(_Skinned);
|
|
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);
|
|
f.serialCont(_LodInfos);
|
|
|
|
// save number of wedges.
|
|
uint32 nWedges;
|
|
nWedges= _VBufferFinal.getNumVertices();
|
|
f.serial(nWedges);
|
|
// Save the VBuffer header.
|
|
_VBufferFinal.serialHeader(f);
|
|
|
|
|
|
// If new version, serial SkinWeights in header, not in lods.
|
|
if(ver >= 4)
|
|
{
|
|
f.serialCont(_SkinWeights);
|
|
}
|
|
|
|
// if >= version 5, serial Shadow Skin Information
|
|
if(ver>=5)
|
|
{
|
|
f.serialCont (_ShadowSkin.Vertices);
|
|
f.serialCont (_ShadowSkin.Triangles);
|
|
}
|
|
|
|
// Serial lod offsets.
|
|
// ==================
|
|
// This is the reference pos, to load / save relative offsets.
|
|
sint32 startPos = f.getPos();
|
|
// Those are the lodOffsets, relative to startPos.
|
|
vector<sint32> lodOffsets;
|
|
lodOffsets.resize(_LodInfos.size(), 0);
|
|
|
|
// write all dummy offset. For now (since we don't know what to set), compute the offset of
|
|
// the sint32 to come back in serial lod parts below.
|
|
for(i=0;i<_LodInfos.size(); i++)
|
|
{
|
|
lodOffsets[i]= f.getPos();
|
|
f.serial(lodOffsets[i]);
|
|
}
|
|
|
|
// Serial lod subsets.
|
|
// ==================
|
|
|
|
// Save all the lods.
|
|
for(i=0;i<_LodInfos.size(); i++)
|
|
{
|
|
// get current absolute position.
|
|
sint32 absCurPos= f.getPos();
|
|
|
|
// come back to "relative lodOffset" absolute position in the stream. (temp stored in lodOffset[i]).
|
|
f.seek(lodOffsets[i], NLMISC::IStream::begin);
|
|
|
|
// write the relative position of the lod to the stream.
|
|
sint32 relCurPos= absCurPos - startPos;
|
|
f.serial(relCurPos);
|
|
|
|
// come back to absCurPos, to save the lod.
|
|
f.seek(absCurPos, NLMISC::IStream::begin);
|
|
|
|
// And so now, save the lod.
|
|
// write the lod face data.
|
|
f.serial(_Lods[i]);
|
|
// write the lod vertex data.
|
|
serialLodVertexData(f, _LodInfos[i].StartAddWedge, _LodInfos[i].EndAddWedges);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::serialLodVertexData(NLMISC::IStream &f, uint startWedge, uint endWedge)
|
|
{
|
|
/*
|
|
Version 1:
|
|
- serial SkinWeights per MRM, not per Lod
|
|
*/
|
|
sint ver= f.serialVersion(1);
|
|
|
|
// VBuffer part.
|
|
_VBufferFinal.serialSubset(f, startWedge, endWedge);
|
|
|
|
// SkinWeights.
|
|
if(_Skinned)
|
|
{
|
|
// Serialize SkinWeight per lod only for old versions.
|
|
if(ver<1)
|
|
{
|
|
for(uint i= startWedge; i<endWedge; i++)
|
|
{
|
|
f.serial(_SkinWeights[i]);
|
|
}
|
|
}
|
|
// if reading, must copy original vertices from VB.
|
|
if( f.isReading())
|
|
{
|
|
bkupOriginalSkinVerticesSubset(startWedge, endWedge);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::loadFirstLod(NLMISC::IStream &f)
|
|
{
|
|
// Load the header of the stream.
|
|
// ==================
|
|
sint verHeader= loadHeader(f);
|
|
|
|
|
|
// If empty MRM, quit.
|
|
if(_LodInfos.size()==0)
|
|
return;
|
|
|
|
/* If the version is <4, then SkinWeights are serialised per Lod.
|
|
But for computebonesId(), we must have all SkinWeights RIGHT NOW.
|
|
Hence, if too old version (<4), serialize all the MRM....
|
|
*/
|
|
uint numLodToLoad;
|
|
if(verHeader<4)
|
|
numLodToLoad= (uint)_LodInfos.size();
|
|
else
|
|
numLodToLoad= 1;
|
|
|
|
|
|
// Read lod subset(s).
|
|
// ==================
|
|
for(uint i=0;i<numLodToLoad; i++)
|
|
{
|
|
// read the lod face data.
|
|
f.serial(_Lods[i]);
|
|
// read the lod vertex data.
|
|
serialLodVertexData(f, _LodInfos[i].StartAddWedge, _LodInfos[i].EndAddWedges);
|
|
// if reading, must bkup all original vertices from VB.
|
|
// this is done in serialLodVertexData(). by subset
|
|
}
|
|
|
|
// Now, just first lod is loaded (but if too old file)
|
|
_NbLodLoaded= numLodToLoad;
|
|
|
|
// If version doen't have boneNames, must build BoneId now.
|
|
if(verHeader <= 2)
|
|
{
|
|
buildBoneUsageVer2 ();
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::loadNextLod(NLMISC::IStream &f)
|
|
{
|
|
// If all is loaded, quit.
|
|
if(getNbLodLoaded() == getNbLod())
|
|
return;
|
|
|
|
// Set pos to good lod.
|
|
f.seek(_LodInfos[_NbLodLoaded].LodOffset, NLMISC::IStream::begin);
|
|
|
|
// Serial this lod data.
|
|
// read the lod face data.
|
|
f.serial(_Lods[_NbLodLoaded]);
|
|
// read the lod vertex data.
|
|
serialLodVertexData(f, _LodInfos[_NbLodLoaded].StartAddWedge, _LodInfos[_NbLodLoaded].EndAddWedges);
|
|
// if reading, must bkup all original vertices from VB.
|
|
// this is done in serialLodVertexData(). by subset
|
|
|
|
|
|
// Inc LodLoaded count.
|
|
_NbLodLoaded++;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::unloadNextLod(NLMISC::IStream &f)
|
|
{
|
|
// If just first lod remain (or no lod), quit
|
|
if(getNbLodLoaded() <= 1)
|
|
return;
|
|
|
|
// Reset the entire Lod object. (Free Memory).
|
|
contReset(_Lods[_NbLodLoaded-1]);
|
|
|
|
|
|
// Dec LodLoaded count.
|
|
_NbLodLoaded--;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::bkupOriginalSkinVertices()
|
|
{
|
|
nlassert(_Skinned);
|
|
|
|
// bkup the entire array.
|
|
bkupOriginalSkinVerticesSubset(0, _VBufferFinal.getNumVertices());
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::bkupOriginalSkinVerticesSubset(uint wedgeStart, uint wedgeEnd)
|
|
{
|
|
nlassert(_Skinned);
|
|
|
|
CVertexBufferReadWrite vba;
|
|
_VBufferFinal.lock (vba);
|
|
|
|
// Copy VBuffer content into Original vertices normals.
|
|
if(_VBufferFinal.getVertexFormat() & CVertexBuffer::PositionFlag)
|
|
{
|
|
// copy vertices from VBuffer. (NB: useless geomorphed vertices are still copied, but doesn't matter).
|
|
_OriginalSkinVertices.resize(_VBufferFinal.getNumVertices());
|
|
for(uint i=wedgeStart; i<wedgeEnd;i++)
|
|
{
|
|
_OriginalSkinVertices[i]= *vba.getVertexCoordPointer(i);
|
|
}
|
|
}
|
|
if(_VBufferFinal.getVertexFormat() & CVertexBuffer::NormalFlag)
|
|
{
|
|
// copy normals from VBuffer. (NB: useless geomorphed normals are still copied, but doesn't matter).
|
|
_OriginalSkinNormals.resize(_VBufferFinal.getNumVertices());
|
|
for(uint i=wedgeStart; i<wedgeEnd;i++)
|
|
{
|
|
_OriginalSkinNormals[i]= *vba.getNormalCoordPointer(i);
|
|
}
|
|
}
|
|
|
|
// is there tangent space added ?
|
|
if (_MeshVertexProgram && _MeshVertexProgram->needTangentSpace())
|
|
{
|
|
// yes, backup it
|
|
nlassert(_VBufferFinal.getNumTexCoordUsed() > 0);
|
|
uint tgSpaceStage = _VBufferFinal.getNumTexCoordUsed() - 1;
|
|
_OriginalTGSpace.resize(_VBufferFinal.getNumVertices());
|
|
for(uint i=wedgeStart; i<wedgeEnd;i++)
|
|
{
|
|
_OriginalTGSpace[i]= *(CVector*)vba.getTexCoordPointer(i, tgSpaceStage);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::restoreOriginalSkinVertices()
|
|
{
|
|
nlassert(_Skinned);
|
|
|
|
CVertexBufferReadWrite vba;
|
|
_VBufferFinal.lock (vba);
|
|
|
|
// Copy VBuffer content into Original vertices normals.
|
|
if(_VBufferFinal.getVertexFormat() & CVertexBuffer::PositionFlag)
|
|
{
|
|
// copy vertices from VBuffer. (NB: useless geomorphed vertices are still copied, but doesn't matter).
|
|
for(uint i=0; i<_VBufferFinal.getNumVertices();i++)
|
|
{
|
|
*vba.getVertexCoordPointer(i)= _OriginalSkinVertices[i];
|
|
}
|
|
}
|
|
if(_VBufferFinal.getVertexFormat() & CVertexBuffer::NormalFlag)
|
|
{
|
|
// copy normals from VBuffer. (NB: useless geomorphed normals are still copied, but doesn't matter).
|
|
for(uint i=0; i<_VBufferFinal.getNumVertices();i++)
|
|
{
|
|
*vba.getNormalCoordPointer(i)= _OriginalSkinNormals[i];
|
|
}
|
|
}
|
|
if (_MeshVertexProgram && _MeshVertexProgram->needTangentSpace())
|
|
{
|
|
uint numTexCoords = _VBufferFinal.getNumTexCoordUsed();
|
|
nlassert(numTexCoords >= 2);
|
|
nlassert(_OriginalTGSpace.size() == _VBufferFinal.getNumVertices());
|
|
// copy tangent space vectors
|
|
for(uint i = 0; i < _VBufferFinal.getNumVertices(); ++i)
|
|
{
|
|
*(CVector*)vba.getTexCoordPointer(i, numTexCoords - 1)= _OriginalTGSpace[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::restoreOriginalSkinPart(CLod &lod)
|
|
{
|
|
nlassert(_Skinned);
|
|
|
|
|
|
/*
|
|
YOYO: _Skinned mrms no more support vertexBufferHard
|
|
see note in renderSkin()
|
|
*/
|
|
|
|
// get vertexPtr / normalOff.
|
|
//===========================
|
|
CVertexBufferReadWrite vba;
|
|
_VBufferFinal.lock (vba);
|
|
uint8 *destVertexPtr= (uint8*)vba.getVertexCoordPointer();
|
|
uint flags= _VBufferFinal.getVertexFormat();
|
|
sint32 vertexSize= _VBufferFinal.getVertexSize();
|
|
// must have XYZ.
|
|
nlassert(flags & CVertexBuffer::PositionFlag);
|
|
|
|
// Compute offset of each component of the VB.
|
|
sint32 normalOff;
|
|
if(flags & CVertexBuffer::NormalFlag)
|
|
normalOff= _VBufferFinal.getNormalOff();
|
|
else
|
|
normalOff= 0;
|
|
|
|
|
|
// compute src array.
|
|
CVector *srcVertexPtr;
|
|
CVector *srcNormalPtr= NULL;
|
|
srcVertexPtr= &_OriginalSkinVertices[0];
|
|
if(normalOff)
|
|
srcNormalPtr= &(_OriginalSkinNormals[0]);
|
|
|
|
|
|
// copy skinning.
|
|
//===========================
|
|
for(uint i=0;i<NL3D_MESH_SKINNING_MAX_MATRIX;i++)
|
|
{
|
|
uint nInf= (uint)lod.InfluencedVertices[i].size();
|
|
if( nInf==0 )
|
|
continue;
|
|
uint32 *infPtr= &(lod.InfluencedVertices[i][0]);
|
|
|
|
// for all InfluencedVertices only.
|
|
for(;nInf>0;nInf--, infPtr++)
|
|
{
|
|
uint index= *infPtr;
|
|
CVector *srcVertex= srcVertexPtr + index;
|
|
CVector *srcNormal= srcNormalPtr + index;
|
|
uint8 *dstVertexVB= destVertexPtr + index * vertexSize;
|
|
CVector *dstVertex= (CVector*)(dstVertexVB);
|
|
CVector *dstNormal= (CVector*)(dstVertexVB + normalOff);
|
|
|
|
|
|
// Vertex.
|
|
*dstVertex= *srcVertex;
|
|
// Normal.
|
|
if(normalOff)
|
|
*dstNormal= *srcNormal;
|
|
}
|
|
}
|
|
|
|
|
|
// clean this lod part. (NB: this is not optimal, but sufficient :) ).
|
|
lod.OriginalSkinRestored= true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
float CMeshMRMGeom::getNumTriangles (float distance)
|
|
{
|
|
// NB: this is an approximation, but this is continious.
|
|
return _LevelDetail.getNumTriangles(distance);
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::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 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
|
|
nlassert(_OriginalSkinVertices.size() == _SkinWeights.size());
|
|
static std::vector<CAABBox> boneBBoxes;
|
|
static std::vector<bool> 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;
|
|
for (vert=nGeomSpace; vert<_SkinWeights.size(); vert++)
|
|
{
|
|
// get the vertex position.
|
|
CVector vertex= _OriginalSkinVertices[vert];
|
|
|
|
// For each weight
|
|
uint weight;
|
|
for (weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++)
|
|
{
|
|
// Active ?
|
|
if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0))
|
|
{
|
|
// Check id
|
|
uint srcId= _SkinWeights[vert].MatrixId[weight];
|
|
nlassert (srcId < remap.size());
|
|
// remap
|
|
_SkinWeights[vert].MatrixId[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]<remap.size());
|
|
|
|
// Remap
|
|
_Lods[lod].MatrixInfluences[matrix] = remap[_Lods[lod].MatrixInfluences[matrix]];
|
|
}
|
|
}
|
|
|
|
// **** Remap Shadow Vertices.
|
|
for(vert=0;vert<_ShadowSkin.Vertices.size();vert++)
|
|
{
|
|
CShadowVertex &v= _ShadowSkin.Vertices[vert];
|
|
// Check id
|
|
nlassert (v.MatrixId < remap.size());
|
|
v.MatrixId= remap[v.MatrixId];
|
|
}
|
|
|
|
// 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 CMeshMRMGeom::buildBoneUsageVer2 ()
|
|
{
|
|
if(_Skinned)
|
|
{
|
|
// parse all vertices, couting MaxBoneId used.
|
|
uint32 maxBoneId= 0;
|
|
// for each vertex
|
|
uint vert;
|
|
for (vert=0; vert<_SkinWeights.size(); vert++)
|
|
{
|
|
// For each weight
|
|
for (uint weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++)
|
|
{
|
|
// Active ?
|
|
if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0))
|
|
{
|
|
maxBoneId= max(_SkinWeights[vert].MatrixId[weight], maxBoneId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// alloc an array of maxBoneId+1, reset to 0.
|
|
std::vector<uint8> boneUsage;
|
|
boneUsage.resize(maxBoneId+1, 0);
|
|
|
|
// reparse all vertices, counting usage for each bone.
|
|
for (vert=0; vert<_SkinWeights.size(); vert++)
|
|
{
|
|
// For each weight
|
|
for (uint weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++)
|
|
{
|
|
// Active ?
|
|
if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0))
|
|
{
|
|
// mark this bone as used.
|
|
boneUsage[_SkinWeights[vert].MatrixId[weight]]= 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::updateSkeletonUsage(CSkeletonModel *sm, bool increment)
|
|
{
|
|
// For all Bones used.
|
|
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 CMeshMRMGeom::compileRunTime()
|
|
{
|
|
_PreciseClipping= _BBox.getRadius() >= NL3D_MESH_PRECISE_CLIP_THRESHOLD;
|
|
|
|
// Compute if can support SkinGrouping rendering
|
|
if(_Lods.size()==0 || !_Skinned)
|
|
{
|
|
_SupportSkinGrouping= false;
|
|
_SupportShadowSkinGrouping= false;
|
|
}
|
|
else
|
|
{
|
|
// The Mesh must follow those restrictions, to support group skinning
|
|
_SupportSkinGrouping=
|
|
_VBufferFinal.getVertexFormat() == NL3D_MESH_SKIN_MANAGER_VERTEXFORMAT &&
|
|
_VBufferFinal.getNumVertices() < NL3D_MESH_SKIN_MANAGER_MAXVERTICES &&
|
|
!_MeshVertexProgram;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Support MeshBlockRendering only if not skinned/meshMorphed.
|
|
_SupportMeshBlockRendering= !_Skinned && _MeshMorpher.BlendShapes.size()==0;
|
|
|
|
// \todo yoyo: support later MeshVertexProgram
|
|
_SupportMeshBlockRendering= _SupportMeshBlockRendering && _MeshVertexProgram==NULL;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::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<CMeshBaseInstance*>(trans);
|
|
|
|
// Profile all pass.
|
|
uint triCount= 0;
|
|
for (uint i=0;i<lod.RdrPass.size();i++)
|
|
{
|
|
CRdrPass &rdrPass= lod.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.MeshMRMProfileTriVBFormat,
|
|
_VBufferFinal.getVertexFormat(), triCount);
|
|
|
|
// VBHard
|
|
if(_VBufferFinal.getPreferredMemory()!=CVertexBuffer::RAMPreferred)
|
|
rdrTrav->Scene->BenchRes.NumMeshMRMVBufferHard++;
|
|
else
|
|
rdrTrav->Scene->BenchRes.NumMeshMRMVBufferStd++;
|
|
|
|
// 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.NumMeshMRMRdrBlockWithVBHeap++;
|
|
rdrTrav->Scene->BenchRes.NumMeshMRMTriRdrBlockWithVBHeap+= triCount;
|
|
}
|
|
else
|
|
{
|
|
rdrTrav->Scene->BenchRes.NumMeshMRMRdrBlock++;
|
|
rdrTrav->Scene->BenchRes.NumMeshMRMTriRdrBlock+= triCount;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rdrTrav->Scene->BenchRes.NumMeshMRMRdrNormal++;
|
|
rdrTrav->Scene->BenchRes.NumMeshMRMTriRdrNormal+= triCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CMeshMRMGeom::buildGeometryForLod(uint lodId, std::vector<CVector> &vertices, std::vector<uint32> &triangles) const
|
|
{
|
|
if(lodId>=_Lods.size())
|
|
return false;
|
|
|
|
// avoid slow lock
|
|
if(_VBufferFinal.isResident())
|
|
return false;
|
|
|
|
// clear first
|
|
vertices.clear();
|
|
triangles.clear();
|
|
|
|
|
|
// **** First, for the lod indicate what vertex is used or not. Also index geomorphs to know what real vertex is used
|
|
static vector<sint> vertexRemap;
|
|
vertexRemap.clear();
|
|
// -1 means "not used"
|
|
vertexRemap.resize(_VBufferFinal.getNumVertices(), -1);
|
|
// count also total number of indices
|
|
uint numTotalIndices= 0;
|
|
// Parse all triangles.
|
|
uint i;
|
|
for(i=0;i<getNbRdrPass(lodId);i++)
|
|
{
|
|
const CIndexBuffer &pb = getRdrPassPrimitiveBlock(lodId, i);
|
|
// avoid slow lock
|
|
if(pb.isResident())
|
|
return false;
|
|
CIndexBufferRead iba;
|
|
pb.lock (iba);
|
|
nlassert(iba.getFormat()==NL_MESH_MRM_INDEX_FORMAT);
|
|
TMeshMRMIndexType *src= (TMeshMRMIndexType *) iba.getPtr();
|
|
for(uint n= pb.getNumIndexes();n>0;n--, src++)
|
|
{
|
|
uint idx= *src;
|
|
// Flag the vertex with its own index => used.
|
|
vertexRemap[idx]= idx;
|
|
}
|
|
|
|
// total num indices
|
|
numTotalIndices+= pb.getNumIndexes();
|
|
}
|
|
// Special for Geomorphs: must take The End target vertex.
|
|
const std::vector<CMRMWedgeGeom> &geomorphs= getGeomorphs(lodId);
|
|
for(i=0;i<geomorphs.size();i++)
|
|
{
|
|
uint trueIdx= geomorphs[i].End;
|
|
// map to the Geomorph Target.
|
|
vertexRemap[i]= trueIdx;
|
|
// mark also the real vertex used as used.
|
|
vertexRemap[trueIdx]= trueIdx;
|
|
}
|
|
// if no index, abort
|
|
if(numTotalIndices==0)
|
|
return false;
|
|
|
|
|
|
// **** count number of vertices really used (skip geomorphs)
|
|
uint numUsedVertices=0;
|
|
for(i=(uint)geomorphs.size();i<vertexRemap.size();i++)
|
|
{
|
|
if(vertexRemap[i]>=0)
|
|
numUsedVertices++;
|
|
}
|
|
// if none, abort
|
|
if(numUsedVertices==0)
|
|
return false;
|
|
// Then we have our vertices size
|
|
vertices.resize(numUsedVertices);
|
|
|
|
|
|
// **** Fill the vertex geometry
|
|
{
|
|
// Lock input VB
|
|
CVertexBufferRead vba;
|
|
_VBufferFinal.lock(vba);
|
|
|
|
// get the start vert, beginning at end of geomorphs
|
|
const uint8 *pSrcVert= (const uint8*)vba.getVertexCoordPointer((uint)geomorphs.size());
|
|
uint32 vertSize= _VBufferFinal.getVertexSize();
|
|
CVector *pDstVert= &vertices[0];
|
|
uint dstIndex= 0;
|
|
|
|
// Then run all input vertices (skip geomorphs)
|
|
for(i=(uint)geomorphs.size();i<vertexRemap.size();i++)
|
|
{
|
|
// if the vertex is used
|
|
if(vertexRemap[i]>=0)
|
|
{
|
|
// Final remaping of vertex to final index
|
|
vertexRemap[i]= dstIndex;
|
|
// copy to dest
|
|
*pDstVert= *(CVector*)pSrcVert;
|
|
|
|
// next dest
|
|
pDstVert++;
|
|
dstIndex++;
|
|
}
|
|
|
|
// next src
|
|
pSrcVert+= vertSize;
|
|
}
|
|
|
|
// should have fill the entire buffer
|
|
nlassert(uint(pDstVert-(&vertices[0]))==numUsedVertices);
|
|
|
|
// Resolve remapping for geomorphs
|
|
for(i=0;i<geomorphs.size();i++)
|
|
{
|
|
// use the same final index as the EndGeomorph vertex
|
|
uint endGeomIdx= vertexRemap[i];
|
|
vertexRemap[i]= vertexRemap[endGeomIdx];
|
|
}
|
|
}
|
|
|
|
|
|
// **** Fill the indices
|
|
triangles.resize(numTotalIndices);
|
|
uint32 *pDstIndex= &triangles[0];
|
|
// for all render pass
|
|
for(i=0;i<getNbRdrPass(lodId);i++)
|
|
{
|
|
const CIndexBuffer &pb = getRdrPassPrimitiveBlock(lodId, i);
|
|
CIndexBufferRead iba;
|
|
pb.lock (iba);
|
|
TMeshMRMIndexType *src= (TMeshMRMIndexType *) iba.getPtr();
|
|
for(uint n= pb.getNumIndexes();n>0;n--, src++)
|
|
{
|
|
// resolve any geomorph, and final remapping
|
|
uint remapedIdx= vertexRemap[*src];
|
|
nlassert(remapedIdx<vertices.size());
|
|
*pDstIndex= remapedIdx;
|
|
pDstIndex++;
|
|
}
|
|
}
|
|
// should have filled the entire buffer
|
|
nlassert(uint(pDstIndex-(&triangles[0]))==numTotalIndices);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
// Mesh Block Render Interface
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CMeshMRMGeom::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;
|
|
//return _SupportMeshBlockRendering;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CMeshMRMGeom::sortPerMaterial() const
|
|
{
|
|
// Can't do it per material since 2 lods may not have the same number of RdrPass!!
|
|
return false;
|
|
}
|
|
// ***************************************************************************
|
|
uint CMeshMRMGeom::getNumRdrPassesForMesh() const
|
|
{
|
|
// not used...
|
|
return 0;
|
|
|
|
}
|
|
// ***************************************************************************
|
|
uint CMeshMRMGeom::getNumRdrPassesForInstance(CMeshBaseInstance *inst) const
|
|
{
|
|
return (uint)_Lods[_MBRCurrentLodId].RdrPass.size();
|
|
}
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::beginMesh(CMeshGeomRenderContext &rdrCtx)
|
|
{
|
|
if(_Lods.empty())
|
|
return;
|
|
|
|
IDriver *drv= rdrCtx.Driver;
|
|
|
|
if(rdrCtx.RenderThroughVBHeap)
|
|
{
|
|
// Don't setup VB in this case, since use the VBHeap setuped one.
|
|
}
|
|
else
|
|
{
|
|
drv->activeVertexBuffer(_VBufferFinal);
|
|
}
|
|
|
|
|
|
// force normalisation of normals..
|
|
_MBRBkupNormalize= drv->isForceNormalize();
|
|
drv->forceNormalize(true);
|
|
|
|
}
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::activeInstance(CMeshGeomRenderContext &rdrCtx, CMeshBaseInstance *inst, float polygonCount, void *vbDst)
|
|
{
|
|
H_AUTO( NL3D_MeshMRMGeom_RenderNormal );
|
|
|
|
if(_Lods.empty())
|
|
return;
|
|
|
|
// get the result of the Load Balancing.
|
|
float alphaMRM= _LevelDetail.getLevelDetailFromPolyCount(polygonCount);
|
|
|
|
// choose the lod.
|
|
float alphaLod;
|
|
_MBRCurrentLodId= chooseLod(alphaMRM, alphaLod);
|
|
|
|
// Geomorph the choosen Lod (if not the coarser mesh).
|
|
if(_MBRCurrentLodId>0)
|
|
{
|
|
if(rdrCtx.RenderThroughVBHeap)
|
|
applyGeomorphWithVBHardPtr(_Lods[_MBRCurrentLodId].Geomorphs, alphaLod, (uint8*)vbDst);
|
|
else
|
|
applyGeomorph(_Lods[_MBRCurrentLodId].Geomorphs, alphaLod);
|
|
}
|
|
|
|
// set the instance worldmatrix.
|
|
rdrCtx.Driver->setupModelMatrix(inst->getWorldMatrix());
|
|
|
|
// setupLighting.
|
|
inst->changeLightSetup(rdrCtx.RenderTrav);
|
|
}
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::renderPass(CMeshGeomRenderContext &rdrCtx, CMeshBaseInstance *mi, float polygonCount, uint rdrPassId)
|
|
{
|
|
if(_Lods.empty())
|
|
return;
|
|
|
|
CLod &lod= _Lods[_MBRCurrentLodId];
|
|
CRdrPass &rdrPass= lod.RdrPass[rdrPassId];
|
|
|
|
if ( mi->Materials[rdrPass.MaterialId].getBlend() == false )
|
|
{
|
|
// CMaterial Ref
|
|
CMaterial &material=mi->Materials[rdrPass.MaterialId];
|
|
|
|
// Render with the Materials of the MeshInstance.
|
|
if(rdrCtx.RenderThroughVBHeap)
|
|
{
|
|
// render shifted primitives
|
|
rdrCtx.Driver->activeIndexBuffer(rdrPass.VBHeapPBlock);
|
|
rdrCtx.Driver->renderTriangles(material, 0, rdrPass.VBHeapPBlock.getNumIndexes()/3);
|
|
}
|
|
else
|
|
{
|
|
rdrCtx.Driver->activeIndexBuffer(rdrPass.PBlock);
|
|
rdrCtx.Driver->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::endMesh(CMeshGeomRenderContext &rdrCtx)
|
|
{
|
|
if(_Lods.empty())
|
|
return;
|
|
|
|
// bkup force normalisation.
|
|
rdrCtx.Driver->forceNormalize(_MBRBkupNormalize);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CMeshMRMGeom::getVBHeapInfo(uint &vertexFormat, uint &numVertices)
|
|
{
|
|
// CMeshMRMGeom support VBHeap rendering, assuming _SupportMeshBlockRendering is true
|
|
vertexFormat= _VBufferFinal.getVertexFormat();
|
|
numVertices= _VBufferFinal.getNumVertices();
|
|
return _SupportMeshBlockRendering;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::computeMeshVBHeap(void *dst, uint indexStart)
|
|
{
|
|
// Fill dst with Buffer content.
|
|
CVertexBufferRead vba;
|
|
_VBufferFinal.lock (vba);
|
|
memcpy(dst, vba.getVertexCoordPointer(), _VBufferFinal.getNumVertices()*_VBufferFinal.getVertexSize() );
|
|
|
|
// For All Lods
|
|
for(uint lodId=0; lodId<_Lods.size();lodId++)
|
|
{
|
|
CLod &lod= _Lods[lodId];
|
|
|
|
// For all rdrPass.
|
|
for(uint i=0;i<lod.RdrPass.size();i++)
|
|
{
|
|
// shift the PB
|
|
CIndexBuffer &srcPb= lod.RdrPass[i].PBlock;
|
|
CIndexBuffer &dstPb= lod.RdrPass[i].VBHeapPBlock;
|
|
uint j;
|
|
|
|
// Tris.
|
|
CIndexBufferRead ibaRead;
|
|
srcPb.lock (ibaRead);
|
|
CIndexBufferReadWrite ibaWrite;
|
|
srcPb.lock (ibaWrite);
|
|
dstPb.setNumIndexes(srcPb.getNumIndexes());
|
|
// nico : apparently not used, so don't manage 16 bit index here
|
|
nlassert(ibaRead.getIndexNumBytes() == sizeof(uint32));
|
|
nlassert(ibaWrite.getIndexNumBytes() == sizeof(uint32));
|
|
nlassert(ibaRead.getFormat() == CIndexBuffer::Indices32); // nico : apparently not called for now
|
|
const uint32 *srcTriPtr= (const uint32 *) ibaRead.getPtr();
|
|
uint32 *dstTriPtr= (uint32 *) ibaWrite.getPtr();
|
|
for(j=0; j<dstPb.getNumIndexes();j++)
|
|
{
|
|
dstTriPtr[j]= srcTriPtr[j]+indexStart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CMeshMRMGeom::isActiveInstanceNeedVBFill() const
|
|
{
|
|
// Yes, need it for geomorph
|
|
return true;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
// CMeshMRM.
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
CMeshMRM::CMeshMRM()
|
|
{
|
|
}
|
|
// ***************************************************************************
|
|
void CMeshMRM::build (CMeshBase::CMeshBaseBuild &mBase, CMesh::CMeshBuild &m,
|
|
std::vector<CMesh::CMeshBuild*> &listBS,
|
|
const CMRMParameters ¶ms)
|
|
{
|
|
/// copy MeshBase info: materials ....
|
|
CMeshBase::buildMeshBase (mBase);
|
|
|
|
// Then build the geom.
|
|
_MeshMRMGeom.build (m, listBS, (uint)mBase.Materials.size(), params);
|
|
}
|
|
// ***************************************************************************
|
|
void CMeshMRM::build (CMeshBase::CMeshBaseBuild &m, const CMeshMRMGeom &mgeom)
|
|
{
|
|
/// copy MeshBase info: materials ....
|
|
CMeshBase::buildMeshBase(m);
|
|
|
|
// Then copy the geom.
|
|
_MeshMRMGeom= mgeom;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRM::optimizeMaterialUsage(std::vector<sint> &remap)
|
|
{
|
|
// For each material, count usage.
|
|
vector<bool> materialUsed;
|
|
materialUsed.resize(CMeshBase::_Materials.size(), false);
|
|
for(uint lod=0;lod<getNbLod();lod++)
|
|
{
|
|
for(uint rp=0;rp<getNbRdrPass(lod);rp++)
|
|
{
|
|
uint matId= getRdrPassMaterial(lod, rp);
|
|
// flag as used.
|
|
materialUsed[matId]= true;
|
|
}
|
|
}
|
|
|
|
// Apply it to meshBase
|
|
CMeshBase::applyMaterialUsageOptim(materialUsed, remap);
|
|
|
|
// Apply lut to meshGeom.
|
|
_MeshMRMGeom.applyMaterialRemap(remap);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
CTransformShape *CMeshMRM::createInstance(CScene &scene)
|
|
{
|
|
// Create a CMeshMRMInstance, an instance of a mesh.
|
|
//===============================================
|
|
CMeshMRMInstance *mi= (CMeshMRMInstance*)scene.createModel(NL3D::MeshMRMInstanceId);
|
|
mi->Shape= 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 CMeshMRM::clip(const std::vector<CPlane> &pyramid, const CMatrix &worldMatrix)
|
|
{
|
|
return _MeshMRMGeom.clip(pyramid, worldMatrix);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRM::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 CMeshMRM::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
|
|
{
|
|
/*
|
|
Version 0:
|
|
- base version.
|
|
*/
|
|
(void)f.serialVersion(0);
|
|
|
|
// serial Materials infos contained in CMeshBase.
|
|
CMeshBase::serialMeshBase(f);
|
|
|
|
|
|
// serial the geometry.
|
|
_MeshMRMGeom.serial(f);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
float CMeshMRM::getNumTriangles (float distance)
|
|
{
|
|
return _MeshMRMGeom.getNumTriangles (distance);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
const CMeshMRMGeom& CMeshMRM::getMeshGeom () const
|
|
{
|
|
return _MeshMRMGeom;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRM::computeBonesId (CSkeletonModel *skeleton)
|
|
{
|
|
_MeshMRMGeom.computeBonesId (skeleton);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRM::updateSkeletonUsage (CSkeletonModel *skeleton, bool increment)
|
|
{
|
|
_MeshMRMGeom.updateSkeletonUsage(skeleton, increment);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRM::changeMRMDistanceSetup(float distanceFinest, float distanceMiddle, float distanceCoarsest)
|
|
{
|
|
_MeshMRMGeom.changeMRMDistanceSetup(distanceFinest, distanceMiddle, distanceCoarsest);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
IMeshGeom *CMeshMRM::supportMeshBlockRendering (CTransformShape *trans, float &polygonCount ) const
|
|
{
|
|
// Ok if meshGeom is ok.
|
|
if(_MeshMRMGeom.supportMeshBlockRendering())
|
|
{
|
|
polygonCount= trans->getNumTrianglesAfterLoadBalancing();
|
|
return (IMeshGeom*)&_MeshMRMGeom;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRM::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);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRM::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(_MeshMRMGeom.isSkinned())
|
|
return;
|
|
|
|
// Choose the best Lod available for system geometry
|
|
if(_MeshMRMGeom.getNbLodLoaded()==0)
|
|
return;
|
|
uint lodId= _MeshMRMGeom.getNbLodLoaded()-1;
|
|
|
|
// retrieve geometry (if VB/IB not resident)
|
|
if( !_MeshMRMGeom.buildGeometryForLod(lodId, _SystemGeometry.Vertices, _SystemGeometry.Triangles) )
|
|
{
|
|
_SystemGeometry.clear();
|
|
}
|
|
|
|
// TestYoyo
|
|
/*static uint32 totalMem= 0;
|
|
totalMem+= _SystemGeometry.Vertices.size()*sizeof(CVector);
|
|
totalMem+= _SystemGeometry.Triangles.size()*sizeof(uint32);
|
|
nlinfo("CMeshMRM: TotalMem: %d", totalMem);*/
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
// CMeshMRMGeom RawSkin optimisation
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::dirtMeshDataId()
|
|
{
|
|
// see updateRawSkinNormal()
|
|
_MeshDataId++;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::updateRawSkinNormal(bool enabled, CMeshMRMInstance *mi, sint curLodId)
|
|
{
|
|
if(!enabled)
|
|
{
|
|
// if the instance cache is not cleared, must clear.
|
|
if(mi->_RawSkinCache)
|
|
mi->clearRawSkinCache();
|
|
}
|
|
else
|
|
{
|
|
// If the instance has no RawSkin, or has a too old RawSkin cache, must delete it, and recreate
|
|
if( !mi->_RawSkinCache || mi->_RawSkinCache->MeshDataId!=_MeshDataId)
|
|
{
|
|
// first delete if too old.
|
|
if(mi->_RawSkinCache)
|
|
mi->clearRawSkinCache();
|
|
// Then recreate, and use _MeshDataId to verify that the instance works with same data.
|
|
mi->_RawSkinCache= new CRawSkinNormalCache;
|
|
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 );
|
|
|
|
CVertexBufferRead vba;
|
|
_VBufferFinal.lock (vba);
|
|
|
|
CRawSkinNormalCache &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<uint8> softVertices;
|
|
softVertices.clear();
|
|
softVertices.resize( _VBufferFinal.getNumVertices(), 0 );
|
|
for(i=0;i<lod.Geomorphs.size();i++)
|
|
{
|
|
softVertices[lod.Geomorphs[i].Start]= 1;
|
|
softVertices[lod.Geomorphs[i].End]= 1;
|
|
}
|
|
|
|
// The remap from old index in _VBufferFinal to RawSkin vertices (without Geomorphs).
|
|
static vector<TMeshMRMIndexType> 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<lod.InfluencedVertices[i].size();j++)
|
|
{
|
|
uint vid= lod.InfluencedVertices[i][j];
|
|
if(softVertices[vid])
|
|
softSize[i]++;
|
|
else
|
|
hardSize[i]++;
|
|
}
|
|
skinLod.TotalSoftVertices+= softSize[i];
|
|
skinLod.TotalHardVertices+= hardSize[i];
|
|
skinLod.SoftVertices[i]= softSize[i];
|
|
skinLod.HardVertices[i]= hardSize[i];
|
|
}
|
|
// compute offsets
|
|
softStart[0]= 0;
|
|
hardStart[0]= skinLod.TotalSoftVertices;
|
|
for(i=1;i<4;i++)
|
|
{
|
|
softStart[i]= softStart[i-1]+softSize[i-1];
|
|
hardStart[i]= hardStart[i-1]+hardSize[i-1];
|
|
}
|
|
// compute remap
|
|
for(i=0;i<4;i++)
|
|
{
|
|
uint softIdx= softStart[i];
|
|
uint hardIdx= hardStart[i];
|
|
for(uint j=0;j<lod.InfluencedVertices[i].size();j++)
|
|
{
|
|
uint vid= lod.InfluencedVertices[i][j];
|
|
if(softVertices[vid])
|
|
vertexRemap[vid]= softIdx++;
|
|
else
|
|
vertexRemap[vid]= hardIdx++;
|
|
}
|
|
}
|
|
|
|
|
|
// Resize the dest array.
|
|
skinLod.Vertices1.resize((uint32)lod.InfluencedVertices[0].size());
|
|
skinLod.Vertices2.resize((uint32)lod.InfluencedVertices[1].size());
|
|
skinLod.Vertices3.resize((uint32)lod.InfluencedVertices[2].size());
|
|
skinLod.Vertices4.resize((uint32)lod.InfluencedVertices[3].size());
|
|
|
|
// Remap for BlendShape. Lasts 2 bits tells what RawSkin Array to seek (1 to 4),
|
|
// low Bits indicate the number in them. 0xFFFFFFFF is a special value indicating "NotUsed in this lod"
|
|
static vector<uint32> vertexFinalRemap;
|
|
vertexFinalRemap.clear();
|
|
vertexFinalRemap.resize(vertexRemap.size(), 0xFFFFFFFF);
|
|
|
|
// 1 Matrix skinning.
|
|
//========
|
|
for(i=0;i<skinLod.Vertices1.size();i++)
|
|
{
|
|
// get the dest vertex.
|
|
uint vid= lod.InfluencedVertices[0][i];
|
|
// where to store?
|
|
rawIdx= vertexRemap[vid];
|
|
if(softVertices[vid])
|
|
rawIdx-= softStart[0];
|
|
else
|
|
rawIdx+= softSize[0]-hardStart[0];
|
|
// for BlendShapes remapping
|
|
vertexFinalRemap[vid]= (0<<30) + rawIdx;
|
|
// fill raw struct
|
|
skinLod.Vertices1[rawIdx].MatrixId[0]= _SkinWeights[vid].MatrixId[0];
|
|
skinLod.Vertices1[rawIdx].Vertex.Pos= _OriginalSkinVertices[vid];
|
|
skinLod.Vertices1[rawIdx].Vertex.Normal= _OriginalSkinNormals[vid];
|
|
skinLod.Vertices1[rawIdx].Vertex.UV= *vba.getTexCoordPointer(vid);
|
|
}
|
|
|
|
// 2 Matrix skinning.
|
|
//========
|
|
for(i=0;i<skinLod.Vertices2.size();i++)
|
|
{
|
|
// get the dest vertex.
|
|
uint vid= lod.InfluencedVertices[1][i];
|
|
// where to store?
|
|
rawIdx= vertexRemap[vid];
|
|
if(softVertices[vid])
|
|
rawIdx-= softStart[1];
|
|
else
|
|
rawIdx+= softSize[1]-hardStart[1];
|
|
// for BlendShapes remapping
|
|
vertexFinalRemap[vid]= (1<<30) + rawIdx;
|
|
// fill raw struct
|
|
skinLod.Vertices2[rawIdx].MatrixId[0]= _SkinWeights[vid].MatrixId[0];
|
|
skinLod.Vertices2[rawIdx].MatrixId[1]= _SkinWeights[vid].MatrixId[1];
|
|
skinLod.Vertices2[rawIdx].Weights[0]= _SkinWeights[vid].Weights[0];
|
|
skinLod.Vertices2[rawIdx].Weights[1]= _SkinWeights[vid].Weights[1];
|
|
skinLod.Vertices2[rawIdx].Vertex.Pos= _OriginalSkinVertices[vid];
|
|
skinLod.Vertices2[rawIdx].Vertex.Normal= _OriginalSkinNormals[vid];
|
|
skinLod.Vertices2[rawIdx].Vertex.UV= *vba.getTexCoordPointer(vid);
|
|
}
|
|
|
|
// 3 Matrix skinning.
|
|
//========
|
|
for(i=0;i<skinLod.Vertices3.size();i++)
|
|
{
|
|
// get the dest vertex.
|
|
uint vid= lod.InfluencedVertices[2][i];
|
|
// where to store?
|
|
rawIdx= vertexRemap[vid];
|
|
if(softVertices[vid])
|
|
rawIdx-= softStart[2];
|
|
else
|
|
rawIdx+= softSize[2]-hardStart[2];
|
|
// for BlendShapes remapping
|
|
vertexFinalRemap[vid]= (2<<30) + rawIdx;
|
|
// fill raw struct
|
|
skinLod.Vertices3[rawIdx].MatrixId[0]= _SkinWeights[vid].MatrixId[0];
|
|
skinLod.Vertices3[rawIdx].MatrixId[1]= _SkinWeights[vid].MatrixId[1];
|
|
skinLod.Vertices3[rawIdx].MatrixId[2]= _SkinWeights[vid].MatrixId[2];
|
|
skinLod.Vertices3[rawIdx].Weights[0]= _SkinWeights[vid].Weights[0];
|
|
skinLod.Vertices3[rawIdx].Weights[1]= _SkinWeights[vid].Weights[1];
|
|
skinLod.Vertices3[rawIdx].Weights[2]= _SkinWeights[vid].Weights[2];
|
|
skinLod.Vertices3[rawIdx].Vertex.Pos= _OriginalSkinVertices[vid];
|
|
skinLod.Vertices3[rawIdx].Vertex.Normal= _OriginalSkinNormals[vid];
|
|
skinLod.Vertices3[rawIdx].Vertex.UV= *vba.getTexCoordPointer(vid);
|
|
}
|
|
|
|
// 4 Matrix skinning.
|
|
//========
|
|
for(i=0;i<skinLod.Vertices4.size();i++)
|
|
{
|
|
// get the dest vertex.
|
|
uint vid= lod.InfluencedVertices[3][i];
|
|
// where to store?
|
|
rawIdx= vertexRemap[vid];
|
|
if(softVertices[vid])
|
|
rawIdx-= softStart[3];
|
|
else
|
|
rawIdx+= softSize[3]-hardStart[3];
|
|
// for BlendShapes remapping
|
|
vertexFinalRemap[vid]= (3<<30) + rawIdx;
|
|
// fill raw struct
|
|
skinLod.Vertices4[rawIdx].MatrixId[0]= _SkinWeights[vid].MatrixId[0];
|
|
skinLod.Vertices4[rawIdx].MatrixId[1]= _SkinWeights[vid].MatrixId[1];
|
|
skinLod.Vertices4[rawIdx].MatrixId[2]= _SkinWeights[vid].MatrixId[2];
|
|
skinLod.Vertices4[rawIdx].MatrixId[3]= _SkinWeights[vid].MatrixId[3];
|
|
skinLod.Vertices4[rawIdx].Weights[0]= _SkinWeights[vid].Weights[0];
|
|
skinLod.Vertices4[rawIdx].Weights[1]= _SkinWeights[vid].Weights[1];
|
|
skinLod.Vertices4[rawIdx].Weights[2]= _SkinWeights[vid].Weights[2];
|
|
skinLod.Vertices4[rawIdx].Weights[3]= _SkinWeights[vid].Weights[3];
|
|
skinLod.Vertices4[rawIdx].Vertex.Pos= _OriginalSkinVertices[vid];
|
|
skinLod.Vertices4[rawIdx].Vertex.Normal= _OriginalSkinNormals[vid];
|
|
skinLod.Vertices4[rawIdx].Vertex.UV= *vba.getTexCoordPointer(vid);
|
|
}
|
|
|
|
// Remap Geomorphs.
|
|
//========
|
|
uint numGeoms= (uint)lod.Geomorphs.size();
|
|
skinLod.Geomorphs.resize( numGeoms );
|
|
for(i=0;i<numGeoms;i++)
|
|
{
|
|
// NB: don't add "numGeoms" to the index because RawSkin look in a TempArray in RAM, which start at 0...
|
|
skinLod.Geomorphs[i].Start= vertexRemap[lod.Geomorphs[i].Start];
|
|
skinLod.Geomorphs[i].End= vertexRemap[lod.Geomorphs[i].End];
|
|
}
|
|
|
|
// Remap RdrPass.
|
|
//========
|
|
skinLod.RdrPass.resize(lod.RdrPass.size());
|
|
for(i=0;i<skinLod.RdrPass.size();i++)
|
|
{
|
|
// NB: since RawSkin is possible only with SkinGrouping, and since SkniGrouping is
|
|
// possible only with no Quads/Lines, we should have only Tris here.
|
|
// remap tris.
|
|
skinLod.RdrPass[i].setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT);
|
|
skinLod.RdrPass[i].setNumIndexes(lod.RdrPass[i].PBlock.getNumIndexes());
|
|
CIndexBufferRead ibaRead;
|
|
lod.RdrPass[i].PBlock.lock (ibaRead);
|
|
CIndexBufferReadWrite ibaWrite;
|
|
skinLod.RdrPass[i].lock (ibaWrite);
|
|
if (skinLod.RdrPass[i].getFormat() == CIndexBuffer::Indices32)
|
|
{
|
|
nlassert(ibaRead.getFormat() == CIndexBuffer::Indices32);
|
|
const uint32 *srcTriPtr= (const uint32 *) ibaRead.getPtr();
|
|
uint32 *dstTriPtr= (uint32 *) ibaWrite.getPtr();
|
|
uint32 numIndices= lod.RdrPass[i].PBlock.getNumIndexes();
|
|
for(uint j=0;j<numIndices;j++, srcTriPtr++, dstTriPtr++)
|
|
{
|
|
uint vid= *srcTriPtr;
|
|
// If this index refers to a Geomorphed vertex, don't modify!
|
|
if(vid<numGeoms)
|
|
*dstTriPtr= vid;
|
|
else
|
|
*dstTriPtr= vertexRemap[vid] + numGeoms;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nlassert(ibaRead.getFormat() == CIndexBuffer::Indices16);
|
|
const uint16 *srcTriPtr= (const uint16 *) ibaRead.getPtr();
|
|
uint16 *dstTriPtr= (uint16 *) ibaWrite.getPtr();
|
|
uint32 numIndices= lod.RdrPass[i].PBlock.getNumIndexes();
|
|
for(uint j=0;j<numIndices;j++, srcTriPtr++, dstTriPtr++)
|
|
{
|
|
uint vid= *srcTriPtr;
|
|
// If this index refers to a Geomorphed vertex, don't modify!
|
|
if(vid<numGeoms)
|
|
*dstTriPtr= vid;
|
|
else
|
|
*dstTriPtr= vertexRemap[vid] + numGeoms;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Case of MeshMorpher
|
|
//========
|
|
if(_MeshMorpher.BlendShapes.size()>0)
|
|
{
|
|
skinLod.VertexRemap.resize((uint32)vertexFinalRemap.size());
|
|
|
|
for(i=0;i<vertexFinalRemap.size();i++)
|
|
{
|
|
uint vfr= vertexFinalRemap[i];
|
|
if(vfr!=0xFFFFFFFF)
|
|
{
|
|
uint rsArrayId= vfr >> 30;
|
|
uint rsIndex= vfr & ((1<<30)-1);
|
|
switch(rsArrayId)
|
|
{
|
|
case 0:
|
|
skinLod.VertexRemap[i]= &skinLod.Vertices1[rsIndex].Vertex;
|
|
break;
|
|
case 1:
|
|
skinLod.VertexRemap[i]= &skinLod.Vertices2[rsIndex].Vertex;
|
|
break;
|
|
case 2:
|
|
skinLod.VertexRemap[i]= &skinLod.Vertices3[rsIndex].Vertex;
|
|
break;
|
|
case 3:
|
|
skinLod.VertexRemap[i]= &skinLod.Vertices4[rsIndex].Vertex;
|
|
break;
|
|
};
|
|
}
|
|
else
|
|
skinLod.VertexRemap[i]= NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
// CMeshMRMGeom Shadow Skin Rendering
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
|
|
|
|
// ***************************************************************************
|
|
void CMeshMRMGeom::setShadowMesh(const std::vector<CShadowVertex> &shadowVertices, const std::vector<uint32> &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 CMeshMRMGeom::getNumShadowSkinVertices() const
|
|
{
|
|
return (uint)_ShadowSkin.Vertices.size();
|
|
}
|
|
|
|
// ***************************************************************************
|
|
sint CMeshMRMGeom::renderShadowSkinGeom(CMeshMRMInstance *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.
|
|
//===========
|
|
|
|
// skinning with normal, but no tangent space
|
|
// 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<CMatrix3x4> 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 CMeshMRMGeom::renderShadowSkinPrimitives(CMeshMRMInstance *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;
|
|
NL_SET_IB_NAME(shiftedTris, "CMeshMRMGeom::renderShadowSkinPrimitives::shiftedTris");
|
|
if(shiftedTris.getNumIndexes()<_ShadowSkin.Triangles.size())
|
|
{
|
|
shiftedTris.setFormat(NL_MESH_MRM_INDEX_FORMAT);
|
|
shiftedTris.setNumIndexes((uint32)_ShadowSkin.Triangles.size());
|
|
}
|
|
shiftedTris.setPreferredMemory(CIndexBuffer::RAMVolatile, false);
|
|
{
|
|
CIndexBufferReadWrite iba;
|
|
shiftedTris.lock(iba);
|
|
const uint32 *src= &_ShadowSkin.Triangles[0];
|
|
TMeshMRMIndexType *dst= (TMeshMRMIndexType *) iba.getPtr();
|
|
for(uint n= (uint)_ShadowSkin.Triangles.size();n>0;n--, src++, dst++)
|
|
{
|
|
*dst= (TMeshMRMIndexType)(*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 CMeshMRMGeom::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(boneId<skeleton->Bones.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 Sphere compute
|
|
nlassert(_OriginalSkinVertices.size() == _SkinWeights.size());
|
|
bool bbEmpty= true;
|
|
|
|
// Remap the vertex, and compute the wanted bone bbox
|
|
// for true vertices
|
|
for (uint vert=nGeomSpace; vert<_SkinWeights.size(); vert++)
|
|
{
|
|
// get the vertex position.
|
|
CVector vertex= _OriginalSkinVertices[vert];
|
|
|
|
// For each weight
|
|
uint weight;
|
|
for (weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++)
|
|
{
|
|
// Active ?
|
|
if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0))
|
|
{
|
|
// Check id is the wanted one
|
|
if(_SkinWeights[vert].MatrixId[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;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CMeshMRMGeom::intersectSkin(CMeshMRMInstance *mi, const CMatrix &toRaySpace, float &dist2D, float &distZ, bool computeDist2D)
|
|
{
|
|
// for MeshMRM, Use the Shadow Skinning (simple and light version).
|
|
|
|
// 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);
|
|
}
|
|
|
|
|
|
} // NL3D
|
|
|