khanat-opennel-code/code/nel/src/3d/ps_ribbon_look_at.cpp

631 lines
17 KiB
C++

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "std3d.h"
#include "nel/3d/ps_ribbon_look_at.h"
#include "nel/3d/particle_system.h"
#include "nel/3d/ps_macro.h"
#include "nel/3d/driver.h"
namespace NL3D
{
////////////////////////////////////
// CPSRibbonLookAt implementation //
////////////////////////////////////
const float ZEpsilon = 10E-3f;
const float NormEpsilon = 10E-8f;
struct CVectInfo
{
NLMISC::CVector Interp;
NLMISC::CVector Proj;
};
typedef std::vector<CVectInfo> TRibbonVect; // a vector used for intermediate computations
CPSRibbonLookAt::TVBMap CPSRibbonLookAt::_VBMap; // index buffers with no color
CPSRibbonLookAt::TVBMap CPSRibbonLookAt::_ColoredVBMap; // index buffer + colors
//=======================================================
CPSRibbonLookAt::CPSRibbonLookAt()
{
NL_PS_FUNC(CPSRibbonLookAt_CPSRibbonLookAt)
}
//=======================================================
CPSRibbonLookAt::~CPSRibbonLookAt()
{
NL_PS_FUNC(CPSRibbonLookAt_CPSRibbonLookAtDtor)
// delete _DyingRibbons;
}
//=======================================================
void CPSRibbonLookAt::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
NL_PS_FUNC(CPSRibbonLookAt_serial)
/** Version 4 : added CPSRibbonBase has a base class instead of CPSParticle
*
*/
sint ver = f.serialVersion(4);
if (ver > 3)
{
CPSRibbonBase::serial(f);
}
else
{
CPSParticle::serial(f);
}
CPSColoredParticle::serialColorScheme(f);
CPSSizedParticle::serialSizeScheme(f);
serialMaterial(f);
uint32 dummy = 0; /* _NbDyingRibbons */
if (ver <= 3)
{
f.serial(_SegDuration, _NbSegs, dummy /*_NbDyingRibbons*/);
}
ITexture *tex = NULL;
if (ver > 2)
{
f.serial(_Parametric);
}
if (!f.isReading())
{
tex = _Tex;
f.serialPolyPtr(tex);
}
else
{
f.serialPolyPtr(tex);
setTexture(tex);
_Tex = tex;
if (_Tex)
{
_Tex->setWrapS(ITexture::Clamp);
_Tex->setWrapT(ITexture::Clamp);
}
setTailNbSeg(_NbSegs); // force to build the vb
}
}
//=======================================================
void CPSRibbonLookAt::setTexture(CSmartPtr<ITexture> tex)
{
NL_PS_FUNC(CPSRibbonLookAt_setTexture)
_Tex = tex;
if (_Tex)
{
_Tex->setWrapS(ITexture::Clamp);
_Tex->setWrapT(ITexture::Clamp);
}
updateMatAndVbForColor();
}
//=======================================================
void CPSRibbonLookAt::step(TPSProcessPass pass)
{
NL_PS_FUNC(CPSRibbonLookAt_step)
if (pass == PSMotion)
{
if (!_Parametric)
{
updateGlobals();
}
}
else
if (
(pass == PSBlendRender && hasTransparentFaces())
|| (pass == PSSolidRender && hasOpaqueFaces())
)
{
uint32 step;
uint numToProcess;
computeSrcStep(step, numToProcess);
if (!numToProcess) return;
/// update the material color
CParticleSystem &ps = *(_Owner->getOwner());
if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting())
{
_Mat.setColor(ps.getGlobalColorLighted());
}
else
{
_Mat.setColor(ps.getGlobalColor());
}
/** We support Auto-LOD for ribbons, although there is a built-in LOD (that change the geometry rather than the number of ribbons)
* that gives better result (both can be used simultaneously)
*/
displayRibbons(numToProcess, step);
}
else
if (pass == PSToolRender) // edition mode only
{
//showTool();
}
}
//=======================================================
void CPSRibbonLookAt::newElement(const CPSEmitterInfo &info)
{
NL_PS_FUNC(CPSRibbonLookAt_newElement)
CPSRibbonBase::newElement(info);
newColorElement(info);
newSizeElement(info);
}
//=======================================================
void CPSRibbonLookAt::deleteElement(uint32 index)
{
NL_PS_FUNC(CPSRibbonLookAt_deleteElement)
CPSRibbonBase::deleteElement(index);
deleteColorElement(index);
deleteSizeElement(index);
}
//=======================================================
void CPSRibbonLookAt::resize(uint32 size)
{
NL_PS_FUNC(CPSRibbonLookAt_resize)
nlassert(size < (1 << 16));
CPSRibbonBase::resize(size);
resizeColor(size);
resizeSize(size);
}
//=======================================================
void CPSRibbonLookAt::updateMatAndVbForColor(void)
{
NL_PS_FUNC(CPSRibbonLookAt_updateMatAndVbForColor)
_Mat.setTexture(0, _Tex);
_Mat.setDoubleSided(true);
}
//=======================================================
static inline void MakeProj(NLMISC::CVector &dest, const NLMISC::CVector &src)
{
NL_PS_FUNC(MakeProj)
if (fabsf(src.y) > NormEpsilon * NormEpsilon)
{
dest.x = src.x / src.y;
dest.z = src.z / src.y;
dest.y = src.y;
}
}
static inline void BuildSlice(const NLMISC::CMatrix &mat, CVertexBuffer &vb, uint8 *currVert, uint32 vertexSize,
const NLMISC::CVector &I,
const NLMISC::CVector &K,
TRibbonVect::iterator pos,
TRibbonVect::iterator prev,
TRibbonVect::iterator next,
float ribSize)
/// TODO: some optimisation to get a better speed
{
NL_PS_FUNC(BuildSlice)
CHECK_VERTEX_BUFFER(vb, currVert);
CHECK_VERTEX_BUFFER(vb, currVert);
NLMISC::CVector tangent;
float invTgNorm; // inverse of the' norm of the projected segment
float tgNorm;
if (prev->Proj.y > ZEpsilon && next->Proj.y > ZEpsilon) // the 2 points are in front of the camera
{
tangent = next->Proj - prev->Proj;
tangent.y = 0;
tgNorm = tangent.norm();
if (fabs(tgNorm) > 10E-8)
{
invTgNorm = 1.f / tgNorm;
}
else
{
invTgNorm = 1.f;
}
// build orthogonals vectors to tangent
*(NLMISC::CVector *) currVert = pos->Interp + ribSize * invTgNorm * (tangent.x * K - tangent.z * I);
*(NLMISC::CVector *) (currVert + vertexSize) = pos->Interp + ribSize * invTgNorm * (- tangent.x * K + tangent.z * I);
}
else if (prev->Proj.y > ZEpsilon) // second point cross the near plane
{
// compute intersection point
NLMISC::CVector inter;
NLMISC::CVector tInter = CVector::Null;
if (fabsf(prev->Proj.y - next->Proj.y) > NormEpsilon)
{
float lambda = (next->Proj.y - ZEpsilon) / (next->Proj.y - prev->Proj.y);
inter = lambda * prev->Interp + (1.f - lambda) * next->Interp;
MakeProj(tInter, mat * inter);
}
else //
{
*(NLMISC::CVector *) currVert = pos->Interp;
*(NLMISC::CVector *) (currVert + vertexSize) = pos->Interp;
return;
}
tangent = tInter - prev->Proj;
tangent.y = 0;
tgNorm = tangent.norm();
if (fabs(tgNorm) > 10E-8)
{
invTgNorm = 1.f / tgNorm;
}
else
{
invTgNorm = 1.f;
}
// build orthogonals vectors to tangent
*(NLMISC::CVector *) currVert = inter + ribSize * invTgNorm * (tangent.x * K - tangent.z * I);
*(NLMISC::CVector *) (currVert + vertexSize) = inter + ribSize * invTgNorm * (- tangent.x * K + tangent.z * I);
}
else if (next->Proj.y > ZEpsilon) // first point cross the near plane
{
// compute intersection point
NLMISC::CVector inter;
NLMISC::CVector tInter = NLMISC::CVector::Null;
if (fabsf(prev->Proj.y - next->Proj.y) > NormEpsilon)
{
float lambda = (next->Proj.y - ZEpsilon) / (next->Proj.y - prev->Proj.y);
inter = lambda * prev->Interp + (1.f - lambda) * next->Interp;
MakeProj(tInter, mat * inter);
}
else //
{
*(NLMISC::CVector *) currVert = pos->Interp;
*(NLMISC::CVector *) (currVert + vertexSize) = pos->Interp;
return;
}
tangent = next->Proj - tInter;
tangent.y = 0;
tgNorm = tangent.norm();
if (fabs(tgNorm) > 10E-8)
{
invTgNorm = 1.f / tgNorm;
}
else
{
invTgNorm = 1.f;
}
// build orthogonals vectors to tangent
*(NLMISC::CVector *) currVert = inter + ribSize * invTgNorm * (tangent.x * K - tangent.z * I);
*(NLMISC::CVector *) (currVert + vertexSize) = inter + ribSize * invTgNorm * (- tangent.x * K + tangent.z * I);
}
else // two points are not visible
{
*(NLMISC::CVector *) currVert = pos->Interp;
*(NLMISC::CVector *) (currVert + vertexSize) = pos->Interp;
}
}
//==========================================================================
void CPSRibbonLookAt::displayRibbons(uint32 nbRibbons, uint32 srcStep)
{
// if (!FilterPS[6]) return;
NL_PS_FUNC(CPSRibbonLookAt_displayRibbons)
if (!nbRibbons) return;
nlassert(_Owner);
CPSRibbonBase::updateLOD();
if (_UsedNbSegs < 2) return;
const float date = _Owner->getOwner()->getSystemDate();
uint8 *currVert;
CVBnPB &VBnPB = getVBnPB(); // get the appropriate vb (build it if needed)
CVertexBuffer &VB = VBnPB.VB;
CIndexBuffer &PB = VBnPB.PB;
const uint32 vertexSize = VB.getVertexSize();
uint colorOffset=0;
const uint32 vertexSizeX2 = vertexSize << 1;
const NLMISC::CVector I = _Owner->computeI();
const NLMISC::CVector K = _Owner->computeK();
const NLMISC::CMatrix &localToWorldMatrix = getLocalToWorldTrailMatrix();
const NLMISC::CMatrix &mat = getViewMat() * localToWorldMatrix;
IDriver *drv = this->getDriver();
#ifdef NL_DEBUG
nlassert(drv);
#endif
drv->setupModelMatrix(localToWorldMatrix);
_Owner->incrementNbDrawnParticles(nbRibbons); // for benchmark purpose
const uint numRibbonBatch = getNumRibbonsInVB(); // number of ribbons to process at once
static TRibbonVect currRibbon;
static std::vector<float> sizes;
static std::vector<NLMISC::CRGBA> colors;
if (_UsedNbSegs == 0) return;
currRibbon.resize(_UsedNbSegs + 1);
sizes.resize(numRibbonBatch);
/// update material color
CParticleSystem &ps = *(_Owner->getOwner());
if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting())
{
CPSMaterial::forceModulateConstantColor(true, ps.getGlobalColorLighted());
}
else
if (ps.getColorAttenuationScheme() != NULL || ps.isUserColorUsed())
{
CPSMaterial::forceModulateConstantColor(true, ps.getGlobalColor());
}
else
{
forceModulateConstantColor(false);
_Mat.setColor(ps.getGlobalColor());
}
if (_ColorScheme)
{
colorOffset = VB.getColorOff();
colors.resize(numRibbonBatch);
}
uint toProcess;
uint ribbonIndex = 0; // index of the first ribbon in the batch being processed
uint32 fpRibbonIndex = 0;
if (_ColorScheme)
{
_ColorScheme->setColorType(drv->getVertexColorFormat());
}
do
{
toProcess = std::min((uint) (nbRibbons - ribbonIndex) /* = left to do */, numRibbonBatch);
/// setup sizes
const float *ptCurrSize;
uint32 ptCurrSizeIncrement;
if (_SizeScheme)
{
ptCurrSize = (float *) _SizeScheme->make(this->_Owner, ribbonIndex, &sizes[0], sizeof(float), toProcess, true, srcStep);
ptCurrSizeIncrement = 1;
}
else
{
ptCurrSize = &_ParticleSize;
ptCurrSizeIncrement = 0;
}
/// setup colors
NLMISC::CRGBA *ptCurrColor=0;
if (_ColorScheme)
{
colors.resize(nbRibbons);
ptCurrColor = (NLMISC::CRGBA *) _ColorScheme->make(this->_Owner, ribbonIndex, &colors[0], sizeof(NLMISC::CRGBA), toProcess, true, srcStep);
}
VB.setNumVertices(2 * (_UsedNbSegs + 1) * toProcess);
{
CVertexBufferReadWrite vba;
VB.lock (vba);
currVert = (uint8 *) vba.getVertexCoordPointer();
for (uint k = ribbonIndex; k < ribbonIndex + toProcess; ++k)
{
TRibbonVect::iterator rIt = currRibbon.begin(), rItEnd = currRibbon.end(), rItEndMinusOne = rItEnd - 1;
////////////////////////////////////
// interpolate and project points //
////////////////////////////////////
if (!_Parametric)
{
//////////////////////
// INCREMENTAL CASE //
//////////////////////
// the parent class has a method to get the ribbons positions
computeRibbon((uint) (fpRibbonIndex >> 16), &rIt->Interp, sizeof(CVectInfo));
do
{
MakeProj(rIt->Proj, mat * rIt->Interp);
++rIt;
}
while (rIt != rItEnd);
}
else
{
//////////////////////
// PARAMETRIC CASE //
//////////////////////
// we compute each pos thanks to the parametric curve
_Owner->integrateSingle(date - _UsedSegDuration * (_UsedNbSegs + 1), _UsedSegDuration, _UsedNbSegs + 1, (uint) (fpRibbonIndex >> 16),
&rIt->Interp, sizeof(CVectInfo) );
// project each position now
do
{
MakeProj(rIt->Proj, mat * rIt->Interp);
++rIt;
}
while (rIt != rItEnd);
}
rIt = currRibbon.begin();
// setup colors
if (_ColorScheme)
{
uint8 *currColVertex = currVert + colorOffset;
uint colCount = (_UsedNbSegs + 1) << 1;
do
{
* (CRGBA *) currColVertex = *ptCurrColor;
currColVertex += vertexSize;
}
while (--colCount);
++ptCurrColor;
}
/// build the ribbon in vb
// deals with first point
BuildSlice(mat, VB, currVert, vertexSize, I, K, rIt, rIt, rIt + 1, *ptCurrSize);
currVert += vertexSizeX2;
++rIt;
// deals with other points
for (;;) // we assume at least 2 segments, so we must have a middle point
{
// build 2 vertices with the right tangent. /* to project 2 */ is old projected point
BuildSlice(mat, VB, currVert, vertexSize, I, K, rIt, rIt - 1, rIt + 1, *ptCurrSize);
// next position
++rIt;
if (rIt == rItEndMinusOne) break;
// next vertex
currVert += vertexSizeX2;
}
currVert += vertexSizeX2;
// last point.
BuildSlice(mat, VB, currVert, vertexSize, I, K, rIt , rIt - 1, rIt, *ptCurrSize);
ptCurrSize += ptCurrSizeIncrement;
currVert += vertexSizeX2;
fpRibbonIndex += srcStep;
}
}
PB.setNumIndexes((_UsedNbSegs << 1) * toProcess * 3);
drv->activeVertexBuffer(VB);
// display the result
drv->activeIndexBuffer (PB);
drv->renderTriangles (_Mat, 0, PB.getNumIndexes()/3);
ribbonIndex += toProcess;
}
while (ribbonIndex != nbRibbons);
}
//==========================================================================
bool CPSRibbonLookAt::hasTransparentFaces(void)
{
NL_PS_FUNC(CPSRibbonLookAt_hasTransparentFaces)
return getBlendingMode() != CPSMaterial::alphaTest ;
}
//==========================================================================
bool CPSRibbonLookAt::hasOpaqueFaces(void)
{
NL_PS_FUNC(CPSRibbonLookAt_hasOpaqueFaces)
return !hasTransparentFaces();
}
//==========================================================================
uint32 CPSRibbonLookAt::getNumWantedTris() const
{
NL_PS_FUNC(CPSRibbonLookAt_getNumWantedTris)
nlassert(_Owner);
//return _Owner->getMaxSize() * _NbSegs * 2;
return _Owner->getSize() * _NbSegs * 2;
}
//==========================================================================
CPSRibbonLookAt::CVBnPB &CPSRibbonLookAt::getVBnPB()
{
NL_PS_FUNC(CPSRibbonLookAt_getVBnPB)
TVBMap &map = _ColorScheme ? _VBMap : _ColoredVBMap;
TVBMap::iterator it = map.find(_UsedNbSegs + 1);
if (it != map.end())
{
return it->second;
}
else // must create this vb
{
const uint numRibbonInVB = getNumRibbonsInVB();
CVBnPB &VBnPB = map[_UsedNbSegs + 1]; // make an entry
/// set the vb format & size
CVertexBuffer &vb = VBnPB.VB;
vb.setVertexFormat(CVertexBuffer::PositionFlag |
CVertexBuffer::TexCoord0Flag |
(_ColorScheme ? CVertexBuffer::PrimaryColorFlag : 0));
vb.setNumVertices(2 * (_UsedNbSegs + 1) * numRibbonInVB );
vb.setPreferredMemory(CVertexBuffer::AGPVolatile, true);
CVertexBufferReadWrite vba;
vb.lock (vba);
// set the primitive block size
CIndexBuffer &pb = VBnPB.PB;
pb.setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT);
pb.setNumIndexes((_UsedNbSegs << 1) * numRibbonInVB * 3);
CIndexBufferReadWrite iba;
pb.lock (iba);
/// Setup the pb and vb parts. Not very fast but executed only once
uint vbIndex = 0;
uint pbIndex = 0;
for (uint i = 0; i < numRibbonInVB; ++i)
{
for (uint k = 0; k < (_UsedNbSegs + 1); ++k)
{
vba.setTexCoord(vbIndex, 0, CUV((1.f - k / (float) _UsedNbSegs), 0)); /// top vertex
vba.setTexCoord(vbIndex + 1, 0, CUV((1.f - k / (float) _UsedNbSegs), 1)); /// bottom vertex
if (k != _UsedNbSegs)
{
/// add 2 tri in the primitive block
iba.setTri(pbIndex, vbIndex + 1, vbIndex + 2, vbIndex);
iba.setTri(pbIndex+3, vbIndex + 1, vbIndex + 3, vbIndex + 2);
pbIndex+=6;
}
vbIndex += 2;
}
}
return VBnPB;
}
}
//==========================================================================
uint CPSRibbonLookAt::getNumRibbonsInVB() const
{
NL_PS_FUNC(CPSRibbonLookAt_getNumRibbonsInVB)
/// approximation of the max number of vertices we want in a vb
const uint vertexInVB = 256;
return std::max(1u, (uint) (vertexInVB / (_UsedNbSegs + 1)));
}
//==========================================================================
void CPSRibbonLookAt::enumTexs(std::vector<NLMISC::CSmartPtr<ITexture> > &dest, IDriver &drv)
{
NL_PS_FUNC(CPSRibbonLookAt_enumTexs)
if (_Tex)
{
dest.push_back(_Tex);
_Tex->getShareName();
}
}
} // NL3D