// 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_tail_dot.h"
#include "nel/3d/ps_macro.h"
#include "nel/3d/driver.h"
#include "nel/3d/particle_system.h"
#include "nel/3d/texture_mem.h"
#include "nel/misc/smart_ptr.h"
#include
#ifdef DEBUG_NEW
#define new DEBUG_NEW
#endif
namespace NL3D
{
static NLMISC::CRGBA GradientB2W[] = {NLMISC::CRGBA(0, 0, 0, 0), NLMISC::CRGBA(255, 255, 255, 255) };
/// private use : this create a gradient texture that goew from black to white
static ITexture *CreateGradientTexture()
{
NL_PS_FUNC(CreateGradientTexture)
CUniquePtr tex(new CTextureMem((uint8 *) &GradientB2W,
sizeof(GradientB2W),
false, /* dont delete */
false, /* not a file */
2, 1)
);
tex->setWrapS(ITexture::Clamp);
tex->setShareName("#GradBW");
return tex.release();
}
///////////////////////////////
// CPSTailDot implementation //
///////////////////////////////
CPSTailDot::TVBMap CPSTailDot::_VBMap; // index / vertex buffers with no color
CPSTailDot::TVBMap CPSTailDot::_FadedVBMap; // index / vertex buffers for constant color with fading
CPSTailDot::TVBMap CPSTailDot::_ColoredVBMap; // index / vertex buffer + colors
CPSTailDot::TVBMap CPSTailDot::_FadedColoredVBMap; // index / vertex buffer + faded colors
//=======================================================
CPSTailDot::CPSTailDot() : _ColorFading(false),
_GlobalColor(false),
_Lighted(false),
_ForceLighted(false),
_Touch(true)
{
NL_PS_FUNC(CPSTailDot_CPSTailDot)
setInterpolationMode(Linear);
setSegDuration(0.06f);
if (CParticleSystem::getSerializeIdentifierFlag()) _Name = std::string("TailDot");
}
//=======================================================
CPSTailDot::~CPSTailDot()
{
NL_PS_FUNC(CPSTailDot_CPSTailDotDtor)
// delete _DyingRibbons;
}
//=======================================================
void CPSTailDot::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
NL_PS_FUNC(CPSTailDot_serial)
sint ver = f.serialVersion(3);
if (ver == 1)
{
nlassert(f.isReading());
/// we had CPSParticle::serial(f), but this is not the base class anymore, so we emulate this...
/// version 2 : auto-lod saved
sint ver2 = f.serialVersion(2);
// here is CPSLocatedBindable::serial(f)
sint ver3 = f.serialVersion(4);
f.serialPtr(_Owner);
if (ver3 > 1) f.serialEnum(_LOD);
if (ver3 > 2) f.serial(_Name);
if (ver3 > 3)
{
if (f.isReading())
{
uint32 id;
f.serial(id);
setExternID(id);
}
else
{
f.serial(_ExternID);
}
}
if (ver2 >= 2)
{
bool bDisableAutoLOD;
f.serial(bDisableAutoLOD);
disableAutoLOD(bDisableAutoLOD);
}
uint32 tailNbSegs;
bool colorFading;
bool systemBasisEnabled;
CPSColoredParticle::serialColorScheme(f);
f.serial(tailNbSegs, colorFading, systemBasisEnabled);
_ColorFading = colorFading;
_NbSegs = tailNbSegs >> 1;
if (_NbSegs < 2) _NbSegs = 2;
setInterpolationMode(Linear);
serialMaterial(f);
nlassert(_Owner);
resize(_Owner->getMaxSize());
initDateVect();
resetFromOwner();
}
if (ver >= 2)
{
CPSRibbonBase::serial(f);
CPSColoredParticle::serialColorScheme(f);
CPSMaterial::serialMaterial(f);
bool colorFading = _ColorFading;
f.serial(colorFading);
_ColorFading = colorFading;
if (ver >= 3)
{
uint32 tailNbSegs = _NbSegs;
f.serial(tailNbSegs);
}
if (f.isReading())
{
setTailNbSeg(_NbSegs);
touch();
}
}
}
//=======================================================
void CPSTailDot::step(TPSProcessPass pass)
{
NL_PS_FUNC(CPSTailDot_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 CPSTailDot::newElement(const CPSEmitterInfo &info)
{
NL_PS_FUNC(CPSTailDot_newElement)
CPSRibbonBase::newElement(info);
newColorElement(info);
}
//=======================================================
void CPSTailDot::deleteElement(uint32 index)
{
NL_PS_FUNC(CPSTailDot_deleteElement)
CPSRibbonBase::deleteElement(index);
deleteColorElement(index);
}
//=======================================================
void CPSTailDot::resize(uint32 size)
{
NL_PS_FUNC(CPSTailDot_resize)
nlassert(size < (1 << 16));
CPSRibbonBase::resize(size);
resizeColor(size);
}
//=======================================================
void CPSTailDot::updateMatAndVbForColor(void)
{
NL_PS_FUNC(CPSTailDot_updateMatAndVbForColor)
touch();
}
//==========================================================================
void CPSTailDot::displayRibbons(uint32 nbRibbons, uint32 srcStep)
{
// if (!FilterPS[8]) return;
NL_PS_FUNC(CPSTailDot_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 (built it if needed)
CVertexBuffer &VB = VBnPB.VB;
CIndexBuffer &PB = VBnPB.PB;
const uint32 vertexSize = VB.getVertexSize();
uint colorOffset=0;
IDriver *drv = this->getDriver();
#ifdef NL_DEBUG
nlassert(drv);
#endif
drv->setupModelMatrix(getLocalToWorldTrailMatrix());
_Owner->incrementNbDrawnParticles(nbRibbons); // for benchmark purpose
const uint numRibbonBatch = getNumRibbonsInVB(); // number of ribons to process at once
if (_UsedNbSegs == 0) return;
////////////////////
// Material setup //
////////////////////
CParticleSystem &ps = *(_Owner->getOwner());
bool useGlobalColor = ps.getColorAttenuationScheme() != NULL || ps.isUserColorUsed();
if (useGlobalColor != _GlobalColor)
{
_GlobalColor = useGlobalColor;
touch();
}
if (usesGlobalColorLighting() != _Lighted)
{
_Lighted = usesGlobalColorLighting();
touch();
}
if (ps.getForceGlobalColorLightingFlag() != _ForceLighted)
{
_ForceLighted = ps.getForceGlobalColorLightingFlag();
touch();
}
updateMaterial();
setupGlobalColor();
//
if (_ColorScheme)
{
colorOffset = VB.getColorOff();
}
/////////////////////
// Compute ribbons //
/////////////////////
uint toProcess;
uint ribbonIndex = 0; // index of the first ribbon in the batch being processed
uint32 fpRibbonIndex = 0; // fixed point index in source
if (_ColorScheme)
{
_ColorScheme->setColorType(drv->getVertexColorFormat());
}
do
{
toProcess = std::min((uint) (nbRibbons - ribbonIndex) /* = left to do */, numRibbonBatch);
VB.setNumVertices((_UsedNbSegs + 1) * toProcess);
{
CVertexBufferReadWrite vba;
VB.lock (vba);
currVert = (uint8 *) vba.getVertexCoordPointer();
/// compute colors
if (_ColorScheme)
{
_ColorScheme->makeN(this->_Owner, ribbonIndex, currVert + colorOffset, vertexSize, toProcess, _UsedNbSegs + 1, srcStep);
}
uint k = toProcess;
//////////////////////////////////////////////////////////////////////////////////////
// interpolate and project points the result is directly setup in the vertex buffer //
//////////////////////////////////////////////////////////////////////////////////////
if (!_Parametric)
{
//////////////////////
// INCREMENTAL CASE //
//////////////////////
do
{
// the parent class has a method to get the ribbons positions
computeRibbon((uint) (fpRibbonIndex >> 16), (CVector *) currVert, vertexSize);
currVert += vertexSize * (_UsedNbSegs + 1);
fpRibbonIndex += srcStep;
}
while (--k);
}
else
{
//////////////////////
// PARAMETRIC CASE //
//////////////////////
do
{
// we compute each pos thanks to the parametric curve
_Owner->integrateSingle(date - _UsedSegDuration * (_UsedNbSegs + 1), _UsedSegDuration, _UsedNbSegs + 1, (uint) (fpRibbonIndex >> 16),
(NLMISC::CVector *) currVert, vertexSize);
currVert += vertexSize * (_UsedNbSegs + 1);
fpRibbonIndex += srcStep;
}
while (--k);
}
}
const uint numLine = _UsedNbSegs * toProcess;
PB.setNumIndexes(2 * numLine);
// display the result
drv->activeIndexBuffer(PB);
drv->activeVertexBuffer(VB);
drv->renderLines (_Mat, 0, numLine);
ribbonIndex += toProcess;
}
while (ribbonIndex != nbRibbons);
}
//==========================================================================
bool CPSTailDot::hasTransparentFaces(void)
{
NL_PS_FUNC(CPSTailDot_hasTransparentFaces)
return getBlendingMode() != CPSMaterial::alphaTest ;
}
//==========================================================================
bool CPSTailDot::hasOpaqueFaces(void)
{
NL_PS_FUNC(CPSTailDot_hasOpaqueFaces)
return !hasTransparentFaces();
}
//==========================================================================
uint32 CPSTailDot::getNumWantedTris() const
{
NL_PS_FUNC(CPSTailDot_getNumWantedTris)
nlassert(_Owner);
//return _Owner->getMaxSize() * _NbSegs;
return _Owner->getSize() * _NbSegs;
}
//==========================================================================
CPSTailDot::CVBnPB &CPSTailDot::getVBnPB()
{
NL_PS_FUNC(CPSTailDot_getVBnPB)
/// choose the right vb
TVBMap &map = _ColorScheme ? (_ColorFading ? _FadedColoredVBMap : _ColoredVBMap) // per ribbon color
: (_ColorFading ? _FadedVBMap : _VBMap); // global color
TVBMap::iterator it = map.find(_UsedNbSegs + 1);
if (it != map.end())
{
return it->second;
}
else // must create this vb, with few different size, it is still interseting, though they are only destroyed at exit
{
const uint numRibbonInVB = getNumRibbonsInVB();
CVBnPB &VBnPB = map[_UsedNbSegs + 1]; // make an entry
/// set the vb format & size
/// In the case of a ribbon with color and fading, we encode the fading in a texture
/// If the ribbon has fading, but only a global color, we encode it in the primary color
CVertexBuffer &vb = VBnPB.VB;
vb.setPreferredMemory(CVertexBuffer::AGPVolatile, true);
vb.setVertexFormat(CVertexBuffer::PositionFlag
|(_ColorScheme || _ColorFading ? CVertexBuffer::PrimaryColorFlag : 0)
| (_ColorScheme && _ColorFading ? CVertexBuffer::TexCoord0Flag : 0));
vb.setNumVertices((_UsedNbSegs + 1) * numRibbonInVB ); // 1 seg = 1 line + terminal vertices
// set the primitive block size
CIndexBuffer &pb = VBnPB.PB;
pb.setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT);
pb.setNumIndexes(2 * _UsedNbSegs * numRibbonInVB);
/// Setup the pb and vb parts. Not very fast but executed only once
uint vbIndex = 0;
uint pbIndex = 0;
CIndexBufferReadWrite ibaWrite;
pb.lock (ibaWrite);
CVertexBufferReadWrite vba;
vb.lock (vba);
for (uint i = 0; i < numRibbonInVB; ++i)
{
for (uint k = 0; k < (_UsedNbSegs + 1); ++k)
{
if (_ColorScheme && _ColorFading)
{
vba.setTexCoord(vbIndex, 0, 0.5f - 0.5f * ((float) k / _UsedNbSegs), 0);
}
else if (_ColorFading)
{
uint8 intensity = (uint8) (255 * (1.f - ((float) k / _UsedNbSegs)));
NLMISC::CRGBA col(intensity, intensity, intensity, intensity);
vba.setColor(vbIndex, col);
}
/// add 1 line in the primitive block
if (k != _UsedNbSegs)
{
ibaWrite.setLine(pbIndex, vbIndex, vbIndex + 1);
pbIndex+=2;
}
++vbIndex;
}
}
vb.setName("CPSTailDot");
NL_SET_IB_NAME(pb, "CPSTailDot");
return VBnPB;
}
}
//==========================================================================
uint CPSTailDot::getNumRibbonsInVB() const
{
NL_PS_FUNC(CPSTailDot_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 CPSTailDot::updateMaterial()
{
NL_PS_FUNC(CPSTailDot_updateMaterial)
if (!_Touch) return;
static NLMISC::CRefPtr ptGradTexture;
CParticleSystem &ps = *(_Owner->getOwner());
if (_ColorScheme)
{ // PER RIBBON COLOR
if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting() || ps.getColorAttenuationScheme() || ps.isUserColorUsed())
{
if (_ColorFading) // global color + fading + per ribbon color
{
// the first stage is used to get fading * global color
// the second stage multiply the result by the diffuse colot
if (ptGradTexture == NULL) // have we got a gradient texture ?
{
ptGradTexture = CreateGradientTexture();
}
_Mat.setTexture(0, ptGradTexture);
CPSMaterial::forceTexturedMaterialStages(2); // use constant color 0 * diffuse, 1 stage needed
SetupModulatedStage(_Mat, 0, CMaterial::Texture, CMaterial::Constant);
SetupModulatedStage(_Mat, 1, CMaterial::Previous, CMaterial::Diffuse);
}
else // per ribbon color with global color
{
CPSMaterial::forceTexturedMaterialStages(1); // use constant color 0 * diffuse, 1 stage needed
SetupModulatedStage(_Mat, 0, CMaterial::Diffuse, CMaterial::Constant);
}
}
else
{
if (_ColorFading) // per ribbon color, fading
{
if (ptGradTexture == NULL) // have we got a gradient texture ?
{
ptGradTexture = CreateGradientTexture();
}
_Mat.setTexture(0, ptGradTexture);
CPSMaterial::forceTexturedMaterialStages(1);
SetupModulatedStage(_Mat, 0, CMaterial::Texture, CMaterial::Diffuse);
}
else // per color ribbon with no fading, and no global color
{
CPSMaterial::forceTexturedMaterialStages(0); // no texture use constant diffuse only
}
}
}
else // GLOBAL COLOR
{
if (_ColorFading)
{
CPSMaterial::forceTexturedMaterialStages(1); // use constant color 0 * diffuse, 1 stage needed
SetupModulatedStage(_Mat, 0, CMaterial::Diffuse, CMaterial::Constant);
}
else // constant color
{
CPSMaterial::forceTexturedMaterialStages(0); // no texture use constant diffuse only
}
}
_Touch = false;
}
//==========================================================================
void CPSTailDot::setupGlobalColor()
{
NL_PS_FUNC(CPSTailDot_setupGlobalColor)
/// setup the global color if it is used
CParticleSystem &ps = *(_Owner->getOwner());
if (_ColorScheme)
{
if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting())
{
_Mat.texConstantColor(0, ps.getGlobalColorLighted());
}
else
{
_Mat.texConstantColor(0, ps.getGlobalColor());
}
}
else // GLOBAL COLOR with / without fading
{
if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting())
{
NLMISC::CRGBA col;
col.modulateFromColor(ps.getGlobalColorLighted(), _Color);
if (_ColorFading)
{
_Mat.texConstantColor(0, col);
}
else // color attenuation, no fading :
{
_Mat.setColor(col);
}
}
else
if (ps.getColorAttenuationScheme() || ps.isUserColorUsed())
{
NLMISC::CRGBA col;
col.modulateFromColor(ps.getGlobalColor(), _Color);
if (_ColorFading)
{
_Mat.texConstantColor(0, col);
}
else // color attenuation, no fading :
{
_Mat.setColor(col);
}
}
else
{
if (_ColorFading)
{
_Mat.texConstantColor(0, _Color);
}
else // constant color
{
_Mat.setColor(_Color);
}
}
}
}
} // NL3D