// 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_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 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