// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "std3d.h"
#include "nel/misc/common.h"
#include "nel/3d/lod_character_manager.h"
#include "nel/3d/lod_character_shape.h"
#include "nel/3d/lod_character_shape_bank.h"
#include "nel/3d/lod_character_instance.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/misc/fast_floor.h"
#include "nel/3d/lod_character_texture.h"
#include "nel/3d/ray_mesh.h"
#include "nel/misc/file.h"
#include "nel/misc/algo.h"
#include "nel/misc/fast_mem.h"
#include "nel/misc/system_info.h"
using namespace std;
using namespace NLMISC;
namespace NL3D
{
// ***************************************************************************
// Dest is without Normal because precomputed
#define NL3D_CLOD_VERTEX_FORMAT (CVertexBuffer::PositionFlag | CVertexBuffer::TexCoord0Flag | CVertexBuffer::PrimaryColorFlag)
#define NL3D_CLOD_VERTEX_SIZE 24
#define NL3D_CLOD_UV_OFF 12
#define NL3D_CLOD_COLOR_OFF 20
// size (in block) of the big texture.
#define NL3D_CLOD_TEXT_NLOD_WIDTH 16
#define NL3D_CLOD_TEXT_NLOD_HEIGHT 16
#define NL3D_CLOD_TEXT_NUM_IDS NL3D_CLOD_TEXT_NLOD_WIDTH*NL3D_CLOD_TEXT_NLOD_HEIGHT
#define NL3D_CLOD_BIGTEXT_WIDTH NL3D_CLOD_TEXT_NLOD_WIDTH*NL3D_CLOD_TEXT_WIDTH
#define NL3D_CLOD_BIGTEXT_HEIGHT NL3D_CLOD_TEXT_NLOD_HEIGHT*NL3D_CLOD_TEXT_HEIGHT
// Default texture color. Alpha must be 255
#define NL3D_CLOD_DEFAULT_TEXCOLOR CRGBA(255,255,255,255)
// ***************************************************************************
CLodCharacterManager::CLodCharacterManager()
{
_MaxNumVertices= 3000;
_NumVBHard= 8;
_Rendering= false;
_LockDone= false;
// setup the texture.
_BigTexture= new CTextureBlank;
// The texture always reside in memory... This take 1Mo of RAM. (16*32*16*32 * 4)
// NB: this is simplier like that, and this is not a problem, since only 1 or 2 Mo are allocated :o)
_BigTexture->setReleasable(false);
// create the bitmap.
_BigTexture->resize(NL3D_CLOD_BIGTEXT_WIDTH, NL3D_CLOD_BIGTEXT_HEIGHT, CBitmap::RGBA);
// Format of texture, 16 bits and no mipmaps.
_BigTexture->setUploadFormat(ITexture::RGB565);
_BigTexture->setFilterMode(ITexture::Linear, ITexture::LinearMipMapOff);
_BigTexture->setWrapS(ITexture::Clamp);
_BigTexture->setWrapT(ITexture::Clamp);
// Alloc free Ids
_FreeIds.resize(NL3D_CLOD_TEXT_NUM_IDS);
for(uint i=0;i<_FreeIds.size();i++)
{
_FreeIds[i]= i;
}
// setup the material
_Material.initUnlit();
_Material.setAlphaTest(true);
_Material.setDoubleSided(true);
_Material.setTexture(0, _BigTexture);
// setup for lighting, Default for Ryzom setup
_LightCorrectionMatrix.rotateZ((float)Pi/2);
_LightCorrectionMatrix.invert();
NL_SET_IB_NAME(_Triangles, "CLodCharacterManager::_Triangles");
}
// ***************************************************************************
CLodCharacterManager::~CLodCharacterManager()
{
reset();
}
// ***************************************************************************
void CLodCharacterManager::reset()
{
nlassert(!isRendering());
// delete shapeBanks.
for(uint i=0;i<_ShapeBankArray.size();i++)
{
if(_ShapeBankArray[i])
delete _ShapeBankArray[i];
}
// clears containers
contReset(_ShapeBankArray);
contReset(_ShapeMap);
// reset render part.
_VertexStream.release();
}
// ***************************************************************************
uint32 CLodCharacterManager::createShapeBank()
{
// search a free entry
for(uint i=0;i<_ShapeBankArray.size();i++)
{
// if ree, use it.
if(_ShapeBankArray[i]==NULL)
{
_ShapeBankArray[i]= new CLodCharacterShapeBank;
return i;
}
}
// no free entrey, resize array.
_ShapeBankArray.push_back(new CLodCharacterShapeBank);
return (uint32)_ShapeBankArray.size()-1;
}
// ***************************************************************************
const CLodCharacterShapeBank *CLodCharacterManager::getShapeBank(uint32 bankId) const
{
if(bankId>=_ShapeBankArray.size())
return NULL;
else
return _ShapeBankArray[bankId];
}
// ***************************************************************************
CLodCharacterShapeBank *CLodCharacterManager::getShapeBank(uint32 bankId)
{
if(bankId>=_ShapeBankArray.size())
return NULL;
else
return _ShapeBankArray[bankId];
}
// ***************************************************************************
void CLodCharacterManager::deleteShapeBank(uint32 bankId)
{
if(bankId>=_ShapeBankArray.size())
{
if(_ShapeBankArray[bankId])
{
delete _ShapeBankArray[bankId];
_ShapeBankArray[bankId]= NULL;
}
}
}
// ***************************************************************************
sint32 CLodCharacterManager::getShapeIdByName(const std::string &name) const
{
CstItStrIdMap it= _ShapeMap.find(name);
if(it==_ShapeMap.end())
return -1;
else
return it->second;
}
// ***************************************************************************
const CLodCharacterShape *CLodCharacterManager::getShape(uint32 shapeId) const
{
// split the id
uint bankId= shapeId >> 16;
uint shapeInBankId= shapeId &0xFFFF;
// if valid bankId
const CLodCharacterShapeBank *shapeBank= getShapeBank(bankId);
if(shapeBank)
{
// return the shape from the bank
return shapeBank->getShape(shapeInBankId);
}
else
return NULL;
}
// ***************************************************************************
bool CLodCharacterManager::compile()
{
bool error= false;
// clear the map
contReset(_ShapeMap);
// build the map
for(uint i=0; i<_ShapeBankArray.size(); i++)
{
if(_ShapeBankArray[i])
{
// Parse all Shapes
for(uint j=0; j<_ShapeBankArray[i]->getNumShapes(); j++)
{
// build the shape Id
uint shapeId= (i<<16) + j;
// get the shape
const CLodCharacterShape *shape= _ShapeBankArray[i]->getShape(j);
if(shape)
{
const string &name= shape->getName();
ItStrIdMap it= _ShapeMap.find(name);
if(it == _ShapeMap.end())
// insert the id in the map
_ShapeMap.insert(make_pair(name, shapeId));
else
{
error= true;
nlwarning("Found a Character Lod with same name in the manager: %s", name.c_str());
}
}
}
}
}
return error;
}
// ***************************************************************************
// ***************************************************************************
// Render
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
void CLodCharacterManager::setMaxVertex(uint32 maxVertex)
{
// we must not be between beginRender() and endRender()
nlassert(!isRendering());
_MaxNumVertices= maxVertex;
}
// ***************************************************************************
void CLodCharacterManager::setVertexStreamNumVBHard(uint32 numVBHard)
{
// we must not be between beginRender() and endRender()
nlassert(!isRendering());
_NumVBHard= numVBHard;
}
// ***************************************************************************
void CLodCharacterManager::beginRender(IDriver *driver, const CVector &managerPos)
{
H_AUTO( NL3D_CharacterLod_beginRender );
// we must not be between beginRender() and endRender()
nlassert(!isRendering());
// Reset render
//=================
_CurrentVertexId=0;
_CurrentTriId= 0;
// update Driver.
//=================
nlassert(driver);
// test change of vertexStream setup
bool mustChangeVertexStream= _VertexStream.getDriver() != driver;
if(!mustChangeVertexStream)
{
mustChangeVertexStream= _MaxNumVertices != _VertexStream.getMaxVertices();
mustChangeVertexStream= mustChangeVertexStream || _NumVBHard != _VertexStream.getNumVB();
}
// re-init?
if( mustChangeVertexStream )
{
// chech offset
CVertexBuffer vb;
vb.setVertexFormat(NL3D_CLOD_VERTEX_FORMAT);
// NB: addRenderCharacterKey() loop hardCoded for Vertex+UV+Normal+Color only.
nlassert( NL3D_CLOD_UV_OFF == vb.getTexCoordOff());
nlassert( NL3D_CLOD_COLOR_OFF == vb.getColorOff());
// Setup the vertex stream
_VertexStream.release();
_VertexStream.init(driver, NL3D_CLOD_VERTEX_FORMAT, _MaxNumVertices, _NumVBHard, "CLodManagerVB", false); // nb : don't use volatile lock as we keep the buffer locked
}
// prepare for render.
//=================
// Do not Lock Buffer now (will be done at the first instance added)
nlassert(!_LockDone);
_VertexSize= _VertexStream.getVertexSize();
// NB: addRenderCharacterKey() loop hardCoded for Vertex+UV+Normal+Color only.
nlassert( _VertexSize == NL3D_CLOD_VERTEX_SIZE ); // Vector + Normal + UV + RGBA
// Alloc a minimum of primitives (2*vertices), to avoid as possible reallocation in addRenderCharacterKey
if(_Triangles.getNumIndexes()<_MaxNumVertices * 2)
{
_Triangles.setFormat(NL_LOD_CHARACTER_INDEX_FORMAT);
_Triangles.setNumIndexes(_MaxNumVertices * 2);
}
// Local manager matrix
_ManagerMatrixPos= managerPos;
// Ok, start rendering
_Rendering= true;
}
// ***************************************************************************
static inline void computeLodLighting(CRGBA &lightRes, const CVector &lightObjectSpace, const CVector &normalPtr, CRGBA ambient, CRGBA diffuse)
{
float f= lightObjectSpace * normalPtr;
sint f8= NLMISC::OptFastFloor(f);
fastClamp8(f8);
sint r,g,b;
r= (diffuse.R * f8)>>8;
g= (diffuse.G * f8)>>8;
b= (diffuse.B * f8)>>8;
r+= ambient.R;
g+= ambient.G;
b+= ambient.B;
fastClamp8(r);
fastClamp8(g);
fastClamp8(b);
lightRes.R= r;
lightRes.G= g;
lightRes.B= b;
}
// ***************************************************************************
bool CLodCharacterManager::addRenderCharacterKey(CLodCharacterInstance &instance, const CMatrix &worldMatrix,
CRGBA paramAmbient, CRGBA paramDiffuse, const CVector &lightDir)
{
H_AUTO ( NL3D_CharacterLod_AddRenderKey )
nlassert(_VertexStream.getDriver());
// we must be between beginRender() and endRender()
nlassert(isRendering());
// regroup all variables that will be accessed in the ASM loop (minimize cache problems)
uint numVertices;
const CLodCharacterShape::CVector3s *vertPtr;
const CVector *normalPtr;
const CUV *uvPtr;
const uint8 *alphaPtr;
CVector lightObjectSpace;
CVector matPos;
float a00, a01, a02;
float a10, a11, a12;
float a20, a21, a22;
sint f8;
uint64 blank= 0;
CRGBA ambient= paramAmbient;
CRGBA diffuse= paramDiffuse;
// For ASM / MMX, must set 0 to alpha part, because replaced by *alphaPtr (with add)
ambient.A= 0;
diffuse.A= 0;
// Get the Shape and current key.
//=============
// get the shape
const CLodCharacterShape *clod= getShape(instance.ShapeId);
// if not found quit, return true
if(!clod)
return true;
// get UV/Normal array. NULL => error
normalPtr= clod->getNormals();
// get UV of the instance
uvPtr= instance.getUVs();
// uvPtr is NULL means that initInstance() has not been called!!
nlassert(normalPtr && uvPtr);
// get the anim key
CVector unPackScaleFactor;
vertPtr= clod->getAnimKey(instance.AnimId, instance.AnimTime, instance.WrapMode, unPackScaleFactor);
// if not found quit, return true
if(!vertPtr)
return true;
// get num verts
numVertices= clod->getNumVertices();
// empty shape??
if(numVertices==0)
return true;
// If too many vertices, quit, returning false.
if(_CurrentVertexId+numVertices > _MaxNumVertices)
return false;
// get alpha array
static vector defaultAlphaArray;
// get the instance alpha if correctly setuped
if(instance.VertexAlphas.size() == numVertices)
{
alphaPtr= &instance.VertexAlphas[0];
}
// if error, take 255 as alpha.
else
{
// NB: still use an array. This case should never arise, but support it not at full optim.
if(defaultAlphaArray.size()0)
if(!_LockDone)
{
_VertexData= _VertexStream.lock();
_LockDone= true;
}
// After lock, For D3D, the VertexColor may be in BGRA format
if(_VertexStream.isBRGA())
{
// then swap only the B and R (no cpu cycle added per vertex)
ambient.swapBR();
diffuse.swapBR();
}
// Prepare Transform
//=============
// HTimerInfo: all this block takes 0.1%
// Get matrix pos.
matPos= worldMatrix.getPos();
// compute in manager space.
matPos -= _ManagerMatrixPos;
// Get rotation line vectors
const float *worldM= worldMatrix.get();
a00= worldM[0]; a01= worldM[4]; a02= worldM[8];
a10= worldM[1]; a11= worldM[5]; a12= worldM[9];
a20= worldM[2]; a21= worldM[6]; a22= worldM[10];
// get the light in object space.
// Multiply light dir with transpose of worldMatrix. This may be not exact (not uniform scale) but sufficient.
lightObjectSpace.x= a00 * lightDir.x + a10 * lightDir.y + a20 * lightDir.z;
lightObjectSpace.y= a01 * lightDir.x + a11 * lightDir.y + a21 * lightDir.z;
lightObjectSpace.z= a02 * lightDir.x + a12 * lightDir.y + a22 * lightDir.z;
// animation User correction
lightObjectSpace= _LightCorrectionMatrix.mulVector(lightObjectSpace);
// normalize, and neg for Dot Product.
lightObjectSpace.normalize();
lightObjectSpace= -lightObjectSpace;
// preMul by 255 for RGBA uint8
lightObjectSpace*= 255;
// multiply matrix with scale factor for Pos.
a00*= unPackScaleFactor.x; a01*= unPackScaleFactor.y; a02*= unPackScaleFactor.z;
a10*= unPackScaleFactor.x; a11*= unPackScaleFactor.y; a12*= unPackScaleFactor.z;
a20*= unPackScaleFactor.x; a21*= unPackScaleFactor.y; a22*= unPackScaleFactor.z;
// get dst Array.
uint8 *dstPtr;
dstPtr= _VertexData + _CurrentVertexId * _VertexSize;
/* PreCaching Note: CFastMem::precache() has been tested (done on the 4 arrays) but not very interesting,
maybe because the cache miss improve //ism a bit below.
*/
// Fill the VB
//=============
#if defined(NL_OS_WINDOWS) && !defined(NL_NO_ASM)
// optimized version
if(CSystemInfo::hasMMX())
{
H_AUTO( NL3D_CharacterLod_vertexFill );
if(numVertices)
{
/* NB: order is important for AGP filling optimisation in dstPtr
Pentium2+ optimisation notes:
- "uop" comment formating:
A/B means "A micro-ops in port 0, and B micro-ops in port 2". (port 1 is very rare for FPU)
A/B/C/D means "A micro-ops in port 0, B in port 2, C in port 3 and D in port 4".
The number in () is the delay (if any).
- the "compute lighting part" must done first, because of the "fistp f8" mem writes that must
be place far away from the "mov eax, f8" read in clamp lighting part
(else seems that it crashes all the //ism)
- No need to Interleave on Pentium2+. But prevents "write/read stall" by putting the write
far away from the next read. Else stall of 3 cycles + BIG BREAK OF //ism (I think).
This had save me 120 cycles / 240 !!!
BenchResults:
- The "transform vertex part" and "all next part" cost 42 cycles, but is somewhat optimal:
63 uop (=> min 21 cycles), but 36 uop in the P0 port (=> this is the bottleneck)
- The lighting part adds 1 cycle only ????? (44 cycles) But still relevant and optimal:
43 uop in port P0!!!!
- The UV part adds 4 cycles (47) (should not since 0 in Port P0), still acceptable.
- The clamp part adds 3 cycles (50), and add 11 cycles in "P0 or P1" (but heavy dependency)
If we assume all goes into P1, it should takes 0... still acceptable (optimal==43?)
- The alpha part adds 2 cycles (52, optimal=45). OK.
- The modulate part adds 15 cycles. OK
TOTAL: 67 cycles in theory (write in RAM, no cache miss problem)
BENCH: ASM version: 91 cycles (Write in AGP, some cache miss problems, still good against 67)
C version: 316 cycles.
*/
__asm
{
mov edi, dstPtr
theLoop:
// **** compute lighting
mov esi,normalPtr // uop: 0/1
// dot3
fld dword ptr [esi] // uop: 0/1
fmul lightObjectSpace.x // uop: 1/1 (5)
fld dword ptr [esi+4] // uop: 0/1
fmul lightObjectSpace.y // uop: 1/1 (5)
faddp st(1),st // uop: 1/0 (3)
fld dword ptr [esi+8] // uop: 0/1
fmul lightObjectSpace.z // uop: 1/1 (5)
faddp st(1),st // uop: 1/0 (3)
fistp f8 // uop: 2/0/1/1 (5)
// next
add esi, 12 // uop: 1/0
mov normalPtr, esi // uop: 0/0/1/1
// **** transform vertex, and store
mov esi, vertPtr // uop: 0/1
fild word ptr[esi] // uop: 3/1 (5)
fild word ptr[esi+2] // uop: 3/1 (5)
fild word ptr[esi+4] // uop: 3/1 (5)
// x
fld a00 // uop: 0/1
fmul st, st(3) // uop: 1/0 (5)
fld a01 // uop: 0/1
fmul st, st(3) // uop: 1/0 (5)
faddp st(1), st // uop: 1/0 (3)
fld a02 // uop: 0/1
fmul st, st(2) // uop: 1/0 (5)
faddp st(1), st // uop: 1/0 (3)
fld matPos.x // uop: 0/1
faddp st(1), st // uop: 1/0 (3)
fstp dword ptr[edi] // uop: 0/0/1/1
// y
fld a10
fmul st, st(3)
fld a11
fmul st, st(3)
faddp st(1), st
fld a12
fmul st, st(2)
faddp st(1), st
fld matPos.y
faddp st(1), st
fstp dword ptr[edi+4]
// z
fld a20
fmul st, st(3)
fld a21
fmul st, st(3)
faddp st(1), st
fld a22
fmul st, st(2)
faddp st(1), st
fld matPos.z
faddp st(1), st
fstp dword ptr[edi+8]
// flush stack
fstp st // uop: 1/0
fstp st // uop: 1/0
fstp st // uop: 1/0
// next
add esi, 6 // uop: 1/0
mov vertPtr, esi // uop: 0/0/1/1
// **** copy uv
mov esi, uvPtr // uop: 0/1
mov eax, [esi] // uop: 0/1
mov [edi+NL3D_CLOD_UV_OFF], eax // uop: 0/0/1/1
mov ebx, [esi+4] // uop: 0/1
mov [edi+NL3D_CLOD_UV_OFF+4], ebx // uop: 0/0/1/1
// next
add esi, 8 // uop: 1/0
mov uvPtr, esi // uop: 0/0/1/1
// **** Clamp lighting
// clamp to 0 only. will be clamped to 255 by MMX
mov eax, f8 // uop: 0/1
cmp eax, 0x80000000 // if>=0 => CF=1
sbb ebx, ebx // if>=0 => CF==1 => ebx=0xFFFFFFFF
and eax, ebx // if>=0 => eax unchanged, else eax=0 (clamped)
// **** Modulate lighting modulate with diffuse color, add ambient term, using MMX
movd mm0, eax // 0000000L uop: 1/0
packuswb mm0, mm0 // 000L000L uop: 1/0 (p1)
packuswb mm0, mm0 // 0L0L0L0L uop: 1/0 (p1)
movd mm1, diffuse // uop: 0/1
punpcklbw mm1, blank // uop: 1/1 (p1)
pmullw mm0, mm1 // diffuse*L uop: 1/0 (3)
psrlw mm0, 8 // 0A0B0G0R uop: 1/0 (p1)
packuswb mm0, blank // 0000ABGR uop: 1/1 (p1)
movd mm2, ambient // uop: 0/1
paddusb mm0, mm2 // uop: 1/0
movd ebx, mm0 // ebx= AABBGGRR uop: 1/0
// NB: emms is not so bad on P2+: delay of 6, +11 (NB: far better than no MMX instructions)
emms // uop: 11/0 (6). (?????)
// **** append alpha, and store
mov esi, alphaPtr // uop: 0/1
movzx eax, byte ptr[esi] // uop: 0/1
shl eax, 24 // uop: 1/0
add ebx, eax // uop: 1/0
// now, ebx= AABBGGRR
mov [edi+NL3D_CLOD_COLOR_OFF], ebx // uop: 0/0/1/1
// next
add esi, 1 // uop: 1/0
mov alphaPtr, esi // uop: 0/0/1/1
// **** next
add edi, NL3D_CLOD_VERTEX_SIZE // uop: 1/0
mov eax, numVertices // uop: 0/1
dec eax // uop: 1/0
mov numVertices, eax // uop: 0/0/1/1
jnz theLoop // uop: 1/1 (p1)
// To have same behavior than c code
mov dstPtr, edi
}
}
}
else
#endif
{
H_AUTO( NL3D_CharacterLod_vertexFill );
CVector fVect;
for(;numVertices>0;)
{
// NB: order is important for AGP filling optimisation
// transform vertex, and store.
CVector *dstVector= (CVector*)dstPtr;
fVect.x= vertPtr->x; fVect.y= vertPtr->y; fVect.z= vertPtr->z;
++vertPtr;
dstVector->x= a00 * fVect.x + a01 * fVect.y + a02 * fVect.z + matPos.x;
dstVector->y= a10 * fVect.x + a11 * fVect.y + a12 * fVect.z + matPos.y;
dstVector->z= a20 * fVect.x + a21 * fVect.y + a22 * fVect.z + matPos.z;
// Copy UV
*(CUV*)(dstPtr + NL3D_CLOD_UV_OFF)= *uvPtr;
++uvPtr;
// Compute Lighting.
CRGBA lightRes;
computeLodLighting(lightRes, lightObjectSpace, *normalPtr, ambient, diffuse);
++normalPtr;
lightRes.A= *alphaPtr;
++alphaPtr;
// store.
*((CRGBA*)(dstPtr + NL3D_CLOD_COLOR_OFF))= lightRes;
// next
dstPtr+= NL3D_CLOD_VERTEX_SIZE;
numVertices--;
}
}
// Add Primitives.
//=============
{
H_AUTO( NL3D_CharacterLod_primitiveFill )
// get number of tri indexes
uint numTriIdxs= clod->getNumTriangles() * 3;
// Yoyo: there is an assert with getPtr(). Not sure, but maybe arise if numTriIdxs==0
if(numTriIdxs)
{
// realloc tris if needed.
if(_CurrentTriId+numTriIdxs > _Triangles.getNumIndexes())
{
_Triangles.setFormat(NL_LOD_CHARACTER_INDEX_FORMAT);
_Triangles.setNumIndexes(_CurrentTriId+numTriIdxs);
}
// reindex and copy tris
CIndexBufferReadWrite iba;
_Triangles.lock(iba);
const TLodCharacterIndexType *srcIdx= clod->getTriangleArray();
nlassert(sizeof(TLodCharacterIndexType) == _Triangles.getIndexNumBytes());
TLodCharacterIndexType *dstIdx= (TLodCharacterIndexType *) iba.getPtr()+_CurrentTriId;
for(;numTriIdxs>0;numTriIdxs--, srcIdx++, dstIdx++)
{
*dstIdx= *srcIdx + _CurrentVertexId;
}
}
}
// Next
//=============
// Inc Vertex count.
_CurrentVertexId+= clod->getNumVertices();
// Inc Prim count.
_CurrentTriId+= clod->getNumTriangles() * 3;
// key added
return true;
}
// ***************************************************************************
void CLodCharacterManager::endRender()
{
H_AUTO ( NL3D_CharacterLod_endRender );
IDriver *driver= _VertexStream.getDriver();
nlassert(driver);
// we must be between beginRender() and endRender()
nlassert(isRendering());
// if something rendered
if(_LockDone)
{
// UnLock Buffer.
_VertexStream.unlock(_CurrentVertexId);
_LockDone= false;
// Render the VBuffer and the primitives.
if(_CurrentTriId>0)
{
// setup matrix.
CMatrix managerMatrix;
managerMatrix.setPos(_ManagerMatrixPos);
driver->setupModelMatrix(managerMatrix);
// active VB
_VertexStream.activate();
// render triangles
driver->activeIndexBuffer(_Triangles);
driver->renderTriangles(_Material, 0, _CurrentTriId/3);
}
// swap Stream VBHard
_VertexStream.swapVBHard();
}
// Ok, end rendering
_Rendering= false;
}
// ***************************************************************************
void CLodCharacterManager::setupNormalCorrectionMatrix(const CMatrix &normalMatrix)
{
_LightCorrectionMatrix= normalMatrix;
_LightCorrectionMatrix.setPos(CVector::Null);
_LightCorrectionMatrix.invert();
}
// ***************************************************************************
// ***************************************************************************
// Texturing.
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
CLodCharacterTmpBitmap::CLodCharacterTmpBitmap()
{
reset();
}
// ***************************************************************************
void CLodCharacterTmpBitmap::reset()
{
// setup a 1*1 bitmap
_Bitmap.resize(1);
_Bitmap[0]= CRGBA::Black;
_WidthPower=0;
_UShift= 8;
_VShift= 8;
}
// ***************************************************************************
void CLodCharacterTmpBitmap::build(const NLMISC::CBitmap &bmpIn)
{
uint width= bmpIn.getWidth();
uint height= bmpIn.getHeight();
nlassert(width>0 && width<=256);
nlassert(height>0 && height<=256);
// resize bitmap.
_Bitmap.resize(width*height);
_WidthPower= getPowerOf2(width);
// compute shift
_UShift= 8-getPowerOf2(width);
_VShift= 8-getPowerOf2(height);
// convert the bitmap.
CBitmap bmp= bmpIn;
bmp.convertToType(CBitmap::RGBA);
CRGBA *src= (CRGBA*)&bmp.getPixels()[0];
CRGBA *dst= _Bitmap.getPtr();
for(sint nPix= width*height;nPix>0;nPix--, src++, dst++)
{
*dst= *src;
}
}
// ***************************************************************************
void CLodCharacterTmpBitmap::build(CRGBA col)
{
// setup a 1*1 bitmap and set it with col
reset();
_Bitmap[0]= col;
}
// ***************************************************************************
void CLodCharacterManager::initInstance(CLodCharacterInstance &instance)
{
// first release in (maybe) other manager.
if(instance._Owner)
instance._Owner->releaseInstance(instance);
// get the shape
const CLodCharacterShape *clod= getShape(instance.ShapeId);
// if not found quit
if(!clod)
return;
// get Uvs.
const CUV *uvSrc= clod->getUVs();
nlassert(uvSrc);
// Ok, init header
instance._Owner= this;
instance._UVs.resize(clod->getNumVertices());
// allocate an id. If cannot, then fill Uvs with 0 => filled with Black. (see endTextureCompute() why).
if(_FreeIds.empty())
{
// set a "Not enough memory" id
instance._TextureId= NL3D_CLOD_TEXT_NUM_IDS;
CUV uv(0,0);
fill(instance._UVs.begin(), instance._UVs.end(), uv);
}
// else OK, can instanciate the Uvs.
else
{
// get the id.
instance._TextureId= _FreeIds.back();
_FreeIds.pop_back();
// get the x/y.
uint xId= instance._TextureId % NL3D_CLOD_TEXT_NLOD_WIDTH;
uint yId= instance._TextureId / NL3D_CLOD_TEXT_NLOD_WIDTH;
// compute the scale/bias to apply to Uvs.
float scaleU= 1.0f / NL3D_CLOD_TEXT_NLOD_WIDTH;
float scaleV= 1.0f / NL3D_CLOD_TEXT_NLOD_HEIGHT;
float biasU= (float)xId / NL3D_CLOD_TEXT_NLOD_WIDTH;
float biasV= (float)yId / NL3D_CLOD_TEXT_NLOD_HEIGHT;
// apply it to each UVs.
CUV *uvDst= &instance._UVs[0];
for(uint i=0; i=0 && instance._TextureIdgetPixels(0)[0];
return pix + yId*NL3D_CLOD_TEXT_HEIGHT*NL3D_CLOD_BIGTEXT_WIDTH + xId*NL3D_CLOD_TEXT_WIDTH;
}
// ***************************************************************************
bool CLodCharacterManager::startTextureCompute(CLodCharacterInstance &instance)
{
CRGBA *dst= getTextureInstance(instance);
if(!dst)
return false;
// erase the texture with 0,0,0,255. Alpha is actually the min "Quality" part of the CTUVQ.
CRGBA col= NL3D_CLOD_DEFAULT_TEXCOLOR;
for(uint y=0;y no AlphaTest problems
for(uint y=0;ygetPixels(0)[0]= NL3D_CLOD_DEFAULT_TEXCOLOR;
// get the x/y.
uint xId= instance._TextureId % NL3D_CLOD_TEXT_NLOD_WIDTH;
uint yId= instance._TextureId / NL3D_CLOD_TEXT_NLOD_WIDTH;
// touch the texture for Driver update.
_BigTexture->touchRect(
CRect(xId*NL3D_CLOD_TEXT_WIDTH, yId*NL3D_CLOD_TEXT_HEIGHT, NL3D_CLOD_TEXT_WIDTH, NL3D_CLOD_TEXT_HEIGHT) );
// reset tmpBitmaps / free memory.
for(uint i=0; iwriteTGA(f,32);*/
}
// ***************************************************************************
bool CLodCharacterManager::fastIntersect(const CLodCharacterInstance &instance, const NLMISC::CMatrix &toRaySpace, float &dist2D, float &distZ, bool computeDist2D)
{
H_AUTO ( NL3D_CharacterLod_fastIntersect )
uint numVertices;
const CLodCharacterShape::CVector3s *vertPtr;
CVector matPos;
float a00, a01, a02;
float a10, a11, a12;
float a20, a21, a22;
// Get the Shape and current key.
//=============
// get the shape
const CLodCharacterShape *clod= getShape(instance.ShapeId);
// if not found quit
if(!clod)
return false;
// get the anim key
CVector unPackScaleFactor;
vertPtr= clod->getAnimKey(instance.AnimId, instance.AnimTime, instance.WrapMode, unPackScaleFactor);
// if not found quit
if(!vertPtr)
return false;
// get num verts
numVertices= clod->getNumVertices();
// empty shape??
if(numVertices==0)
return false;
// Prepare Transform
//=============
// Get matrix pos.
matPos= toRaySpace.getPos();
// Get rotation line vectors
const float *rayM= toRaySpace.get();
a00= rayM[0]; a01= rayM[4]; a02= rayM[8];
a10= rayM[1]; a11= rayM[5]; a12= rayM[9];
a20= rayM[2]; a21= rayM[6]; a22= rayM[10];
// multiply matrix with scale factor for Pos.
a00*= unPackScaleFactor.x; a01*= unPackScaleFactor.y; a02*= unPackScaleFactor.z;
a10*= unPackScaleFactor.x; a11*= unPackScaleFactor.y; a12*= unPackScaleFactor.z;
a20*= unPackScaleFactor.x; a21*= unPackScaleFactor.y; a22*= unPackScaleFactor.z;
// get dst Array.
// enlarge temp buffer
static std::vector lodInRaySpace;
if(numVertices>lodInRaySpace.size())
lodInRaySpace.resize(numVertices);
CVector *dstPtr= &lodInRaySpace[0];
// Fill the temp skin
//=============
{
CVector fVect;
for(;numVertices>0;)
{
// transform vertex, and store.
fVect.x= vertPtr->x; fVect.y= vertPtr->y; fVect.z= vertPtr->z;
++vertPtr;
dstPtr->x= a00 * fVect.x + a01 * fVect.y + a02 * fVect.z + matPos.x;
dstPtr->y= a10 * fVect.x + a11 * fVect.y + a12 * fVect.z + matPos.y;
dstPtr->z= a20 * fVect.x + a21 * fVect.y + a22 * fVect.z + matPos.z;
// next
dstPtr++;
numVertices--;
}
}
// Test intersection
//=============
return CRayMesh::getRayIntersection(lodInRaySpace, clod->getTriangleIndices(), dist2D, distZ, computeDist2D);
}
} // NL3D