// 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/texture_grouped.h"
#include "nel/3d/texture_file.h"
#include "nel/misc/common.h"
#include "nel/misc/path.h"
#include
namespace NL3D {
std::map CTextureGrouped::_NameToSize;
///=====================================================================================================
/// This is used to get the size of a texture, with an optimisation in the case of texture files
static inline void GetTextureSize(ITexture *tex, uint &width, uint &height)
{
if (tex->getClassName() == "CTextureFile")
{
CTextureFile *tf = static_cast(tex);
uint32 srcWidth, srcHeight;
if (!tf->getFileName().empty())
{
try
{
CBitmap::loadSize(NLMISC::CPath::lookup(tf->getFileName()), srcWidth, srcHeight);
if (srcWidth == 0 || srcHeight == 0)
{
nlinfo("Unable to get size of texture : %s", tf->getFileName().c_str());
width = height = 0;
return;
}
width = srcWidth;
height = srcHeight;
}
catch (const NLMISC::EPathNotFound &e)
{
nlinfo("%s", e.what());
width = height = 0;
}
catch (const NLMISC::EStream &e)
{
nlinfo("unable to load size from a bitmap ! name = %s", tf->getFileName().c_str());
nlinfo("reason = %s", e.what());
width = height = 0;
}
}
else
{
width = height = 0;
}
}
else // we must generate the texture to get its size
{
tex->generate();
width = tex->getWidth();
height = tex->getHeight();
if (tex->getReleasable())
{
tex->release();
}
}
}
///=====================================================================================================
CTextureGrouped::CTextureGrouped() : _NbTex(0)
{
setFilterMode(Linear, LinearMipMapOff);
}
///=====================================================================================================
CTextureGrouped::CTextureGrouped(const CTextureGrouped &src) : ITexture(src)
{
// copy the part associated with us;
duplicate(src);
}
///=====================================================================================================
void CTextureGrouped::duplicate(const CTextureGrouped &src)
{
_NbTex = src._NbTex;
TTexList texCopy(src._Textures.begin(), src._Textures.end());
_Textures.swap(texCopy);
TFourUVList uvCopy(_TexUVs.begin(), _TexUVs.end());
_TexUVs.swap(uvCopy);
}
///=====================================================================================================
CTextureGrouped &CTextureGrouped::operator=(const CTextureGrouped &src)
{
ITexture::operator=(src); // copy parent part
duplicate(src);
return *this;
}
///=====================================================================================================
bool CTextureGrouped::areValid(CSmartPtr *textureTab, uint nbTex)
{
bool result = true;
uint k;
for(k = 0; k < nbTex; ++k)
{
textureTab[k]->generate();
if (textureTab[k]->getWidth() != textureTab[0]->getWidth()
|| textureTab[k]->getHeight() != textureTab[0]->getHeight()
|| textureTab[k]->getPixelFormat() != textureTab[0]->getPixelFormat()
)
{
result = false;
break;
}
}
for (k = 0; k < nbTex; ++k)
{
if (textureTab[k]->getReleasable()) textureTab[k]->release();
}
return result;
}
///=====================================================================================================
void CTextureGrouped::setTextures(CSmartPtr *textureTab, uint nbTex, bool checkValid)
{
nlassert(nbTex > 0);
if (checkValid)
{
if (!areValid(textureTab, nbTex))
{
displayIncompatibleTextureWarning(textureTab, nbTex);
makeDummies(textureTab, nbTex);
}
}
_Textures.resize(nbTex);
std::copy(textureTab, textureTab + nbTex, _Textures.begin());
_TexUVs.resize(nbTex);
for(uint k = 0; k < nbTex; ++k)
{
// real uvs are generated during doGenerate
_TexUVs[k].uv0.set(0.f, 0.f);
_TexUVs[k].uv1.set(0.f, 0.f);
_TexUVs[k].uv2.set(0.f, 0.f);
_TexUVs[k].uv3.set(0.f, 0.f);
}
_DeltaUV.set(0.f, 0.f);
_NbTex = nbTex;
touch(); // the texture need regeneration
nlassert(_NbTex == _Textures.size());
}
///=====================================================================================================
void CTextureGrouped::doGenerate(bool async)
{
nlassert(_NbTex == _Textures.size());
if (_NbTex == 0)
{
makeDummy();
}
else
{
// Generate the first texture to get the size
_Textures[0]->generate();
const uint width = _Textures[0]->getWidth(), height = _Textures[0]->getHeight();
const uint totalHeight = height * _NbTex;
const uint realHeight = NLMISC::raiseToNextPowerOf2(totalHeight);
resize(width, realHeight, _Textures[0]->getPixelFormat());
uint k;
sint32 currY = 0;
for(k = 0; k < _NbTex; ++k)
{
_Textures[k]->generate();
if (_Textures[k]->getWidth() != width
|| _Textures[k]->getHeight() != height
|| _Textures[k]->getPixelFormat() != _Textures[0]->getPixelFormat())
{
makeDummy();
break;
}
this->blit(_Textures[k], 0, currY);
currY += height;
}
for(k = 0; k < _NbTex; ++k)
{
if ( _Textures[k]->getReleasable())
{
_Textures[k]->release();
}
}
// save sub bitmap size so that bitmaps won't be reloaded to get their size the next time
_NameToSize[getShareName()] = height;
// compute the uvs
genUVs(height);
}
}
///=====================================================================================================
void CTextureGrouped::forceGenUVs()
{
// retrieve size of sub-bitmap
std::string shareName = getShareName();
std::map::const_iterator it = _NameToSize.find(shareName);
if (it == _NameToSize.end())
{
doGenerate(); // this will retrieve size of sub-bitmaps
// TODO : only load the size... not a problem for now because textures can be cached at startup
release();
it = _NameToSize.find(getShareName());
if (it == _NameToSize.end())
{
_TexUVs.resize(_NbTex);
// generate default uvs
for(uint k = 0; k < _NbTex; ++k)
{
TFourUV &uvs = _TexUVs[k];
uvs.uv0.set(0.f, 0.f);
uvs.uv1.set(1.f, 0.f);
uvs.uv2.set(1.f, 1.f);
uvs.uv3.set(0.f, 1.f);
}
return;
}
}
genUVs(it->second);
}
///=====================================================================================================
void CTextureGrouped::genUVs(uint subBitmapHeight)
{
_TexUVs.resize(_NbTex);
//
const uint totalHeight = subBitmapHeight * _NbTex; // *it contains the height of bitmap
const uint realHeight = NLMISC::raiseToNextPowerOf2(totalHeight);
// TODO : better ordering of sub-bitmaps
const float deltaV = realHeight ? (float(totalHeight) / float(realHeight)) * (1.0f / _NbTex)
: 0.f;
_DeltaUV = CUV(1, deltaV);
CUV currentUV(0, 0);
for(uint k = 0; k < _NbTex; ++k)
{
TFourUV &uvs = _TexUVs[k];
uvs.uv0 = currentUV;
uvs.uv1 = currentUV + CUV(1, 0);
uvs.uv2 = currentUV + _DeltaUV;
uvs.uv3 = currentUV + CUV(0, deltaV);
currentUV.V += deltaV;
}
}
///=====================================================================================================
void CTextureGrouped::getTextures(CSmartPtr *textureTab) const
{
CSmartPtr *dest = textureTab;
for(TTexList::const_iterator it = _Textures.begin(); it != _Textures.end(); ++it, ++dest)
{
*dest = *it;
}
}
///=====================================================================================================
bool CTextureGrouped::supportSharing() const
{
// all textures in this group must support sharing for this one to support it
for(TTexList::const_iterator it = _Textures.begin(); it != _Textures.end(); ++it)
{
if (!(*it)->supportSharing()) return false;
}
return true;
}
///=====================================================================================================
std::string CTextureGrouped::getShareName() const
{
nlassert(supportSharing());
std::string shareName("groupedTex:");
for(TTexList::const_iterator it = _Textures.begin(); it != _Textures.end(); ++it)
{
shareName += (*it)->getShareName() + std::string("|");
}
return shareName;
}
///=====================================================================================================
void CTextureGrouped::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
f.serialVersion(1);
if (f.isReading())
{
TTexList texList;
/// read the number of textures
uint32 nbTex;
f.serial(nbTex);
/// cerate a vector of textures
ITexture *ptTex = NULL;
texList.reserve(nbTex);
for (uint k = 0; k < nbTex; ++k)
{
f.serialPolyPtr(ptTex);
texList.push_back(ptTex);
}
// setup the textures
setTextures(&texList[0], nbTex, false);
forceGenUVs();
touch();
}
else
{
f.serial(_NbTex);
ITexture *ptTex;
for (TTexList::iterator it = _Textures.begin(); it != _Textures.end(); ++it)
{
ptTex = *it;
f.serialPolyPtr(ptTex);
}
}
}
///=====================================================================================================
void CTextureGrouped::release()
{
ITexture::release();
for(uint k = 0; k < _NbTex; ++k)
{
if ( _Textures[k]->getReleasable())
{
_Textures[k]->release();
}
}
}
///=====================================================================================================
void CTextureGrouped::makeDummies(CSmartPtr *textureTab,uint nbTex)
{
for(uint k = 0; k < nbTex; ++k)
{
textureTab[k] = new CTextureFile("DummyTex"); // well this shouldn't exist..
}
}
///=====================================================================================================
void CTextureGrouped::displayIncompatibleTextureWarning(CSmartPtr *textureTab,uint nbTex)
{
nlwarning("=======================================================");
nlwarning("CTextureGrouped : found incompatible textures, that differs by size or format :" );
for(uint k = 0; k < nbTex; ++k)
{
if (textureTab[k])
{
nlwarning((textureTab[k]->getShareName()).c_str());
}
else
{
nlwarning("NULL Texture found");
}
}
nlwarning("=======================================================");
}
} // NL3D