// 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_ribbon.h" #include "nel/3d/ps_macro.h" #include "nel/3d/particle_system.h" #include "nel/3d/driver.h" #include "nel/3d/ps_util.h" #include "nel/3d/texture_mem.h" #include "nel/misc/matrix.h" namespace NL3D { static NLMISC::CRGBA GradientB2W[] = {NLMISC::CRGBA(0, 0, 0, 0), NLMISC::CRGBA(255, 255, 255, 255) }; CPSRibbon::TVBMap CPSRibbon::_VBMaps[16]; /// private use : this create a gradient texture that goew from black to white static ITexture *CreateGradientTexture() { NL_PS_FUNC(CreateGradientTexture) std::auto_ptr 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(); } /////////////////////////// // ribbon implementation // /////////////////////////// // predifined shapes const NLMISC::CVector CPSRibbon::Triangle[] = { NLMISC::CVector(0, 1, 0), NLMISC::CVector(1, -1, 0), NLMISC::CVector(-1, -1, 0), }; const NLMISC::CVector CPSRibbon::Losange[] = { NLMISC::CVector(0, 1.f, 0), NLMISC::CVector(1.f, 0, 0), NLMISC::CVector(0, -1.f, 0), NLMISC::CVector(-1.f, 0, 0) }; const NLMISC::CVector CPSRibbon::HeightSides[] = { NLMISC::CVector(-0.5f, 1, 0), NLMISC::CVector(0.5f, 1, 0), NLMISC::CVector(1, 0.5f, 0), NLMISC::CVector(1, -0.5f, 0), NLMISC::CVector(0.5f, -1, 0), NLMISC::CVector(-0.5f, -1, 0), NLMISC::CVector(-1, -0.5f, 0), NLMISC::CVector(-1, 0.5f, 0) }; const NLMISC::CVector CPSRibbon::Pentagram[] = { NLMISC::CVector(0, 1, 0), NLMISC::CVector(1, -1, 0), NLMISC::CVector(-1, 0, 0), NLMISC::CVector(1, 0, 0), NLMISC::CVector(-1, -1, 0) }; const NLMISC::CVector CPSRibbon::SimpleSegmentX[] = { NLMISC::CVector(1, 0, 0), NLMISC::CVector(-1, 0, 0), }; const uint CPSRibbon::NbVerticesInSimpleSegmentX = sizeof(CPSRibbon::SimpleSegmentX) / sizeof(CVector); const NLMISC::CVector CPSRibbon::SimpleSegmentY[] = { NLMISC::CVector(0, 1, 0), NLMISC::CVector(0, -1, 0), }; const uint CPSRibbon::NbVerticesInSimpleSegmentY = sizeof(CPSRibbon::SimpleSegmentY) / sizeof(CVector); const NLMISC::CVector CPSRibbon::SimpleSegmentZ[] = { NLMISC::CVector(0, 0, 1), NLMISC::CVector(0, 0, -1), }; const uint CPSRibbon::NbVerticesInSimpleSegmentZ = sizeof(CPSRibbon::SimpleSegmentZ) / sizeof(CVector); const NLMISC::CVector CPSRibbon::SimpleBrace[] = { NLMISC::CVector(1, 0, 0), NLMISC::CVector(-1, 0, 0), NLMISC::CVector(0, 1, 0), NLMISC::CVector(0, -1, 0) }; const uint CPSRibbon::NbVerticesInSimpleBrace = sizeof(CPSRibbon::SimpleBrace) / sizeof(CVector); const uint CPSRibbon::NbVerticesInTriangle = sizeof(CPSRibbon::Triangle) / sizeof(CVector); const uint CPSRibbon::NbVerticesInLosange = sizeof(Losange) / sizeof(CVector); const uint CPSRibbon::NbVerticesInHeightSide = sizeof(CPSRibbon::HeightSides) / sizeof(CVector); const uint CPSRibbon::NbVerticesInPentagram = sizeof(CPSRibbon::Pentagram) / sizeof(CVector); struct CDummy2DAngle : CPSRotated2DParticle { CPSLocated *getAngle2DOwner(void) { return NULL; } }; ///================================================================================================================== void CPSRibbon::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSRibbon_serial) // Version 3 : - added brace mode // - added orientation enum 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; bool drEnabled; // dying ribbons, not supported in this version CPSColoredParticle::serialColorScheme(f); CPSSizedParticle::serialSizeScheme(f); // we dont use the 2d angle anymore...serial a dummy one { CDummy2DAngle _Dummy2DAngle; _Dummy2DAngle.serialAngle2DScheme(f); } f.serial(colorFading, systemBasisEnabled); serialMaterial(f); f.serial(drEnabled); f.serial(tailNbSegs); ITexture *tex = NULL; f.serialPolyPtr(tex); _Tex = tex; if (_Tex != NULL) { f.serial(_UFactor, _VFactor) ; } // shape serialization f.serialCont(_Shape); _NbSegs = tailNbSegs >> 1; if (_NbSegs < 1) _NbSegs = 2; setInterpolationMode(Linear); nlassert(_Owner); resize(_Owner->getMaxSize()); initDateVect(); resetFromOwner(); } if (ver >= 2) { CPSRibbonBase::serial(f); CPSColoredParticle::serialColorScheme(f); CPSSizedParticle::serialSizeScheme(f); CPSMaterial::serialMaterial(f); f.serialCont(_Shape); bool colorFading = _ColorFading; f.serial(colorFading); _ColorFading = colorFading; uint32 tailNbSegs = _NbSegs; f.serial(tailNbSegs); if (f.isReading()) { setTailNbSeg(_NbSegs); touch(); } ITexture *tex = _Tex; f.serialPolyPtr(tex); _Tex = tex; if (_Tex != NULL) { f.serial(_UFactor, _VFactor) ; } } if (ver >= 3) { bool braceMode = _BraceMode; f.serial(braceMode); _BraceMode = braceMode; f.serialEnum(_Orientation); } if (f.isReading()) { touch(); } } //======================================================= CPSRibbon::CPSRibbon() : _UFactor(1.f), _VFactor(1.f), _Orientation(FollowPath), _BraceMode(true), _ColorFading(true), _GlobalColor(false), _Lighted(false), _ForceLighted(false), _Touch(true) { NL_PS_FUNC(CPSRibbon_CPSRibbon) setInterpolationMode(Linear); setSegDuration(0.06f); if (CParticleSystem::getSerializeIdentifierFlag()) _Name = std::string("Ribbon"); setShape(Triangle, NbVerticesInTriangle); _Mat.setDoubleSided(true); } //======================================================= CPSRibbon::~CPSRibbon() { NL_PS_FUNC(CPSRibbon_CPSRibbonDtor) } //========================================================================== inline uint CPSRibbon::getNumVerticesInSlice() const { NL_PS_FUNC(CPSRibbon_getNumVerticesInSlice) if (_BraceMode) { return (uint)_Shape.size(); } else { return (uint)_Shape.size() + (_Tex == NULL ? 0 : 1); } } //======================================================= void CPSRibbon::step(TPSProcessPass pass) { NL_PS_FUNC(CPSRibbon_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 CPSRibbon::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSRibbon_newElement) CPSRibbonBase::newElement(info); newColorElement(info); newSizeElement(info); } //======================================================= void CPSRibbon::deleteElement(uint32 index) { NL_PS_FUNC(CPSRibbon_deleteElement) CPSRibbonBase::deleteElement(index); deleteColorElement(index); deleteSizeElement(index); } //======================================================= void CPSRibbon::resize(uint32 size) { NL_PS_FUNC(CPSRibbon_resize) nlassert(size < (1 << 16)); CPSRibbonBase::resize(size); resizeColor(size); resizeSize(size); } //======================================================= void CPSRibbon::updateMatAndVbForColor(void) { NL_PS_FUNC(CPSRibbon_updateMatAndVbForColor) touch(); } ///========================================================================= // Create the start slice of a ribbon (all vertices at the same pos) static inline uint8 *BuildRibbonFirstSlice(const NLMISC::CVector &pos, uint numVerts, uint8 *dest, uint vertexSize ) { NL_PS_FUNC(BuildRibbonFirstSlice) do { * (NLMISC::CVector *) dest = pos; dest += vertexSize; } while (--numVerts); return dest; } ///========================================================================= // This compute one slice of a ribbon, and return the next vertex to be filled static inline uint8 *ComputeRibbonSliceFollowPath(const NLMISC::CVector &prev, const NLMISC::CVector &next, const NLMISC::CVector *shape, uint numVerts, uint8 *dest, uint vertexSize, float size, NLMISC::CMatrix &basis ) { NL_PS_FUNC(ComputeRibbonSliceFollowPath) // compute a basis from the next and previous position. // (not optimized for now, but not widely used, either...) const float epsilon = 10E-5f; if (fabsf(next.x - prev.x) > epsilon || fabsf(next.y - prev.y) > epsilon || fabsf(next.z - prev.z) > epsilon) { // build a new basis, or use the previous one otherwise CPSUtil::buildSchmidtBasis(next - prev, basis); } basis.setPos(next); const NLMISC::CVector *shapeEnd = shape + numVerts; do { *(NLMISC::CVector *) dest = basis * (size * (*shape)); ++shape; dest += vertexSize; } while (shape != shapeEnd); return dest; } ///========================================================================= // This compute one slice of a ribbon, and return the next vertex to be filled static inline uint8 *ComputeRibbonSliceIdentity(const NLMISC::CVector &prev, const NLMISC::CVector &next, const NLMISC::CVector *shape, uint numVerts, uint8 *dest, uint vertexSize, float size ) { NL_PS_FUNC(ComputeRibbonSliceIdentity) const NLMISC::CVector *shapeEnd = shape + numVerts; do { ((NLMISC::CVector *) dest)->set(size * shape->x + next.x, size * shape->y + next.y, size * shape->z + next.z); ++shape; dest += vertexSize; } while (shape != shapeEnd); return dest; } ///========================================================================= static inline uint8 *ComputeRibbonSliceFollowPathXY(const NLMISC::CVector &prev, const NLMISC::CVector &next, const NLMISC::CVector *shape, uint numVerts, uint8 *dest, uint vertexSize, float size, NLMISC::CMatrix &basis ) { NL_PS_FUNC(ComputeRibbonSliceFollowPathXY) float deltaX = next.x - prev.x; float deltaY = next.y - prev.y; const float epsilon = 10E-5f; if (fabsf(deltaX) > epsilon || fabsf(deltaY) > epsilon) { float norm = sqrtf(NLMISC::sqr(deltaX) + NLMISC::sqr(deltaY)); float invNorm = (norm != 0.f) ? 1.f / norm : 0.f; NLMISC::CVector I, J; J.set(deltaX * invNorm, deltaY * invNorm, 0.f); I.set(-J.y, J.x, 0.f); basis.setRot(I, CVector::K, J, true); } basis.setPos(next); const NLMISC::CVector *shapeEnd = shape + numVerts; do { *(NLMISC::CVector *) dest = basis * (size * (*shape)); ++shape; dest += vertexSize; } while (shape != shapeEnd); return dest; } ///========================================================================= // This is used to compute a ribbon mesh from its curve and its base shape. // This is for untextured versions (no need to duplicate the last vertex of each slice) static inline uint8 *ComputeUntexturedRibbonMesh(uint8 *destVb, uint vertexSize, const NLMISC::CVector *curve, const NLMISC::CVector *shape, uint numSegs, uint numVerticesInShape, float sizeIncrement, float size, CPSRibbon::TOrientation orientation ) { NL_PS_FUNC(ComputeUntexturedRibbonMesh) CMatrix basis; basis.scale(0); switch(orientation) { case CPSRibbon::FollowPath: do { destVb = ComputeRibbonSliceFollowPath(curve[1], curve[0], shape, numVerticesInShape, destVb, vertexSize, size, basis); ++ curve; size -= sizeIncrement; } while (--numSegs); break; case CPSRibbon::FollowPathXY: do { destVb = ComputeRibbonSliceFollowPathXY(curve[1], curve[0], shape, numVerticesInShape, destVb, vertexSize, size, basis); ++ curve; size -= sizeIncrement; } while (--numSegs); break; case CPSRibbon::Identity: do { destVb = ComputeRibbonSliceIdentity(curve[1], curve[0], shape, numVerticesInShape, destVb, vertexSize, size ); ++ curve; size -= sizeIncrement; } while (--numSegs); break; default: nlassert(0); break; } return BuildRibbonFirstSlice(curve[0], numVerticesInShape, destVb, vertexSize); } ///========================================================================= // This is used to compute a ribbon mesh from its curve and its base shape. // (Textured Version) static inline uint8 *ComputeTexturedRibbonMesh(uint8 *destVb, uint vertexSize, const NLMISC::CVector *curve, const NLMISC::CVector *shape, uint numSegs, uint numVerticesInShape, float sizeIncrement, float size, CPSRibbon::TOrientation orientation ) { NL_PS_FUNC(ComputeTexturedRibbonMesh) CMatrix basis; basis.scale(0); switch(orientation) { case CPSRibbon::FollowPath: do { uint8 *nextDestVb = ComputeRibbonSliceFollowPath(curve[1], curve[0], shape, numVerticesInShape, destVb, vertexSize, size, basis ); // duplicate last vertex ( equal first) * (NLMISC::CVector *) nextDestVb = * (NLMISC::CVector *) destVb; destVb = nextDestVb + vertexSize; // ++ curve; size -= sizeIncrement; } while (--numSegs); break; case CPSRibbon::FollowPathXY: do { uint8 *nextDestVb = ComputeRibbonSliceFollowPathXY(curve[1], curve[0], shape, numVerticesInShape, destVb, vertexSize, size, basis ); // duplicate last vertex ( equal first) * (NLMISC::CVector *) nextDestVb = * (NLMISC::CVector *) destVb; destVb = nextDestVb + vertexSize; // ++ curve; size -= sizeIncrement; } while (--numSegs); break; case CPSRibbon::Identity: do { uint8 *nextDestVb = ComputeRibbonSliceIdentity(curve[1], curve[0], shape, numVerticesInShape, destVb, vertexSize, size ); // duplicate last vertex ( equal first) * (NLMISC::CVector *) nextDestVb = * (NLMISC::CVector *) destVb; destVb = nextDestVb + vertexSize; // ++ curve; size -= sizeIncrement; } while (--numSegs); break; default: nlassert(0); break; } return BuildRibbonFirstSlice(curve[0], numVerticesInShape + 1, destVb, vertexSize); } //========================================================================== void CPSRibbon::displayRibbons(uint32 nbRibbons, uint32 srcStep) { // if (!FilterPS[5]) return; NL_PS_FUNC(CPSRibbon_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 // ///////////////////// const uint numVerticesInSlice = getNumVerticesInSlice(); const uint numVerticesInShape = (uint)_Shape.size(); // static std::vector sizes; static std::vector ribbonPos; // this is where the position of each ribbon slice center i stored ribbonPos.resize(_UsedNbSegs + 1); // make sure we have enough room sizes.resize(numRibbonBatch); // 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) , numRibbonBatch); VB.setNumVertices((_UsedNbSegs + 1) * toProcess * numVerticesInSlice); { CVertexBufferReadWrite vba; VB.lock(vba); currVert = (uint8 *) vba.getVertexCoordPointer(); /// 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; } /// compute colors if (_ColorScheme) { _ColorScheme->makeN(this->_Owner, ribbonIndex, currVert + colorOffset, vertexSize, toProcess, numVerticesInSlice * (_UsedNbSegs + 1), srcStep); } uint k = toProcess; ////////////////////////////////////////////////////////////////////////////////////// // interpolate and project points the result is directly setup in the vertex buffer // ////////////////////////////////////////////////////////////////////////////////////// if (!_Parametric) { ////////////////////// // INCREMENTAL CASE // ////////////////////// if (_Tex != NULL && !_BraceMode) // textured case : must duplicate last vertex, unless in brace mod { do { const float ribbonSizeIncrement = *ptCurrSize / (float) _UsedNbSegs; ptCurrSize += ptCurrSizeIncrement; // the parent class has a method to get the ribbons positions computeRibbon((uint) (fpRibbonIndex >> 16), &ribbonPos[0], sizeof(NLMISC::CVector)); currVert = ComputeTexturedRibbonMesh(currVert, vertexSize, &ribbonPos[0], &_Shape[0], _UsedNbSegs, numVerticesInShape, ribbonSizeIncrement, *ptCurrSize, _Orientation ); fpRibbonIndex += srcStep; } while (--k); } else // untextured case { do { const float ribbonSizeIncrement = *ptCurrSize / (float) _UsedNbSegs; ptCurrSize += ptCurrSizeIncrement; // the parent class has a method to get the ribbons positions computeRibbon((uint) (fpRibbonIndex >> 16), &ribbonPos[0], sizeof(NLMISC::CVector)); currVert = ComputeUntexturedRibbonMesh(currVert, vertexSize, &ribbonPos[0], &_Shape[0], _UsedNbSegs, numVerticesInShape, ribbonSizeIncrement, *ptCurrSize, _Orientation ); fpRibbonIndex += srcStep; } while (--k); } } else { ////////////////////// // PARAMETRIC CASE // ////////////////////// if (_Tex != NULL) // textured case { do { const float ribbonSizeIncrement = *ptCurrSize / (float) _UsedNbSegs; ptCurrSize += ptCurrSizeIncrement; _Owner->integrateSingle(date - _UsedSegDuration * (_UsedNbSegs + 1), _UsedSegDuration, _UsedNbSegs + 1, (uint) (fpRibbonIndex >> 16), &ribbonPos[0]); currVert = ComputeTexturedRibbonMesh(currVert, vertexSize, &ribbonPos[0], &_Shape[0], _UsedNbSegs, numVerticesInShape, ribbonSizeIncrement, *ptCurrSize, _Orientation ); fpRibbonIndex += srcStep; } while (--k); } else // untextured case { do { const float ribbonSizeIncrement = *ptCurrSize / (float) _UsedNbSegs; ptCurrSize += ptCurrSizeIncrement; _Owner->integrateSingle(date - _UsedSegDuration * (_UsedNbSegs + 1), _UsedSegDuration, _UsedNbSegs + 1, (uint) (fpRibbonIndex >> 16), &ribbonPos[0]); currVert = ComputeUntexturedRibbonMesh(currVert, vertexSize, &ribbonPos[0], &_Shape[0], _UsedNbSegs, numVerticesInShape, ribbonSizeIncrement, *ptCurrSize, _Orientation ); fpRibbonIndex += srcStep; } while (--k); } } } // display the result uint numTri = numVerticesInShape * _UsedNbSegs * toProcess; if (!_BraceMode) { numTri <<= 1; } PB.setNumIndexes(3 * numTri); drv->activeIndexBuffer(PB); drv->activeVertexBuffer(VB); drv->renderTriangles(_Mat, 0, numTri); ribbonIndex += toProcess; } while (ribbonIndex != nbRibbons); } //========================================================================== bool CPSRibbon::hasTransparentFaces(void) { NL_PS_FUNC(CPSRibbon_hasTransparentFaces) return getBlendingMode() != CPSMaterial::alphaTest ; } //========================================================================== bool CPSRibbon::hasOpaqueFaces(void) { NL_PS_FUNC(CPSRibbon_hasOpaqueFaces) return !hasTransparentFaces(); } //========================================================================== uint32 CPSRibbon::getNumWantedTris() const { NL_PS_FUNC(CPSRibbon_getNumWantedTris) nlassert(_Owner); //return _Owner->getMaxSize() * _NbSegs; return _Owner->getSize() * _NbSegs; } //========================================================================== // Set a tri in ribbon with check added static inline void setTri(CIndexBufferReadWrite &ibrw, const CVertexBuffer &vb, uint32 triIndex, uint32 i0, uint32 i1, uint32 i2) { nlassert(i0 < vb.getNumVertices()); nlassert(i1 < vb.getNumVertices()); nlassert(i2 < vb.getNumVertices()); ibrw.setTri(triIndex, i0, i1, i2); } //========================================================================== CPSRibbon::CVBnPB &CPSRibbon::getVBnPB() { NL_PS_FUNC(CPSRibbon_getVBnPB) // TODO : vb pointer caching ? // TODO : better vb reuse ? /// choose the right vb by building an index for lookup into 'vbMaps' defined above TVBMap &map = _VBMaps[ (_BraceMode ? 8 : 0) | // set bit 3 if brace mode (_Tex != NULL ? 4 : 0) | // set bit 2 if textured (_ColorScheme != NULL ? 2 : 0) | // set bit 1 if per ribbon color (_ColorFading ? 1 : 0) // set bit 0 if color fading ]; const uint numVerticesInSlice = getNumVerticesInSlice(); /// 1 vertex added for textured ribbon (to avoid texture stretching) const uint numVerticesInShape = (uint)_Shape.size(); // The number of slice is encoded in the upper word of the vb index // The number of vertices per slices is encoded in the lower word uint VBnPDIndex = ((_UsedNbSegs + 1) << 16) | numVerticesInSlice; TVBMap::iterator it = map.find(VBnPDIndex); 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[VBnPDIndex]; // make an entry CIndexBuffer &pb = VBnPB.PB; CVertexBuffer &vb = VBnPB.VB; vb.setPreferredMemory(CVertexBuffer::AGPVolatile, true); // keep local memory because of interleaved format /// 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 vb.setVertexFormat(CVertexBuffer::PositionFlag | /* alway need position */ (_ColorScheme || _ColorFading ? CVertexBuffer::PrimaryColorFlag : 0) | /* need a color ? */ ((_ColorScheme && _ColorFading) || _Tex != NULL ? CVertexBuffer::TexCoord0Flag : 0) | /* need texture coordinates ? */ (_Tex != NULL && _ColorScheme && _ColorFading ? CVertexBuffer::TexCoord1Flag : 0) /* need 2nd texture coordinates ? */ ); vb.setNumVertices((_UsedNbSegs + 1) * numRibbonInVB * numVerticesInSlice); // 1 seg = 1 line + terminal vertices pb.setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT); // set the primitive block size if (_BraceMode) { pb.setNumIndexes(6 * _UsedNbSegs * numRibbonInVB * (uint32)(_Shape.size() / 2)); } else { pb.setNumIndexes(6 * _UsedNbSegs * numRibbonInVB * (uint32)_Shape.size()); } // CIndexBufferReadWrite ibaWrite; pb.lock (ibaWrite); CVertexBufferReadWrite vba; vb.lock(vba); /// Setup the pb and vb parts. Not very fast but executed only once uint vbIndex = 0; uint pbIndex = 0; uint i, k, l; for (i = 0; i < numRibbonInVB; ++i) { for (k = 0; k < (_UsedNbSegs + 1); ++k) { /// setup primitive block if (k != _UsedNbSegs) /// there are alway one more slice than segments in the ribbon... { if (_BraceMode) { uint vIndex = vbIndex; for (l = 0; l < numVerticesInShape / 2; ++l) /// deals with segment { setTri(ibaWrite, vb, pbIndex, vIndex, vIndex + numVerticesInSlice, vIndex + numVerticesInSlice + 1); pbIndex+=3; setTri(ibaWrite, vb, pbIndex, vIndex, vIndex + numVerticesInSlice + 1, vIndex + 1); pbIndex+=3; vIndex += 2; } } else { uint vIndex = vbIndex; for (l = 0; l < (numVerticesInShape - 1); ++l) /// deals with each ribbon vertices { setTri(ibaWrite, vb, pbIndex, vIndex, vIndex + numVerticesInSlice, vIndex + numVerticesInSlice + 1); pbIndex+=3; setTri(ibaWrite, vb, pbIndex, vIndex, vIndex + numVerticesInSlice + 1, vIndex + 1); pbIndex+=3; ++ vIndex; } /// the last 2 index don't loop if there's a texture uint nextVertexIndex = (numVerticesInShape == numVerticesInSlice) ? vIndex + 1 - numVerticesInShape // no texture -> we loop : vIndex + 1; // a texture is used : use onemore vertex setTri(ibaWrite, vb, pbIndex, vIndex, vIndex + numVerticesInSlice, nextVertexIndex + numVerticesInSlice); pbIndex+=3; setTri(ibaWrite, vb, pbIndex, vIndex, nextVertexIndex + numVerticesInSlice, nextVertexIndex); pbIndex+=3; } } /// setup vb if (_BraceMode) { for (l = 0; l < numVerticesInSlice / 2; ++l) /// deals with each ribbon vertices { nlassert(vbIndex < vb.getNumVertices()); /// setup texture (if any) if (_Tex != NULL) { vba.setTexCoord(vbIndex, _ColorScheme && _ColorFading ? 1 : 0, // must we use the second texture coord ? (when 1st one used by the gradient texture : we can't encode it in the diffuse as it encodes each ribbon color) (float) k / _UsedNbSegs, // u 0.f // v ); vba.setTexCoord(vbIndex + 1, _ColorScheme && _ColorFading ? 1 : 0, (float) k / _UsedNbSegs, 1.f ); } /// setup gradient if (_ColorFading) { // If not per ribbon color, we can encode it in the diffuse if (_ColorScheme == NULL) { uint8 intensity = (uint8) (255 * (1.f - ((float) k / _UsedNbSegs))); NLMISC::CRGBA col(intensity, intensity, intensity, intensity); vba.setColor(vbIndex, col); vba.setColor(vbIndex + 1, col); } else // encode it in the first texture { vba.setTexCoord(vbIndex, 0, 0.5f - 0.5f * ((float) k / _UsedNbSegs), 0); vba.setTexCoord(vbIndex + 1, 0, 0.5f - 0.5f * ((float) k / _UsedNbSegs), 0); } } vbIndex += 2; } } else { for (l = 0; l < numVerticesInSlice; ++l) /// deals with each ribbon vertices { nlassert(vbIndex < vb.getNumVertices()); /// setup texture (if any) if (_Tex != NULL) { vba.setTexCoord(vbIndex, _ColorScheme && _ColorFading ? 1 : 0, // must we use the second texture coord ? (when 1st one used by the gradient texture : we can't encode it in the diffuse as it encodes each ribbon color) (float) k / _UsedNbSegs, // u 1.f - (l / (float) numVerticesInShape) // v ); } /// setup gradient if (_ColorFading) { // If not per ribbon color, we can encode it in the diffuse if (_ColorScheme == NULL) { uint8 intensity = (uint8) (255 * (1.f - ((float) k / _UsedNbSegs))); NLMISC::CRGBA col(intensity, intensity, intensity, intensity); vba.setColor(vbIndex, col); } else // encode it in the first texture { vba.setTexCoord(vbIndex, 0, 0.5f - 0.5f * ((float) k / _UsedNbSegs), 0); } } ++ vbIndex; } } } } return VBnPB; } } //========================================================================== uint CPSRibbon::getNumRibbonsInVB() const { NL_PS_FUNC(CPSRibbon_getNumRibbonsInVB) const uint numVerticesInSlice = getNumVerticesInSlice(); /// 1 vertex added for textured ribbon (to avoid texture stretching) const uint vertexInVB = 512; return std::max(1u, (uint) (vertexInVB / (numVerticesInSlice * (_UsedNbSegs + 1)))); } //========================================================================== inline void CPSRibbon::updateUntexturedMaterial() { NL_PS_FUNC(CPSRibbon_updateUntexturedMaterial) /////////////////////// // UNTEXTURED RIBBON // /////////////////////// 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, no fading { if (ptGradTexture == NULL) // have we got a gradient texture ? { ptGradTexture = CreateGradientTexture(); } _Mat.setTexture(0, ptGradTexture); ptGradTexture->setWrapS(ITexture::Clamp); ptGradTexture->setWrapT(ITexture::Clamp); 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 // color attenuation, no fading : { CPSMaterial::forceTexturedMaterialStages(0); // no texture use constant diffuse only } } _Touch = false; } //========================================================================== inline void CPSRibbon::updateTexturedMaterial() { NL_PS_FUNC(CPSRibbon_updateTexturedMaterial) ///////////////////// // TEXTURED RIBBON // ///////////////////// if (_Tex) { //_Tex->setWrapS(ITexture::Clamp); //_Tex->setWrapT(ITexture::Clamp); } 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 { if (ptGradTexture == NULL) // have we got a gradient texture ? { ptGradTexture = CreateGradientTexture(); // create it } /// fading is stored in last stage (work only with 3 stages...) _Mat.setTexture(0, ptGradTexture); ptGradTexture->setWrapS(ITexture::Clamp); ptGradTexture->setWrapT(ITexture::Clamp); _Mat.setTexture(1, _Tex); CPSMaterial::forceTexturedMaterialStages(3); // use constant color 0 * diffuse, 1 stage needed SetupModulatedStage(_Mat, 0, CMaterial::Texture, CMaterial::Diffuse); SetupModulatedStage(_Mat, 1, CMaterial::Texture, CMaterial::Previous); SetupModulatedStage(_Mat, 2, CMaterial::Previous, CMaterial::Constant); } else // per ribbon color with global color { _Mat.setTexture(0, _Tex); CPSMaterial::forceTexturedMaterialStages(2); // use constant color 0 * diffuse, 1 stage needed SetupModulatedStage(_Mat, 0, CMaterial::Texture, CMaterial::Diffuse); SetupModulatedStage(_Mat, 1, CMaterial::Previous, CMaterial::Constant); } } else { if (_ColorFading) // per ribbon color, fading : 2 textures needed { if (ptGradTexture == NULL) // have we got a gradient texture ? { ptGradTexture = CreateGradientTexture(); // create it } _Mat.setTexture(0, ptGradTexture); ptGradTexture->setWrapS(ITexture::Clamp); ptGradTexture->setWrapT(ITexture::Clamp); _Mat.setTexture(1, _Tex); CPSMaterial::forceTexturedMaterialStages(2); SetupModulatedStage(_Mat, 0, CMaterial::Texture, CMaterial::Diffuse); // texture * ribbon color SetupModulatedStage(_Mat, 1, CMaterial::Texture, CMaterial::Previous); // * gradient } else // per color ribbon with no fading, and no global color { _Mat.setTexture(0, _Tex); CPSMaterial::forceTexturedMaterialStages(1); // no texture use constant diffuse only SetupModulatedStage(_Mat, 0, CMaterial::Texture, CMaterial::Diffuse); } } } else // GLOBAL COLOR { if (_ColorFading) // gradient is encoded in diffuse { _Mat.setTexture(0, _Tex); CPSMaterial::forceTexturedMaterialStages(2); // use constant color 0 * diffuse, 1 stage needed SetupModulatedStage(_Mat, 0, CMaterial::Texture, CMaterial::Diffuse); SetupModulatedStage(_Mat, 1, CMaterial::Previous, CMaterial::Constant); } else // constant color { _Mat.setTexture(0, _Tex); CPSMaterial::forceTexturedMaterialStages(1); // no texture use constant diffuse only SetupModulatedStage(_Mat, 0, CMaterial::Texture, CMaterial::Diffuse); } } _Touch = false; } //========================================================================== void CPSRibbon::updateMaterial() { NL_PS_FUNC(CPSRibbon_updateMaterial) if (!_Touch) return; if (_Tex != NULL) { updateTexturedMaterial(); setupTextureMatrix(); } else { updateUntexturedMaterial(); } } //========================================================================== inline void CPSRibbon::setupUntexturedGlobalColor() { NL_PS_FUNC(CPSRibbon_setupUntexturedGlobalColor) /// 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 { NLMISC::CRGBA col; if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting()) { col.modulateFromColor(ps.getGlobalColorLighted(), _Color); } else if (ps.getColorAttenuationScheme() || ps.isUserColorUsed()) { col.modulateFromColor(ps.getGlobalColor(), _Color); } else { col = _Color; } if (_ColorFading) { _Mat.texConstantColor(0, col); } else // color attenuation, no fading : { _Mat.setColor(col); } } } //========================================================================== inline void CPSRibbon::setupTexturedGlobalColor() { NL_PS_FUNC(CPSRibbon_setupTexturedGlobalColor) /// setup the global color if it is used CParticleSystem &ps = *(_Owner->getOwner()); if (_ColorScheme) { if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting()) { if (_ColorFading) { _Mat.texConstantColor(2, ps.getGlobalColorLighted()); } else { _Mat.texConstantColor(1, ps.getGlobalColorLighted()); } } else { if (_ColorFading) { _Mat.texConstantColor(2, ps.getGlobalColor()); } else { _Mat.texConstantColor(1, ps.getGlobalColor()); } } } else // GLOBAL COLOR with / without fading { if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting()) { NLMISC::CRGBA col; col.modulateFromColor(ps.getGlobalColorLighted(), _Color); if (_ColorFading) { _Mat.texConstantColor(1, 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(1, col); } else // color attenuation, no fading : { _Mat.setColor(col); } } else { if (_ColorFading) { _Mat.texConstantColor(1, _Color); } else // constant color { _Mat.setColor(_Color); } } } } //========================================================================== void CPSRibbon::setupGlobalColor() { NL_PS_FUNC(CPSRibbon_setupGlobalColor) if (_Tex != NULL) setupTexturedGlobalColor(); else setupUntexturedGlobalColor(); } //========================================================================== void CPSRibbon::setupTextureMatrix() { NL_PS_FUNC(CPSRibbon_setupTextureMatrix) uint stage = (_ColorScheme != NULL && _ColorFading == true) ? 1 : 0; if (_UFactor != 1.f || _VFactor != 1.f) { _Mat.enableUserTexMat(stage); CMatrix texMat; texMat.setRot(_UFactor * NLMISC::CVector::I, _VFactor * NLMISC::CVector::J, NLMISC::CVector::K ); _Mat.setUserTexMat(stage, texMat); } else { _Mat.enableUserTexMat(stage, false); } _Mat.enableUserTexMat(1 - stage, false); } //========================================================================== ///================================================================================================================== void CPSRibbon::setShape(const CVector *shape, uint32 nbPointsInShape, bool braceMode) { NL_PS_FUNC(CPSRibbon_setShape) if (!braceMode) { nlassert(nbPointsInShape >= 3); } else { nlassert(nbPointsInShape >= 2); nlassert(!(nbPointsInShape & 1)); // must be even } _Shape.resize(nbPointsInShape); std::copy(shape, shape + nbPointsInShape, _Shape.begin()); _BraceMode = braceMode; } ///================================================================================================================== void CPSRibbon::getShape(CVector *shape) const { NL_PS_FUNC(CPSRibbon_getShape) std::copy(_Shape.begin(), _Shape.end(), shape); } ///================================================================================================================== void CPSRibbon::enumTexs(std::vector > &dest, IDriver &drv) { NL_PS_FUNC(CPSRibbon_enumTexs) if (_Tex) { dest.push_back(_Tex); } } } // NL3D