// 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_quad.h"
#include "nel/3d/ps_macro.h"
#include "nel/3d/driver.h"
#include "nel/3d/ps_attrib_maker.h"
#include "nel/3d/texture_grouped.h"
#include "nel/3d/particle_system.h"
#include "nel/3d/ps_iterator.h"
namespace NL3D
{
///////////////////////////
// constant definition //
///////////////////////////
static const uint dotBufSize = 1024; // size used for point particles batching
////////////////////
// vertex buffers //
////////////////////
/// the various kind of vertex buffers we need for quads
CVertexBuffer CPSQuad::_VBPos;
CVertexBuffer CPSQuad::_VBPosCol;
CVertexBuffer CPSQuad::_VBPosTex1;
CVertexBuffer CPSQuad::_VBPosTex1Col;
CVertexBuffer CPSQuad::_VBPosTex1Anim;
CVertexBuffer CPSQuad::_VBPosTex1AnimCol;
//==========
CVertexBuffer CPSQuad::_VBPosTex1Tex2;
CVertexBuffer CPSQuad::_VBPosTex1ColTex2;
CVertexBuffer CPSQuad::_VBPosTex1AnimTex2;
CVertexBuffer CPSQuad::_VBPosTex1AnimColTex2;
//==========
CVertexBuffer CPSQuad::_VBPosTex1Tex2Anim;
CVertexBuffer CPSQuad::_VBPosTex1ColTex2Anim;
CVertexBuffer CPSQuad::_VBPosTex1AnimTex2Anim;
CVertexBuffer CPSQuad::_VBPosTex1AnimColTex2Anim;
/// tmp : ram
CVertexBuffer RAMVBPos;
CVertexBuffer RAMVBPosCol;
CVertexBuffer RAMVBPosTex1;
CVertexBuffer RAMVBPosTex1Col;
CVertexBuffer RAMVBPosTex1Anim;
CVertexBuffer RAMVBPosTex1AnimCol;
//==========
CVertexBuffer RAMVBPosTex1Tex2;
CVertexBuffer RAMVBPosTex1ColTex2;
CVertexBuffer RAMVBPosTex1AnimTex2;
CVertexBuffer RAMVBPosTex1AnimColTex2;
//==========
CVertexBuffer RAMVBPosTex1Tex2Anim;
CVertexBuffer RAMVBPosTex1ColTex2Anim;
CVertexBuffer RAMVBPosTex1AnimTex2Anim;
CVertexBuffer RAMVBPosTex1AnimColTex2Anim;
CVertexBuffer * const CPSQuad::_VbTab[] =
{
// tex1 only
&_VBPos, &_VBPosCol, &_VBPosTex1, &_VBPosTex1Col,
NULL, NULL, &_VBPosTex1Anim, &_VBPosTex1AnimCol,
// tex1 & tex2
NULL, NULL, &_VBPosTex1Tex2, &_VBPosTex1ColTex2,
NULL, NULL, &_VBPosTex1AnimTex2, &_VBPosTex1AnimColTex2,
// tex2 & !tex1 (invalid)
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
// tex2 & !tex1 (invalid)
// tex1 & tex2
NULL, NULL, &_VBPosTex1Tex2Anim, &_VBPosTex1ColTex2Anim,
NULL, NULL, &_VBPosTex1AnimTex2Anim, &_VBPosTex1AnimColTex2Anim,
};
CVertexBuffer * const RAMVbTab[] =
{
// tex1 only
&RAMVBPos, &RAMVBPosCol, &RAMVBPosTex1, &RAMVBPosTex1Col,
NULL, NULL, &RAMVBPosTex1Anim, &RAMVBPosTex1AnimCol,
// tex1 & tex2
NULL, NULL, &RAMVBPosTex1Tex2, &RAMVBPosTex1ColTex2,
NULL, NULL, &RAMVBPosTex1AnimTex2, &RAMVBPosTex1AnimColTex2,
// tex2 & !tex1 (invalid)
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
// tex2 & !tex1 (invalid)
// tex1 & tex2
NULL, NULL, &RAMVBPosTex1Tex2Anim, &RAMVBPosTex1ColTex2Anim,
NULL, NULL, &RAMVBPosTex1AnimTex2Anim, &RAMVBPosTex1AnimColTex2Anim,
};
//////////////////////////////////
// CPSQuad implementation //
//////////////////////////////////
///==================================================================================
/// fill textures coordinates for a quad vb
static void SetupQuadVBTexCoords(CVertexBuffer &vb, uint texCoordSet)
{
NL_PS_FUNC(SetupQuadVBTexCoords)
nlassert(texCoordSet < 2);
CVertexBufferReadWrite vba;
vb.lock (vba);
// the size used for buffer can't be higher than quad buf size
// to have too large buffer will broke the cache
for (uint32 k = 0; k < CPSQuad::quadBufSize; ++k)
{
vba.setTexCoord(k * 4, texCoordSet, CUV(0, 0));
vba.setTexCoord(k * 4 + 1, texCoordSet, CUV(1, 0));
vba.setTexCoord(k * 4 + 2, texCoordSet, CUV(1, 1));
vba.setTexCoord(k * 4 + 3, texCoordSet, CUV(0, 1));
}
}
///==================================================================================
/// this static method init vertex buffers
void CPSQuad::initVertexBuffers()
{
NL_PS_FUNC(CPSQuad_initVertexBuffers)
for (uint k = 0; k < 32; ++k)
{
CVertexBuffer *vb = _VbTab[k];
if (vb) // valid vb ?
{
vb->setName("CPSQuad");
vb->setPreferredMemory(CVertexBuffer::AGPVolatile, true);
uint32 vf = CVertexBuffer::PositionFlag;
/// setup vertex format
if (k & (uint) VBCol) vf |= CVertexBuffer::PrimaryColorFlag;
if (k & (uint) VBTex || k & (uint) VBTexAnimated) vf |= CVertexBuffer::TexCoord0Flag;
if (k & (uint) VBTex2 || k & (uint) VBTex2Animated) vf |= CVertexBuffer::TexCoord1Flag;
vb->setVertexFormat(vf);
vb->setNumVertices(quadBufSize << 2);
if ((k & (uint) VBTex) && !(k & (uint) VBTexAnimated))
{
SetupQuadVBTexCoords(*vb, 0);
}
if ((k & (uint) VBTex2) && !(k & (uint) VBTex2Animated))
{
SetupQuadVBTexCoords(*vb, 1);
}
}
}
/*
for (uint k = 0; k < 32; ++k)
{
CVertexBuffer *vb = RAMVbTab[k];
if (vb) // valid vb ?
{
vb->setName("CPSQuadRAM");
vb->setPreferredMemory(CVertexBuffer::RAMPreferred, false);
uint32 vf = CVertexBuffer::PositionFlag;
/// setup vertex format
if (k & (uint) VBCol) vf |= CVertexBuffer::PrimaryColorFlag;
if (k & (uint) VBTex || k & (uint) VBTexAnimated) vf |= CVertexBuffer::TexCoord0Flag;
if (k & (uint) VBTex2 || k & (uint) VBTex2Animated) vf |= CVertexBuffer::TexCoord1Flag;
vb->setVertexFormat(vf);
vb->setNumVertices(quadBufSize << 2);
if ((k & (uint) VBTex) && !(k & (uint) VBTexAnimated))
{
SetupQuadVBTexCoords(*vb, 0);
}
if ((k & (uint) VBTex2) && !(k & (uint) VBTex2Animated))
{
SetupQuadVBTexCoords(*vb, 1);
}
}
}
*/
}
// tmp
//volatile bool TestWantAGPCPSQuad = true;
///==================================================================================
/// choose the vertex buffex that we need
CVertexBuffer &CPSQuad::getNeededVB(IDriver &drv)
{
NL_PS_FUNC(CPSQuad_getNeededVB)
uint flags = 0;
if (_ColorScheme) flags |= (uint) VBCol;
if (_TexGroup)
{
flags |= VBTex | VBTexAnimated;
}
else if (_Tex)
{
flags |= VBTex;
}
if (flags & VBTex)
{
/// check if multitexturing is enabled, and which texture are enabled and / or animated
if (CPSMultiTexturedParticle::isMultiTextureEnabled())
{
if (isAlternateTextureUsed(drv) && _AlternateTexture2)
{
if ((flags & VBTex) && (_TexScrollAlternate[0].x != 0 || _TexScrollAlternate[0].y != 0)) flags |= VBTexAnimated;
if (_AlternateOp != Decal && (_TexScrollAlternate[1].x != 0 || _TexScrollAlternate[1].y != 0))
{
flags |= VBTex2 | VBTex2Animated;
}
else
{
flags |= VBTex2;
}
}
else
{
if ((flags & VBTex) && (_TexScroll[0].x != 0 || _TexScroll[0].y != 0)) flags |= VBTexAnimated;
if (_Texture2)
{
if (_MainOp != Decal && (_TexScroll[1].x != 0 || _TexScroll[1].y != 0))
{
flags |= VBTex2 | VBTex2Animated;
}
else
{
flags |= VBTex2;
}
}
}
}
}
/*if (TestWantAGPCPSQuad)
{*/
nlassert((flags & ~VBFullMask) == 0); // check for overflow
nlassert(_VbTab[flags] != NULL);
return *(_VbTab[flags]); // get the vb
/*}
else
{
nlassert((flags & ~VBFullMask) == 0); // check for overflow
nlassert(RAMVbTab[flags] != NULL);
return *(RAMVbTab[flags]); // get the vb
}*/
}
///==================================================================================
CPSQuad::CPSQuad(CSmartPtr tex)
{
NL_PS_FUNC(CPSQuad_CPSQuad)
setTexture(tex);
init();
// we don't init the _IndexBuffer for now, as it will be when resize is called
if (CParticleSystem::getSerializeIdentifierFlag()) _Name = std::string("quad");
}
///==================================================================================
CPSQuad::~CPSQuad()
{
NL_PS_FUNC(CPSQuad_CPSQuadDtor)
}
///==================================================================================
uint32 CPSQuad::getNumWantedTris() const
{
NL_PS_FUNC(CPSQuad_getNumWantedTris)
nlassert(_Owner);
//return _Owner->getMaxSize() << 1;
return _Owner->getSize() << 1;
}
///==================================================================================
bool CPSQuad::hasTransparentFaces(void)
{
NL_PS_FUNC(CPSQuad_hasTransparentFaces)
return getBlendingMode() != CPSMaterial::alphaTest ;
}
///==================================================================================
bool CPSQuad::hasOpaqueFaces(void)
{
NL_PS_FUNC(CPSQuad_hasOpaqueFaces)
return !hasTransparentFaces();
}
///==================================================================================
void CPSQuad::init(void)
{
NL_PS_FUNC(CPSQuad_init)
_Mat.setLighting(false);
_Mat.setDoubleSided(true);
updateMatAndVbForColor();
updateMatAndVbForTexture();
}
///==================================================================================
void CPSQuad::updateMatAndVbForTexture(void)
{
NL_PS_FUNC(CPSQuad_updateMatAndVbForTexture)
if (CPSMultiTexturedParticle::isMultiTextureEnabled())
{
touch();
}
else
{
_Mat.setTexture(0, _TexGroup ? (ITexture *) _TexGroup : (ITexture *) _Tex);
}
}
///==================================================================================
bool CPSQuad::completeBBox(NLMISC::CAABBox &box) const
{
NL_PS_FUNC(CPSQuad_completeBBox)
if (!_SizeScheme)
{
CPSUtil::addRadiusToAABBox(box, _ParticleSize);
}
else
{
CPSUtil::addRadiusToAABBox(box, std::max(fabsf(_SizeScheme->getMaxValue()), fabsf(_SizeScheme->getMinValue())));
}
return true ;
}
///==================================================================================
void CPSQuad::resize(uint32 size)
{
NL_PS_FUNC(CPSQuad_resize)
nlassert(size < (1 << 16));
resizeSize(size);
resizeColor(size);
resizeTextureIndex(size);
}
//==============================================================
void CPSQuad::updateMatAndVbForColor(void)
{
NL_PS_FUNC(CPSQuad_updateMatAndVbForColor)
// no vb to setup, now..
/* if (!_ColorScheme)
{
_Mat.setColor(_Color);
}
else
{
_Mat.setColor(CRGBA::White);
} */
}
//==============================================================
void CPSQuad::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
NL_PS_FUNC(CPSQuad_serial)
sint ver = f.serialVersion(2);
CPSParticle::serial(f);
CPSSizedParticle::serialSizeScheme(f);
CPSColoredParticle::serialColorScheme(f);
CPSTexturedParticle::serialTextureScheme(f);
serialMaterial(f);
if (ver > 1)
{
CPSMultiTexturedParticle::serialMultiTex(f);
}
}
//==============================================================
// static func used to fill texture coordinates of quads, with global time
static void FillQuadCoords(uint8 *dest, uint stride, const NLMISC::CVector2f &speed, float time, uint num)
{
NL_PS_FUNC(FillQuadCoords)
if (!num) return;
const float topV = speed.y * time;
const float bottomV = topV + 1.f;
const float leftU = speed.x * time;
const float rightU = leftU + 1.f;
do
{
((NLMISC::CUV *) dest)->set(leftU, topV);
((NLMISC::CUV *) (dest + stride))->set(rightU, topV);
((NLMISC::CUV *) (dest + (stride << 1)))->set(rightU, bottomV);
((NLMISC::CUV *) (dest + (stride * 3)))->set(leftU, bottomV);
dest += stride << 2;
}
while (--num);
}
//==============================================================
// static func used to fill texture coordinates of quads, with local time
static void FillQuadCoordsLocalTime(uint8 *dest, uint stride, const NLMISC::CVector2f &speed, CPSLocated &srcLoc, uint startIndex, uint num, uint32 srcStep)
{
NL_PS_FUNC(FillQuadCoordsLocalTime)
if (!num) return;
if (srcStep == (1 << 16)) // step = 1.0 ?
{
TPSAttribTime::iterator timePt = srcLoc.getTime().begin() + startIndex;
do
{
const float topV = speed.y * *timePt;
const float bottomV = topV + 1.f;
const float leftU = speed.x * *timePt;
const float rightU = leftU + 1.f;
((NLMISC::CUV *) dest)->set(leftU, topV);
((NLMISC::CUV *) (dest + stride))->set(rightU, topV);
((NLMISC::CUV *) (dest + (stride << 1)))->set(rightU, bottomV);
((NLMISC::CUV *) (dest + (stride * 3)))->set(leftU, bottomV);
dest += stride << 2;
++timePt;
}
while (--num);
}
else
{
TIteratorTimeStep1616 timePt(srcLoc.getTime().begin(), startIndex, srcStep);
do
{
const float topV = speed.y * *timePt;
const float bottomV = topV + 1.f;
const float leftU = speed.x * *timePt;
const float rightU = leftU + 1.f;
((NLMISC::CUV *) dest)->set(leftU, topV);
((NLMISC::CUV *) (dest + stride))->set(rightU, topV);
((NLMISC::CUV *) (dest + (stride << 1)))->set(rightU, bottomV);
((NLMISC::CUV *) (dest + (stride * 3)))->set(leftU, bottomV);
dest += stride << 2;
++timePt;
}
while (--num);
}
}
//==============================================================
void CPSQuad::updateVbColNUVForRender(CVertexBuffer &vb, uint32 startIndex, uint32 size, uint32 srcStep, IDriver &drv)
{
NL_PS_FUNC(CPSQuad_updateVbColNUVForRender)
nlassert(_Owner);
if (!size) return;
CVertexBufferReadWrite vba;
vb.lock (vba);
if (_ColorScheme)
{
// compute the colors, each color is replicated 4 times
// todo hulud d3d vertex color RGBA / BGRA
_ColorScheme->make4(_Owner, startIndex, vba.getColorPointer(), vb.getVertexSize(), size, srcStep);
}
if (_TexGroup) // if it has a constant texture we are sure it has been setupped before...
{
sint32 textureIndex[quadBufSize];
const uint32 stride = vb.getVertexSize(), stride2 = stride << 1, stride3 = stride2 + stride, stride4 = stride2 << 1;
uint8 *currUV = (uint8 *) vba.getTexCoordPointer();
const sint32 *currIndex;
uint32 currIndexIncr;
if (_TextureIndexScheme)
{
currIndex = (sint32 *) _TextureIndexScheme->make(_Owner, startIndex, textureIndex, sizeof(sint32), size, true, srcStep);
currIndexIncr = 1;
}
else
{
currIndex = &_TextureIndex;
currIndexIncr = 0;
}
uint32 left = size;
while (left--)
{
// for now, we don't make texture index wrapping
const CTextureGrouped::TFourUV &uvGroup = _TexGroup->getUVQuad((uint32) *currIndex);
// copy the 4 uv's for this face
*(CUV *) currUV = uvGroup.uv0;
*(CUV *) (currUV + stride) = uvGroup.uv1;
*(CUV *) (currUV + stride2) = uvGroup.uv2;
*(CUV *) (currUV + stride3) = uvGroup.uv3;
// point the next face
currUV += stride4;
currIndex += currIndexIncr;
}
}
nlassert(_Owner && _Owner->getOwner());
const float date= (float) _Owner->getOwner()->getSystemDate();
/// todo: vertex program optimisation (& choose the vb accordingly)
if (CPSMultiTexturedParticle::isMultiTextureEnabled() && (_Tex || _TexGroup))
{
// perform tex1 animation if needed
if (!_TexGroup) // doesn't work with texGroup enabled
{
if (!isAlternateTextureUsed(drv) || !isAlternateTexEnabled() || !_AlternateTexture2)
{
if (_Tex && (_TexScroll[0].x != 0 || _TexScroll[0].y != 0))
{
// animation of texture 1 with main speed
if (!getUseLocalDate())
{
FillQuadCoords((uint8 *) vba.getTexCoordPointer(0, 0), vb.getVertexSize(), _TexScroll[0], date, size);
}
else
{
FillQuadCoordsLocalTime((uint8 *) vba.getTexCoordPointer(0, 0), vb.getVertexSize(), _TexScroll[0], *_Owner, startIndex, size, srcStep);
}
}
}
else
{
if (_Tex && (_TexScrollAlternate[0].x != 0 || _TexScrollAlternate[0].y != 0))
{
// animation of texture 1 with alternate speed
if (!getUseLocalDateAlt())
{
FillQuadCoords((uint8 *) vba.getTexCoordPointer(0, 0), vb.getVertexSize(), _TexScrollAlternate[0], date, size);
}
else
{
FillQuadCoordsLocalTime((uint8 *) vba.getTexCoordPointer(0, 0), vb.getVertexSize(), _TexScrollAlternate[0], *_Owner, startIndex, size, srcStep);
}
}
}
}
// perform tex2 animation if needed
if (!isAlternateTextureUsed(drv))
{
if (_Texture2 && (_TexScroll[1].x != 0 || _TexScroll[1].y != 0))
{
// animation of texture 2 with main speed
if (!getUseLocalDate())
{
FillQuadCoords((uint8 *) vba.getTexCoordPointer(0, 1), vb.getVertexSize(), _TexScroll[1], date, size);
}
else
{
FillQuadCoordsLocalTime((uint8 *) vba.getTexCoordPointer(0, 1), vb.getVertexSize(), _TexScroll[1], *_Owner, startIndex, size, srcStep);
}
}
}
else
{
if (_AlternateTexture2 && isAlternateTexEnabled() && (_TexScrollAlternate[1].x != 0 || _TexScrollAlternate[1].y != 0))
{
// animation of texture 2 with alternate speed
if (!getUseLocalDateAlt())
{
FillQuadCoords((uint8 *) vba.getTexCoordPointer(0, 1), vb.getVertexSize(), _TexScrollAlternate[1], date, size);
}
else
{
FillQuadCoordsLocalTime((uint8 *) vba.getTexCoordPointer(0, 1), vb.getVertexSize(), _TexScrollAlternate[1], *_Owner, startIndex, size, srcStep);
}
}
}
}
}
///==================================================================================
void CPSQuad::updateMatBeforeRendering(IDriver *drv, CVertexBuffer &vb)
{
NL_PS_FUNC(CPSQuad_updateMatBeforeRendering)
nlassert(_Owner && _Owner->getOwner());
CParticleSystem &ps = *(_Owner->getOwner());
vb.setUVRouting(0, 0);
vb.setUVRouting(1, 1); // default uv routing
if (isMultiTextureEnabled())
{
setupMaterial(_Tex, drv, _Mat, vb);
if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting())
{
_Mat.setColor(ps.getGlobalColorLighted());
}
else
{
_Mat.setColor(ps.getGlobalColor());
}
}
else
{
/// update the material if the global color of the system is variable
if (_ColorScheme != NULL &&
(ps.getColorAttenuationScheme() != NULL ||
ps.isUserColorUsed() ||
ps.getForceGlobalColorLightingFlag() ||
usesGlobalColorLighting()
)
)
{
if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting())
{
CPSMaterial::forceModulateConstantColor(true, ps.getGlobalColorLighted());
}
else
{
CPSMaterial::forceModulateConstantColor(true, ps.getGlobalColor());
}
}
else
{
forceModulateConstantColor(false);
NLMISC::CRGBA col;
if (ps.getForceGlobalColorLightingFlag() || usesGlobalColorLighting())
{
col.modulateFromColor(ps.getGlobalColorLighted(), _Color);
}
else if (ps.getColorAttenuationScheme() != NULL || ps.isUserColorUsed())
{
col.modulateFromColor(ps.getGlobalColor(), _Color);
}
else
{
col = _Color;
}
_Mat.setColor(col);
}
}
}
//*****************************************************************************************************
void CPSQuad::enumTexs(std::vector > &dest, IDriver &drv)
{
NL_PS_FUNC(CPSQuad_enumTexs)
CPSTexturedParticle::enumTexs(dest);
CPSMultiTexturedParticle::enumTexs(dest, drv);
}
//*****************************************************************************************************
void CPSQuad::setZBias(float value)
{
NL_PS_FUNC(CPSQuad_setZBias)
CPSMaterial::setZBias(value);
}
//*****************************************************************************************************
void CPSQuad::setTexture(CSmartPtr tex)
{
NL_PS_FUNC(CPSQuad_setTexture)
CPSTexturedParticle::setTexture(tex);
CPSMultiTexturedParticle::touch();
}
//*****************************************************************************************************
void CPSQuad::setTextureGroup(NLMISC::CSmartPtr texGroup)
{
NL_PS_FUNC(CPSQuad_setTextureGroup)
CPSTexturedParticle::setTextureGroup(texGroup);
CPSMultiTexturedParticle::touch();
}
//*****************************************************************************************************
void CPSQuad::setTexture2(ITexture *tex)
{
NL_PS_FUNC(CPSQuad_setTexture2)
CPSMultiTexturedParticle::setTexture2(tex);
}
//*****************************************************************************************************
void CPSQuad::setTexture2Alternate(ITexture *tex)
{
NL_PS_FUNC(CPSQuad_setTexture2Alternate)
CPSMultiTexturedParticle::setTexture2Alternate(tex);
}
//*****************************************************************************************************
void CPSQuad::updateTexWrapMode(IDriver &drv)
{
NL_PS_FUNC(CPSQuad_updateTexWrapMode)
if (isMultiTextureEnabled())
{
if (_Tex)
{
if (isAlternateTextureUsed(drv))
{
_Tex->setWrapS(_TexScroll[0].x == 0 ? ITexture::Clamp : ITexture::Repeat);
_Tex->setWrapT(_TexScroll[0].y == 0 ? ITexture::Clamp : ITexture::Repeat);
}
else
{
_Tex->setWrapS(_TexScrollAlternate[0].x == 0 ? ITexture::Clamp : ITexture::Repeat);
_Tex->setWrapT(_TexScrollAlternate[0].y == 0 ? ITexture::Clamp : ITexture::Repeat);
}
}
ITexture *tex2 = isAlternateTextureUsed(drv) ? getTexture2Alternate() : getTexture2();
if (tex2)
{
tex2->setWrapS(_TexScroll[1].x == 0 ? ITexture::Clamp : ITexture::Repeat);
tex2->setWrapT(_TexScroll[1].y == 0 ? ITexture::Clamp : ITexture::Repeat);
}
}
else
{
if (_Tex)
{
_Tex->setWrapS(ITexture::Clamp);
_Tex->setWrapT(ITexture::Clamp);
}
// nb for grouped texture we assume that no mipmapping is used, and that the border is black (or white for modulate material mode)
}
}
} // NL3D