// 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