// 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/3d/ps_face.h"
#include "nel/3d/ps_macro.h"
#include "nel/3d/driver.h"
#include "nel/3d/ps_iterator.h"
#include "nel/3d/particle_system.h"
#include "nel/misc/quat.h"
namespace NL3D
{
using NLMISC::CQuat;
////////////////////////////
// CPSFace implementation //
////////////////////////////
/** Well, we could have put a method template in CPSFace, but some compilers
* want the definition of the methods in the header, and some compilers
* don't want friend with function template, so we use a static method template of a friend class instead,
* which gives us the same result :)
*/
class CPSFaceHelper
{
public:
template
static void drawFaces(T posIt, U indexIt, CPSFace &f, uint size, uint32 srcStep)
{
NL_PS_FUNC(CPSFaceHelper_drawFaces)
PARTICLES_CHECK_MEM;
nlassert(f._Owner);
IDriver *driver = f.getDriver();
CVertexBuffer &vb = f.getNeededVB(*driver);
f.updateMatBeforeRendering(driver, vb);
uint8 *currVertex;
// number of left faces to draw, number of faces to process at once
uint32 leftFaces = size, toProcess;
f._Owner->incrementNbDrawnParticles(size); // for benchmark purpose
f.setupDriverModelMatrix();
float sizeBuf[CPSQuad::quadBufSize];
float *ptSize;
T endPosIt;
// if constant size is used, the pointer points always the same float
uint32 ptSizeIncrement = f._SizeScheme ? 1 : 0;
if (f._ColorScheme)
{
f._ColorScheme->setColorType(driver->getVertexColorFormat());
}
if (f._PrecompBasis.size()) // do we use precomputed basis ?
{
do
{
{
toProcess = leftFaces > (uint32) CPSQuad::quadBufSize ? (uint32) CPSQuad::quadBufSize : leftFaces;
vb.setNumVertices(4 * toProcess);
CVertexBufferReadWrite vba;
vb.lock (vba);
currVertex = (uint8 *) vba.getVertexCoordPointer() ;
if (f._SizeScheme)
{
ptSize = (float *) (f._SizeScheme->make(f._Owner, size - leftFaces, sizeBuf, sizeof(float), toProcess, true, srcStep));
}
else
{
ptSize = &f._ParticleSize;
}
f.updateVbColNUVForRender(vb, size - leftFaces, toProcess, srcStep, *driver);
const uint32 stride = vb.getVertexSize();
endPosIt = posIt + toProcess;
do
{
const CPlaneBasis &currBasis = f._PrecompBasis[*indexIt].Basis;
CHECK_VERTEX_BUFFER(vb, currVertex);
((CVector *) currVertex)->x = (*posIt).x + *ptSize * currBasis.X.x;
((CVector *) currVertex)->y = (*posIt).y + *ptSize * currBasis.X.y;
((CVector *) currVertex)->z = (*posIt).z + *ptSize * currBasis.X.z;
currVertex += stride;
CHECK_VERTEX_BUFFER(vb, currVertex);
((CVector *) currVertex)->x = (*posIt).x + *ptSize * currBasis.Y.x;
((CVector *) currVertex)->y = (*posIt).y + *ptSize * currBasis.Y.y;
((CVector *) currVertex)->z = (*posIt).z + *ptSize * currBasis.Y.z;
currVertex += stride;
CHECK_VERTEX_BUFFER(vb, currVertex);
((CVector *) currVertex)->x = (*posIt).x - *ptSize * currBasis.X.x;
((CVector *) currVertex)->y = (*posIt).y - *ptSize * currBasis.X.y;
((CVector *) currVertex)->z = (*posIt).z - *ptSize * currBasis.X.z;
currVertex += stride;
CHECK_VERTEX_BUFFER(vb, currVertex);
((CVector *) currVertex)->x = (*posIt).x - *ptSize * currBasis.Y.x;
((CVector *) currVertex)->y = (*posIt).y - *ptSize * currBasis.Y.y;
((CVector *) currVertex)->z = (*posIt).z - *ptSize * currBasis.Y.z;
currVertex += stride;
ptSize += ptSizeIncrement;
++indexIt;
++posIt;
}
while (posIt != endPosIt);
}
driver->activeVertexBuffer(vb),
driver->renderRawQuads(f._Mat, 0, toProcess);
leftFaces -= toProcess;
}
while (leftFaces);
}
else
{
// must compute each particle basis at each time
static CPlaneBasis planeBasis[CPSQuad::quadBufSize]; // buffer to compute each particle basis
CPlaneBasis *currBasis;
uint32 ptPlaneBasisIncrement = f._PlaneBasisScheme ? 1 : 0;
const uint32 vSize = vb.getVertexSize();
do
{
{
toProcess = leftFaces > (uint32) CPSQuad::quadBufSize ? (uint32) CPSQuad::quadBufSize : leftFaces;
vb.setNumVertices(4 * toProcess);
CVertexBufferReadWrite vba;
vb.lock (vba);
currVertex = (uint8 *) vba.getVertexCoordPointer() ;
if (f._SizeScheme)
{
ptSize = (float *) (f._SizeScheme->make(f._Owner, size - leftFaces, sizeBuf, sizeof(float), toProcess, true, srcStep));
}
else
{
ptSize = &f._ParticleSize;
}
if (f._PlaneBasisScheme)
{
currBasis = (CPlaneBasis *) (f._PlaneBasisScheme->make(f._Owner, size - leftFaces, planeBasis, sizeof(CPlaneBasis), toProcess, true, srcStep));
}
else
{
currBasis = &f._PlaneBasis;
}
f.updateVbColNUVForRender(vb, size - leftFaces, toProcess, srcStep, *driver);
endPosIt = posIt + toProcess;
do
{
// we use this instead of the + operator, because we avoid 4 constructor calls this way
CHECK_VERTEX_BUFFER(vb, currVertex);
((CVector *) currVertex)->x = (*posIt).x + *ptSize * currBasis->X.x;
((CVector *) currVertex)->y = (*posIt).y + *ptSize * currBasis->X.y;
((CVector *) currVertex)->z = (*posIt).z + *ptSize * currBasis->X.z;
currVertex += vSize;
CHECK_VERTEX_BUFFER(vb, currVertex);
((CVector *) currVertex)->x = (*posIt).x + *ptSize * currBasis->Y.x;
((CVector *) currVertex)->y = (*posIt).y + *ptSize * currBasis->Y.y;
((CVector *) currVertex)->z = (*posIt).z + *ptSize * currBasis->Y.z;
currVertex += vSize;
CHECK_VERTEX_BUFFER(vb, currVertex);
((CVector *) currVertex)->x = (*posIt).x - *ptSize * currBasis->X.x;
((CVector *) currVertex)->y = (*posIt).y - *ptSize * currBasis->X.y;
((CVector *) currVertex)->z = (*posIt).z - *ptSize * currBasis->X.z;
currVertex += vSize;
CHECK_VERTEX_BUFFER(vb, currVertex);
((CVector *) currVertex)->x = (*posIt).x - *ptSize * currBasis->Y.x;
((CVector *) currVertex)->y = (*posIt).y - *ptSize * currBasis->Y.y;
((CVector *) currVertex)->z = (*posIt).z - *ptSize * currBasis->Y.z;
currVertex += vSize;
ptSize += ptSizeIncrement;
++posIt;
currBasis += ptPlaneBasisIncrement;
}
while (posIt != endPosIt);
}
driver->activeVertexBuffer(vb);
driver->renderRawQuads(f._Mat, 0, toProcess);
leftFaces -= toProcess;
}
while (leftFaces);
}
PARTICLES_CHECK_MEM;
}
};
///======================================================================================
CPSFace::CPSFace(CSmartPtr tex) : CPSQuad(tex)
{
NL_PS_FUNC(CPSFace_CPSFace)
if (CParticleSystem::getSerializeIdentifierFlag()) _Name = std::string("Face");
}
///======================================================================================
void CPSFace::step(TPSProcessPass pass)
{
// if (!FilterPS[1]) return;
NL_PS_FUNC(CPSFace_step)
if (pass == PSToolRender) // edition mode only
{
showTool();
return;
}
else if (pass == PSMotion)
{
if (_PrecompBasis.size()) // do we use precomputed basis ?
{
// rotate all precomputed basis
for (CPSVector< CPlaneBasisPair >::V::iterator it = _PrecompBasis.begin(); it != _PrecompBasis.end(); ++it)
{
// not optimized at all, but this will apply to very few elements anyway...
CMatrix mat;
mat.rotate(CQuat(it->Axis, CParticleSystem::EllapsedTime * it->AngularVelocity));
CVector n = mat * it->Basis.getNormal();
it->Basis = CPlaneBasis(n);
}
}
return;
}
else // check this is the right pass
if (!
( (pass == PSBlendRender && hasTransparentFaces())
|| (pass == PSSolidRender && hasOpaqueFaces())
)
)
{
return;
}
if (!_Owner->getSize()) return;
uint32 step;
uint numToProcess;
computeSrcStep(step, numToProcess);
if (!numToProcess) return;
if (step == (1 << 16))
{
/// build index iterator
CPSVector::V::const_iterator indexIt = _IndexInPrecompBasis.begin();
/// draw the faces
CPSFaceHelper::drawFaces(_Owner->getPos().begin(),
indexIt,
*this,
numToProcess,
step
);
}
else
{
/// build index iterator
CAdvance1616Iterator::V::const_iterator, const uint32>
indexIt(_IndexInPrecompBasis.begin(), 0, step);
CPSFaceHelper::drawFaces(TIteratorVectStep1616(_Owner->getPos().begin(), 0, step),
indexIt,
*this,
numToProcess,
step
);
}
}
///======================================================================================
void CPSFace::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
NL_PS_FUNC(CPSFace_IStream )
f.serialVersion(1);
CPSQuad::serial(f);
CPSRotated3DPlaneParticle::serialPlaneBasisScheme(f);
if (f.isReading())
{
uint32 nbConfigurations;
f.serial(nbConfigurations);
if (nbConfigurations)
{
f.serial(_MinAngularVelocity, _MaxAngularVelocity);
}
hintRotateTheSame(nbConfigurations, _MinAngularVelocity, _MaxAngularVelocity);
init();
}
else
{
uint32 nbConfigurations = _PrecompBasis.size();
f.serial(nbConfigurations);
if (nbConfigurations)
{
f.serial(_MinAngularVelocity, _MaxAngularVelocity);
}
}
}
///======================================================================================
/// this produce a random unit vector
static CVector MakeRandomUnitVect(void)
{
NL_PS_FUNC(MakeRandomUnitVect)
CVector v((float) ((rand() % 20000) - 10000)
,(float) ((rand() % 20000) - 10000)
,(float) ((rand() % 20000) - 10000)
);
v.normalize();
return v;
}
///======================================================================================
void CPSFace::hintRotateTheSame(uint32 nbConfiguration
, float minAngularVelocity
, float maxAngularVelocity
)
{
NL_PS_FUNC(CPSFace_hintRotateTheSame)
_MinAngularVelocity = minAngularVelocity;
_MaxAngularVelocity = maxAngularVelocity;
_PrecompBasis.resize(nbConfiguration);
if (nbConfiguration)
{
// each precomp basis is created randomly;
for (uint k = 0; k < nbConfiguration; ++k)
{
CVector v = MakeRandomUnitVect();
_PrecompBasis[k].Basis = CPlaneBasis(v);
_PrecompBasis[k].Axis = MakeRandomUnitVect();
_PrecompBasis[k].AngularVelocity = minAngularVelocity
+ (rand() % 20000) / 20000.f * (maxAngularVelocity - minAngularVelocity);
}
// we need to do this because nbConfs may have changed
fillIndexesInPrecompBasis();
}
}
///======================================================================================
void CPSFace::fillIndexesInPrecompBasis(void)
{
NL_PS_FUNC(CPSFace_fillIndexesInPrecompBasis)
const uint32 nbConf = _PrecompBasis.size();
if (_Owner)
{
_IndexInPrecompBasis.resize( _Owner->getMaxSize() );
}
for (CPSVector::V::iterator it = _IndexInPrecompBasis.begin(); it != _IndexInPrecompBasis.end(); ++it)
{
*it = rand() % nbConf;
}
}
///======================================================================================
void CPSFace::newElement(const CPSEmitterInfo &info)
{
NL_PS_FUNC(CPSFace_newElement)
CPSQuad::newElement(info);
newPlaneBasisElement(info);
const uint32 nbConf = _PrecompBasis.size();
if (nbConf) // do we use precomputed basis ?
{
_IndexInPrecompBasis[_Owner->getNewElementIndex()] = rand() % nbConf;
}
}
///======================================================================================
void CPSFace::deleteElement(uint32 index)
{
NL_PS_FUNC(CPSFace_deleteElement)
CPSQuad::deleteElement(index);
deletePlaneBasisElement(index);
if (_PrecompBasis.size()) // do we use precomputed basis ?
{
// replace ourself by the last element...
_IndexInPrecompBasis[index] = _IndexInPrecompBasis[_Owner->getSize() - 1];
}
}
///======================================================================================
void CPSFace::resize(uint32 size)
{
NL_PS_FUNC(CPSFace_resize)
nlassert(size < (1 << 16));
resizePlaneBasis(size);
if (_PrecompBasis.size()) // do we use precomputed basis ?
{
_IndexInPrecompBasis.resize(size);
}
CPSQuad::resize(size);
}
} // NL3D