338 lines
9.2 KiB
C++
338 lines
9.2 KiB
C++
|
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
||
|
// 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 <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
#include "std3d.h"
|
||
|
|
||
|
#include "nel/3d/texture_bump.h"
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
namespace NL3D {
|
||
|
|
||
|
|
||
|
CTextureBump::TNameToNI CTextureBump::_NameToNF;
|
||
|
|
||
|
|
||
|
#define GET_HGT(x, y) ((sint) ((src[(uint) (x) % width + ((uint) (y) % height) * width] & 0x00ff00) >> 8))
|
||
|
/// create a DsDt texture from a height map (red component of a rgba bitmap)
|
||
|
static void BuildDsDt(uint32 *src, sint width, sint height, uint16 *dest)
|
||
|
{
|
||
|
#define GET_HGT(x, y) ((sint) ((src[(uint) (x) % width + ((uint) (y) % height) * width] & 0x00ff00) >> 8))
|
||
|
sint x, y;
|
||
|
for (x = 0; x < width; ++x)
|
||
|
{
|
||
|
for (y = 0; y < height; ++y)
|
||
|
{
|
||
|
sint off = x + y * width;
|
||
|
sint16 ds = (sint16) (GET_HGT(x + 1, y) - GET_HGT(x - 1, y));
|
||
|
sint16 dt = (sint16) (GET_HGT(x, y + 1) - GET_HGT(x, y - 1));
|
||
|
dest[off] = (uint16) ((ds & 0xff) | ((dt & 0xff) << 8));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// create a rgba gradient texture from a height map (red component of a rgba bitmap)
|
||
|
static void BuildDsDtAsRGBA(uint32 *src, sint width, sint height, uint32 *dest)
|
||
|
{
|
||
|
#define GET_HGT(x, y) ((sint) ((src[(uint) (x) % width + ((uint) (y) % height) * width] & 0x00ff00) >> 8))
|
||
|
sint x, y;
|
||
|
for (x = 0; x < width; ++x)
|
||
|
{
|
||
|
for (y = 0; y < height; ++y)
|
||
|
{
|
||
|
sint off = x + y * width;
|
||
|
sint16 ds = (sint16) (GET_HGT(x + 1, y) - GET_HGT(x - 1, y));
|
||
|
sint16 dt = (sint16) (GET_HGT(x, y + 1) - GET_HGT(x, y - 1));
|
||
|
dest[off] = 0xff000000 | (uint32) ((ds + 0x80) & 0xff) | (uint32) ((dt + 0x80) << 8);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Normalize a DsDt texture after it has been built, and return the normalization factor
|
||
|
static float NormalizeDsDt(uint16 *src, sint width, sint height)
|
||
|
{
|
||
|
const uint size = width * height;
|
||
|
uint highestDelta = 0;
|
||
|
uint k;
|
||
|
|
||
|
for (k = 0; k < size; ++k)
|
||
|
{
|
||
|
highestDelta = std::max(highestDelta, (uint) ::abs((sint) (sint8) (src[k] & 255)));
|
||
|
highestDelta = std::max(highestDelta, (uint) ::abs((sint) (sint8) (src[k] >> 8)));
|
||
|
}
|
||
|
|
||
|
if (highestDelta == 0)
|
||
|
{
|
||
|
return 1.f;
|
||
|
}
|
||
|
float normalizationFactor = 127.f / highestDelta;
|
||
|
for (k = 0; k < size; ++k)
|
||
|
{
|
||
|
float fdu = (sint8) (src[k] & 255) * normalizationFactor;
|
||
|
float fdv = (sint8) (src[k] >> 8) * normalizationFactor;
|
||
|
NLMISC::clamp(fdu, -128, 127);
|
||
|
NLMISC::clamp(fdv, -128, 127);
|
||
|
uint8 du = (uint8) (sint8) fdu;
|
||
|
uint8 dv = (uint8) (sint8) fdv;
|
||
|
src[k] = (uint16) du | (((uint16) dv) << 8);
|
||
|
}
|
||
|
return 1.f / normalizationFactor;
|
||
|
}
|
||
|
|
||
|
static float NormalizeDsDtAsRGBA(uint32 *src, sint width, sint height)
|
||
|
{
|
||
|
uint k;
|
||
|
const uint size = width * height;
|
||
|
uint highestDelta = 0;
|
||
|
|
||
|
/// first, get the highest delta
|
||
|
for (k = 0; k < size; ++k)
|
||
|
{
|
||
|
highestDelta = std::max(highestDelta, (uint) abs((sint8) ((src[k] & 0xff) - 0x80)));
|
||
|
highestDelta = std::max(highestDelta, (uint) abs((sint8) (((src[k] >> 8) & 0xff) - 0x80)));
|
||
|
}
|
||
|
|
||
|
if (highestDelta == 0)
|
||
|
{
|
||
|
return 1.f;
|
||
|
}
|
||
|
float normalizationFactor = 127.f / highestDelta;
|
||
|
for (k = 0; k < size; ++k)
|
||
|
{
|
||
|
float fdu = ((sint8) ((src[k] & 255) - 0x80)) * normalizationFactor;
|
||
|
float fdv = ((sint8) (((src[k] >> 8) & 0xff) - 0x80)) * normalizationFactor;
|
||
|
NLMISC::clamp(fdu, -128, 127);
|
||
|
NLMISC::clamp(fdv, -128, 127);
|
||
|
uint8 du = (uint8) ((sint8) fdu + 0x80);
|
||
|
uint8 dv = (uint8) ((sint8) fdv + 0x80);
|
||
|
src[k] = (src[k] & 0xffff0000) | (uint32) du | (uint32) (((uint16) dv) << 8);
|
||
|
}
|
||
|
return 1.f / normalizationFactor;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Constructor
|
||
|
*/
|
||
|
CTextureBump::CTextureBump() : _NormalizationFactor(NULL),
|
||
|
_DisableSharing(false),
|
||
|
_ForceNormalize(true)
|
||
|
{
|
||
|
// mipmapping not supported for now, disable it
|
||
|
ITexture::setFilterMode(ITexture::Linear, ITexture::LinearMipMapOff);
|
||
|
}
|
||
|
|
||
|
|
||
|
///==============================================================================================
|
||
|
void CTextureBump::setFilterMode(TMagFilter magf, TMinFilter minf)
|
||
|
{
|
||
|
nlstop; // set filter mode not allowed with bump textures (not supported by some GPUs)
|
||
|
}
|
||
|
|
||
|
void CTextureBump::setHeightMap(ITexture *heightMap)
|
||
|
{
|
||
|
if (heightMap != _HeightMap)
|
||
|
{
|
||
|
_HeightMap = heightMap;
|
||
|
touch();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
///==============================================================================================
|
||
|
void CTextureBump::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
|
||
|
{
|
||
|
/// version 2 : normalization flag
|
||
|
sint ver = f.serialVersion(3);
|
||
|
ITexture::serial(f);
|
||
|
ITexture *tex = NULL;
|
||
|
if (f.isReading())
|
||
|
{
|
||
|
f.serialPolyPtr(tex);
|
||
|
_HeightMap = tex;
|
||
|
touch();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
tex = _HeightMap;
|
||
|
f.serialPolyPtr(tex);
|
||
|
}
|
||
|
f.serial(_DisableSharing);
|
||
|
if (ver >= 1)
|
||
|
{
|
||
|
bool oldFlag = false;
|
||
|
f.serial(oldFlag); // backaward compatibility : serial the old "forceAbsoluteOffset" flag
|
||
|
}
|
||
|
if (ver >= 2)
|
||
|
{
|
||
|
f.serial(_ForceNormalize);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///==============================================================================================
|
||
|
void CTextureBump::doGenerate(bool async)
|
||
|
{
|
||
|
if (!_HeightMap)
|
||
|
{
|
||
|
makeDummy();
|
||
|
return;
|
||
|
}
|
||
|
// generate the height map
|
||
|
_HeightMap->generate();
|
||
|
if (!_HeightMap->convertToType(CBitmap::RGBA))
|
||
|
{
|
||
|
makeDummy();
|
||
|
return;
|
||
|
}
|
||
|
releaseMipMaps();
|
||
|
uint width = _HeightMap->getWidth();
|
||
|
uint height = _HeightMap->getHeight();
|
||
|
if (getUploadFormat() == RGBA8888)
|
||
|
{
|
||
|
CBitmap::resize(_HeightMap->getWidth(), _HeightMap->getHeight(), CBitmap::RGBA);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CBitmap::resize(_HeightMap->getWidth(), _HeightMap->getHeight(), CBitmap::DsDt);
|
||
|
}
|
||
|
// build the DsDt map
|
||
|
if (getUploadFormat() == RGBA8888)
|
||
|
{
|
||
|
BuildDsDtAsRGBA((uint32 *) &(_HeightMap->getPixels()[0]), width, height, (uint32 *) &(getPixels()[0]));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
BuildDsDt((uint32 *) &(_HeightMap->getPixels()[0]), width, height, (uint16 *) &(getPixels()[0]));
|
||
|
}
|
||
|
|
||
|
float normalizationFactor = 1.0f;
|
||
|
// Normalize the map if needed
|
||
|
if (_ForceNormalize)
|
||
|
{
|
||
|
if (getUploadFormat() == RGBA8888)
|
||
|
{
|
||
|
normalizationFactor = NormalizeDsDtAsRGBA((uint32 *) &(getPixels()[0]), width, height);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
normalizationFactor = NormalizeDsDt((uint16 *) &(getPixels()[0]), width, height);
|
||
|
}
|
||
|
}
|
||
|
// create entry in the map for the normalization factor
|
||
|
std::string shareName = getShareName();
|
||
|
TNameToNI::iterator it = _NameToNF.find(shareName);
|
||
|
if (it == _NameToNF.end())
|
||
|
{
|
||
|
// create a new entry
|
||
|
CNormalizationInfo ni;
|
||
|
ni.NumRefs = 1;
|
||
|
ni.NormalizationFactor = normalizationFactor;
|
||
|
std::pair<TNameToNI::iterator, bool> pb = _NameToNF.insert(TNameToNI::value_type(shareName, ni));
|
||
|
_NormalizationFactor = &(pb.first->second.NormalizationFactor);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// another map has computed the factor
|
||
|
_NormalizationFactor = &(it->second.NormalizationFactor);
|
||
|
++(it->second.NumRefs);
|
||
|
}
|
||
|
|
||
|
if (_HeightMap->getReleasable())
|
||
|
{
|
||
|
_HeightMap->release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///==============================================================================================
|
||
|
void CTextureBump::release()
|
||
|
{
|
||
|
ITexture::release();
|
||
|
if (_HeightMap != NULL)
|
||
|
{
|
||
|
if (_HeightMap->getReleasable())
|
||
|
{
|
||
|
_HeightMap->release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
///==============================================================================================
|
||
|
bool CTextureBump::supportSharing() const
|
||
|
{
|
||
|
return !_DisableSharing && _HeightMap && _HeightMap->supportSharing();
|
||
|
}
|
||
|
|
||
|
|
||
|
///==============================================================================================
|
||
|
std::string CTextureBump::getShareName() const
|
||
|
{
|
||
|
nlassert(supportSharing());
|
||
|
return "BumpDsDt:" + _HeightMap->getShareName();
|
||
|
}
|
||
|
|
||
|
///==============================================================================================
|
||
|
float CTextureBump::getNormalizationFactor()
|
||
|
{
|
||
|
if (!_Touched)
|
||
|
{
|
||
|
if (_NormalizationFactor) return *_NormalizationFactor;
|
||
|
}
|
||
|
if (!_HeightMap) return 1.f;
|
||
|
// not computed yet, see if another map has computed it
|
||
|
std::string shareName = getShareName();
|
||
|
TNameToNI::iterator it = _NameToNF.find(shareName);
|
||
|
if (it != _NameToNF.end())
|
||
|
{
|
||
|
_NormalizationFactor = &(it->second.NormalizationFactor);
|
||
|
++(it->second.NumRefs);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
doGenerate();
|
||
|
if (this->getReleasable()) this->release();
|
||
|
}
|
||
|
return _NormalizationFactor ? *_NormalizationFactor : 1.f;
|
||
|
}
|
||
|
|
||
|
///==============================================================================================
|
||
|
CTextureBump::~CTextureBump()
|
||
|
{
|
||
|
if (_NormalizationFactor && !_NameToNF.empty())
|
||
|
{
|
||
|
// find normalization factor from its name
|
||
|
TNameToNI::iterator it = _NameToNF.find(getShareName());
|
||
|
|
||
|
// if found
|
||
|
if (it != _NameToNF.end())
|
||
|
{
|
||
|
// we can delete it only if it's not used anymore
|
||
|
if (--(it->second.NumRefs) == 0) _NameToNF.erase(it);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
} // NL3D
|