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