From 9c7474f88c8a7a9f3127eba14a0f6ec793df64f1 Mon Sep 17 00:00:00 2001 From: capostrophic Date: Tue, 13 Aug 2019 01:20:30 +0300 Subject: [PATCH] Implement NiPalette support (feature #4882) --- CHANGELOG.md | 1 + components/nif/data.cpp | 38 +++++++++++++++------ components/nif/data.hpp | 17 ++++++++-- components/nif/niffile.cpp | 1 + components/nif/record.hpp | 3 +- components/nif/recordptr.hpp | 2 ++ components/nifosg/nifloader.cpp | 59 ++++++++++++++++++++++++++++++--- 7 files changed, 103 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfe21ab8d..45ffb8618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -148,6 +148,7 @@ Feature #4812: Support NiSwitchNode Feature #4836: Daytime node switch Feature #4859: Make water reflections more configurable + Feature #4882: Support for NiPalette node Feature #4887: Add openmw command option to set initial random seed Feature #4890: Make Distant Terrain configurable Feature #4958: Support eight blood types diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 4a9266239..5f7722794 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -163,22 +163,23 @@ void NiPixelData::read(NIFStream *nif) { fmt = (Format)nif->getUInt(); - rmask = nif->getInt(); // usually 0xff - gmask = nif->getInt(); // usually 0xff00 - bmask = nif->getInt(); // usually 0xff0000 - amask = nif->getInt(); // usually 0xff000000 or zero + rmask = nif->getUInt(); // usually 0xff + gmask = nif->getUInt(); // usually 0xff00 + bmask = nif->getUInt(); // usually 0xff0000 + amask = nif->getUInt(); // usually 0xff000000 or zero - bpp = nif->getInt(); + bpp = nif->getUInt(); - // Unknown - nif->skip(12); + // 8 bytes of "Old Fast Compare". Whatever that means. + nif->skip(8); + palette.read(nif); - numberOfMipmaps = nif->getInt(); + numberOfMipmaps = nif->getUInt(); // Bytes per pixel, should be bpp * 8 - /* int bytes = */ nif->getInt(); + /* int bytes = */ nif->getUInt(); - for(int i=0; igetInt(); + unsigned int dataSize = nif->getUInt(); data.reserve(dataSize); for (unsigned i=0; igetChar()); } +void NiPixelData::post(NIFFile *nif) +{ + palette.post(nif); +} + void NiColorData::read(NIFStream *nif) { mKeyMap = std::make_shared(); @@ -278,4 +284,14 @@ void NiKeyframeData::read(NIFStream *nif) mScales->read(nif); } +void NiPalette::read(NIFStream *nif) +{ + unsigned int alphaMask = !nif->getChar() ? 0xFF000000 : 0; + // Fill the entire palette with black even if there isn't enough entries. + colors.resize(256); + unsigned int numEntries = nif->getUInt(); + for (unsigned int i = 0; i < numEntries; i++) + colors[i] = nif->getUInt() | alphaMask; +} + } // Namespace diff --git a/components/nif/data.hpp b/components/nif/data.hpp index fb1199cff..a0d4960e0 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -116,6 +116,7 @@ public: NIPXFMT_RGB8, NIPXFMT_RGBA8, NIPXFMT_PAL8, + NIPXFMT_PALA8, NIPXFMT_DXT1, NIPXFMT_DXT3, NIPXFMT_DXT5, @@ -123,8 +124,10 @@ public: }; Format fmt; - unsigned int rmask, gmask, bmask, amask; - int bpp, numberOfMipmaps; + unsigned int rmask, gmask, bmask, amask, bpp; + + NiPalettePtr palette; + unsigned int numberOfMipmaps; struct Mipmap { @@ -136,6 +139,7 @@ public: std::vector data; void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiColorData : public Record @@ -219,5 +223,14 @@ struct NiKeyframeData : public Record void read(NIFStream *nif); }; +class NiPalette : public Record +{ +public: + // 32-bit RGBA colors that correspond to 8-bit indices + std::vector colors; + + void read(NIFStream *nif); +}; + } // Namespace #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 4b3760ba2..d4f1203cc 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -112,6 +112,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiSourceTexture", &construct , RC_NiSourceTexture )); newFactory.insert(makeEntry("NiSkinInstance", &construct , RC_NiSkinInstance )); newFactory.insert(makeEntry("NiLookAtController", &construct , RC_NiLookAtController )); + newFactory.insert(makeEntry("NiPalette", &construct , RC_NiPalette )); return newFactory; } diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 909c268bb..67ffbc574 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -97,7 +97,8 @@ enum RecordType RC_NiSkinInstance, RC_RootCollisionNode, RC_NiSphericalCollider, - RC_NiLookAtController + RC_NiLookAtController, + RC_NiPalette }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 977973517..e23beb786 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -140,6 +140,7 @@ class NiSkinInstance; class NiSourceTexture; class NiRotatingParticlesData; class NiAutoNormalParticlesData; +class NiPalette; typedef RecordPtrT NodePtr; typedef RecordPtrT ExtraPtr; @@ -160,6 +161,7 @@ typedef RecordPtrT NiSkinInstancePtr; typedef RecordPtrT NiSourceTexturePtr; typedef RecordPtrT NiRotatingParticlesDataPtr; typedef RecordPtrT NiAutoNormalParticlesDataPtr; +typedef RecordPtrT NiPalettePtr; typedef RecordListT NodeList; typedef RecordListT PropertyList; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f0e41c3ac..31dcd2a55 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1276,9 +1276,11 @@ namespace NifOsg switch (pixelData->fmt) { case Nif::NiPixelData::NIPXFMT_RGB8: + case Nif::NiPixelData::NIPXFMT_PAL8: pixelformat = GL_RGB; break; case Nif::NiPixelData::NIPXFMT_RGBA8: + case Nif::NiPixelData::NIPXFMT_PALA8: pixelformat = GL_RGBA; break; default: @@ -1293,7 +1295,7 @@ namespace NifOsg int height = 0; std::vector mipmapVector; - for (unsigned int i=0; imipmaps.size()-3; ++i) + for (unsigned int i=0; imipmaps.size(); ++i) { const Nif::NiPixelData::Mipmap& mip = pixelData->mipmaps[i]; @@ -1319,10 +1321,59 @@ namespace NifOsg return nullptr; } - unsigned char* data = new unsigned char[pixelData->data.size()]; - memcpy(data, pixelData->data.data(), pixelData->data.size()); + const std::vector& pixels = pixelData->data; + switch (pixelData->fmt) + { + case Nif::NiPixelData::NIPXFMT_RGB8: + case Nif::NiPixelData::NIPXFMT_RGBA8: + { + unsigned char* data = new unsigned char[pixels.size()]; + memcpy(data, pixels.data(), pixels.size()); + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); + break; + } + case Nif::NiPixelData::NIPXFMT_PAL8: + case Nif::NiPixelData::NIPXFMT_PALA8: + { + if (pixelData->palette.empty() || pixelData->bpp != 8) + { + Log(Debug::Info) << "Palettized texture in " << mFilename << " is invalid, ignoring"; + return nullptr; + } + // We're going to convert the indices that pixel data contains + // into real colors using the palette. + const std::vector& palette = pixelData->palette->colors; + if (pixelData->fmt == Nif::NiPixelData::NIPXFMT_PAL8) + { + unsigned char* data = new unsigned char[pixels.size() * 3]; + for (size_t i = 0; i < pixels.size(); i++) + { + unsigned int color = palette[pixels[i]]; + data[i * 3 + 0] = (color >> 0) & 0xFF; + data[i * 3 + 1] = (color >> 8) & 0xFF; + data[i * 3 + 2] = (color >> 16) & 0xFF; + } + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); + } + else // if (fmt = NIPXFMT_PALA8) + { + unsigned char* data = new unsigned char[pixels.size() * 4]; + for (size_t i = 0; i < pixels.size(); i++) + { + unsigned int color = palette[pixels[i]]; + data[i * 4 + 0] = (color >> 0) & 0xFF; + data[i * 4 + 1] = (color >> 8) & 0xFF; + data[i * 4 + 2] = (color >> 16) & 0xFF; + data[i * 4 + 3] = (color >> 24) & 0xFF; + } + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); + } + break; + } + default: + return nullptr; + } - image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); image->setMipmapLevels(mipmapVector); image->flipVertical();