mirror of https://github.com/OpenMW/openmw.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
15 KiB
C++
420 lines
15 KiB
C++
#include "util.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
|
|
#include <osg/FrameBufferObject>
|
|
#include <osg/Node>
|
|
#include <osg/NodeVisitor>
|
|
#include <osg/TexEnvCombine>
|
|
#include <osg/TexGen>
|
|
#include <osgUtil/CullVisitor>
|
|
#include <osgUtil/RenderStage>
|
|
|
|
#include <components/resource/imagemanager.hpp>
|
|
#include <components/resource/scenemanager.hpp>
|
|
#include <components/sceneutil/texturetype.hpp>
|
|
|
|
namespace SceneUtil
|
|
{
|
|
namespace
|
|
{
|
|
std::array<std::string, 32> generateGlowTextureNames()
|
|
{
|
|
std::array<std::string, 32> result;
|
|
for (std::size_t i = 0; i < result.size(); ++i)
|
|
{
|
|
std::stringstream stream;
|
|
stream << "textures/magicitem/caust";
|
|
stream << std::setw(2);
|
|
stream << std::setfill('0');
|
|
stream << i;
|
|
stream << ".dds";
|
|
result[i] = std::move(stream).str();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const std::array<std::string, 32> glowTextureNames = generateGlowTextureNames();
|
|
|
|
struct FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor
|
|
{
|
|
FindLowestUnusedTexUnitVisitor()
|
|
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
|
{
|
|
}
|
|
|
|
void apply(osg::Node& node) override
|
|
{
|
|
if (osg::StateSet* stateset = node.getStateSet())
|
|
mLowestUnusedTexUnit
|
|
= std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size()));
|
|
|
|
traverse(node);
|
|
}
|
|
|
|
int mLowestUnusedTexUnit = 0;
|
|
};
|
|
}
|
|
|
|
GlowUpdater::GlowUpdater(int texUnit, const osg::Vec4f& color,
|
|
const std::vector<osg::ref_ptr<osg::Texture2D>>& textures, osg::Node* node, float duration,
|
|
Resource::ResourceSystem* resourcesystem)
|
|
: mTexUnit(texUnit)
|
|
, mColor(color)
|
|
, mOriginalColor(color)
|
|
, mTextures(textures)
|
|
, mNode(node)
|
|
, mDuration(duration)
|
|
, mOriginalDuration(duration)
|
|
, mStartingTime(0)
|
|
, mResourceSystem(resourcesystem)
|
|
, mColorChanged(false)
|
|
, mDone(false)
|
|
{
|
|
}
|
|
|
|
void GlowUpdater::setDefaults(osg::StateSet* stateset)
|
|
{
|
|
if (mDone)
|
|
removeTexture(stateset);
|
|
else
|
|
{
|
|
stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON);
|
|
osg::TexGen* texGen = new osg::TexGen;
|
|
texGen->setMode(osg::TexGen::SPHERE_MAP);
|
|
|
|
stateset->setTextureAttributeAndModes(
|
|
mTexUnit, texGen, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
|
|
|
|
osg::TexEnvCombine* texEnv = new osg::TexEnvCombine;
|
|
texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT);
|
|
texEnv->setConstantColor(mColor);
|
|
texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE);
|
|
texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE);
|
|
texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR);
|
|
|
|
stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON);
|
|
stateset->addUniform(new osg::Uniform("envMapColor", mColor));
|
|
}
|
|
}
|
|
|
|
void GlowUpdater::removeTexture(osg::StateSet* stateset)
|
|
{
|
|
stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE);
|
|
stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN);
|
|
stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXENV);
|
|
stateset->removeTextureMode(mTexUnit, GL_TEXTURE_2D);
|
|
stateset->removeUniform("envMapColor");
|
|
|
|
osg::StateSet::TextureAttributeList& list = stateset->getTextureAttributeList();
|
|
while (list.size() && list.rbegin()->empty())
|
|
list.pop_back();
|
|
}
|
|
|
|
void GlowUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* nv)
|
|
{
|
|
if (mColorChanged)
|
|
{
|
|
this->reset();
|
|
setDefaults(stateset);
|
|
mColorChanged = false;
|
|
}
|
|
if (mDone)
|
|
return;
|
|
|
|
// Set the starting time to measure glow duration from if this is a temporary glow
|
|
if ((mDuration >= 0) && mStartingTime == 0)
|
|
mStartingTime = nv->getFrameStamp()->getSimulationTime();
|
|
|
|
float time = nv->getFrameStamp()->getSimulationTime();
|
|
int index = (int)(time * 16) % mTextures.size();
|
|
stateset->setTextureAttribute(
|
|
mTexUnit, mTextures[index], osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
|
|
|
|
if ((mDuration >= 0)
|
|
&& (time - mStartingTime > mDuration)) // If this is a temporary glow and it has finished its duration
|
|
{
|
|
if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation
|
|
{
|
|
removeTexture(stateset);
|
|
this->reset();
|
|
mDone = true;
|
|
// normally done in StateSetUpdater::operator(), but needs doing here so the shader visitor sees the
|
|
// right StateSet
|
|
mNode->setStateSet(stateset);
|
|
mResourceSystem->getSceneManager()->recreateShaders(mNode);
|
|
}
|
|
if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow
|
|
{
|
|
mDuration = mOriginalDuration;
|
|
mStartingTime = 0;
|
|
mColor = mOriginalColor;
|
|
this->reset();
|
|
setDefaults(stateset);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GlowUpdater::isPermanentGlowUpdater()
|
|
{
|
|
return (mDuration < 0);
|
|
}
|
|
|
|
bool GlowUpdater::isDone()
|
|
{
|
|
return mDone;
|
|
}
|
|
|
|
void GlowUpdater::setColor(const osg::Vec4f& color)
|
|
{
|
|
mColor = color;
|
|
mColorChanged = true;
|
|
}
|
|
|
|
void GlowUpdater::setDuration(float duration)
|
|
{
|
|
mDuration = duration;
|
|
}
|
|
|
|
osg::Vec4f colourFromRGB(unsigned int clr)
|
|
{
|
|
osg::Vec4f colour(
|
|
((clr >> 0) & 0xFF) / 255.0f, ((clr >> 8) & 0xFF) / 255.0f, ((clr >> 16) & 0xFF) / 255.0f, 1.f);
|
|
return colour;
|
|
}
|
|
|
|
osg::Vec4f colourFromRGBA(unsigned int value)
|
|
{
|
|
return osg::Vec4f(makeOsgColorComponent(value, 0), makeOsgColorComponent(value, 8),
|
|
makeOsgColorComponent(value, 16), makeOsgColorComponent(value, 24));
|
|
}
|
|
|
|
float makeOsgColorComponent(unsigned int value, unsigned int shift)
|
|
{
|
|
return float((value >> shift) & 0xFFu) / 255.0f;
|
|
}
|
|
|
|
bool hasUserDescription(const osg::Node* node, std::string_view pattern)
|
|
{
|
|
if (node == nullptr)
|
|
return false;
|
|
|
|
const osg::UserDataContainer* udc = node->getUserDataContainer();
|
|
if (udc && udc->getNumDescriptions() > 0)
|
|
{
|
|
for (auto& descr : udc->getDescriptions())
|
|
{
|
|
if (descr == pattern)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
osg::ref_ptr<GlowUpdater> addEnchantedGlow(osg::ref_ptr<osg::Node> node, Resource::ResourceSystem* resourceSystem,
|
|
const osg::Vec4f& glowColor, float glowDuration)
|
|
{
|
|
std::vector<osg::ref_ptr<osg::Texture2D>> textures;
|
|
for (const std::string& name : glowTextureNames)
|
|
{
|
|
osg::ref_ptr<osg::Image> image = resourceSystem->getImageManager()->getImage(name);
|
|
osg::ref_ptr<osg::Texture2D> tex(new osg::Texture2D(image));
|
|
tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT);
|
|
tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT);
|
|
resourceSystem->getSceneManager()->applyFilterSettings(tex);
|
|
textures.push_back(tex);
|
|
}
|
|
|
|
FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor;
|
|
node->accept(findLowestUnusedTexUnitVisitor);
|
|
int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit;
|
|
|
|
osg::ref_ptr<GlowUpdater> glowUpdater
|
|
= new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, resourceSystem);
|
|
node->addUpdateCallback(glowUpdater);
|
|
|
|
// set a texture now so that the ShaderVisitor can find it
|
|
osg::ref_ptr<osg::StateSet> writableStateSet = nullptr;
|
|
if (!node->getStateSet())
|
|
writableStateSet = node->getOrCreateStateSet();
|
|
else
|
|
{
|
|
writableStateSet = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY);
|
|
node->setStateSet(writableStateSet);
|
|
}
|
|
writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON);
|
|
writableStateSet->setTextureAttributeAndModes(texUnit, new TextureType("envMap"), osg::StateAttribute::ON);
|
|
writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor));
|
|
resourceSystem->getSceneManager()->recreateShaders(std::move(node));
|
|
|
|
return glowUpdater;
|
|
}
|
|
|
|
void attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer,
|
|
osg::Texture* texture, unsigned int level, unsigned int face, bool mipMapGeneration,
|
|
bool addMSAAIntermediateTarget)
|
|
{
|
|
unsigned int samples = 0;
|
|
unsigned int colourSamples = 0;
|
|
if (addMSAAIntermediateTarget)
|
|
{
|
|
// Alpha-to-coverage requires a multisampled framebuffer.
|
|
// OSG will set that up automatically and resolve it to the specified single-sample texture for us.
|
|
// For some reason, two samples are needed, at least with some drivers.
|
|
samples = 2;
|
|
colourSamples = 1;
|
|
}
|
|
camera->attach(buffer, texture, level, face, mipMapGeneration, samples, colourSamples);
|
|
}
|
|
|
|
OperationSequence::OperationSequence(bool keep)
|
|
: Operation("OperationSequence", keep)
|
|
, mOperationQueue(new osg::OperationQueue())
|
|
{
|
|
}
|
|
|
|
void OperationSequence::operator()(osg::Object* object)
|
|
{
|
|
mOperationQueue->runOperations(object);
|
|
}
|
|
|
|
void OperationSequence::add(osg::Operation* operation)
|
|
{
|
|
mOperationQueue->add(operation);
|
|
}
|
|
|
|
GLenum computeUnsizedPixelFormat(GLenum format)
|
|
{
|
|
switch (format)
|
|
{
|
|
// Try compressed formats first, they're more likely to be used
|
|
|
|
// Generic
|
|
case GL_COMPRESSED_ALPHA_ARB:
|
|
return GL_ALPHA;
|
|
case GL_COMPRESSED_INTENSITY_ARB:
|
|
return GL_INTENSITY;
|
|
case GL_COMPRESSED_LUMINANCE_ALPHA_ARB:
|
|
return GL_LUMINANCE_ALPHA;
|
|
case GL_COMPRESSED_LUMINANCE_ARB:
|
|
return GL_LUMINANCE;
|
|
case GL_COMPRESSED_RGB_ARB:
|
|
return GL_RGB;
|
|
case GL_COMPRESSED_RGBA_ARB:
|
|
return GL_RGBA;
|
|
|
|
// S3TC
|
|
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
|
|
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
|
|
return GL_RGB;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
|
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
|
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
|
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
|
|
return GL_RGBA;
|
|
|
|
// RGTC
|
|
case GL_COMPRESSED_RED_RGTC1_EXT:
|
|
case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT:
|
|
return GL_RED;
|
|
case GL_COMPRESSED_RED_GREEN_RGTC2_EXT:
|
|
case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
|
|
return GL_RG;
|
|
|
|
// PVRTC
|
|
case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
|
|
case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
|
|
return GL_RGB;
|
|
case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
|
|
case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
|
|
return GL_RGBA;
|
|
|
|
// ETC
|
|
case GL_COMPRESSED_R11_EAC:
|
|
case GL_COMPRESSED_SIGNED_R11_EAC:
|
|
return GL_RED;
|
|
case GL_COMPRESSED_RG11_EAC:
|
|
case GL_COMPRESSED_SIGNED_RG11_EAC:
|
|
return GL_RG;
|
|
case GL_ETC1_RGB8_OES:
|
|
case GL_COMPRESSED_RGB8_ETC2:
|
|
case GL_COMPRESSED_SRGB8_ETC2:
|
|
return GL_RGB;
|
|
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
|
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
|
case GL_COMPRESSED_RGBA8_ETC2_EAC:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
|
|
return GL_RGBA;
|
|
|
|
// ASTC
|
|
case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
|
|
case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
|
|
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
|
|
return GL_RGBA;
|
|
|
|
// Plug in some holes computePixelFormat has, you never know when these could come in handy
|
|
case GL_INTENSITY4:
|
|
case GL_INTENSITY8:
|
|
case GL_INTENSITY12:
|
|
case GL_INTENSITY16:
|
|
return GL_INTENSITY;
|
|
|
|
case GL_LUMINANCE4:
|
|
case GL_LUMINANCE8:
|
|
case GL_LUMINANCE12:
|
|
case GL_LUMINANCE16:
|
|
return GL_LUMINANCE;
|
|
|
|
case GL_LUMINANCE4_ALPHA4:
|
|
case GL_LUMINANCE6_ALPHA2:
|
|
case GL_LUMINANCE8_ALPHA8:
|
|
case GL_LUMINANCE12_ALPHA4:
|
|
case GL_LUMINANCE12_ALPHA12:
|
|
case GL_LUMINANCE16_ALPHA16:
|
|
return GL_LUMINANCE_ALPHA;
|
|
}
|
|
|
|
return osg::Image::computePixelFormat(format);
|
|
}
|
|
|
|
const std::string& getTextureType(const osg::StateSet& stateset, const osg::Texture& texture, unsigned int texUnit)
|
|
{
|
|
const osg::StateAttribute* type = stateset.getTextureAttribute(texUnit, SceneUtil::TextureType::AttributeType);
|
|
if (type)
|
|
return static_cast<const SceneUtil::TextureType*>(type)->getName();
|
|
|
|
return texture.getName();
|
|
}
|
|
}
|