// 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);
pb.setPreferredMemory(CIndexBuffer::AGPVolatile, false);
// 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