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.
openmw-tes3mp/apps/openmw/mwrender/sky.cpp

1904 lines
64 KiB
C++

#include "sky.hpp"
#include <cmath>
#include <osg/ClipPlane>
#include <osg/Fog>
#include <osg/Transform>
#include <osg/Depth>
#include <osg/Geometry>
#include <osg/Material>
#include <osg/TexEnvCombine>
#include <osg/TexMat>
#include <osg/OcclusionQueryNode>
#include <osg/ColorMask>
#include <osg/PositionAttitudeTransform>
#include <osg/BlendFunc>
#include <osg/AlphaFunc>
#include <osg/PolygonOffset>
#include <osg/observer_ptr>
#include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleSystemUpdater>
#include <osgParticle/ModularEmitter>
#include <osgParticle/BoxPlacer>
#include <osgParticle/ConstantRateCounter>
#include <osgParticle/RadialShooter>
#include <osgParticle/Operator>
#include <osgParticle/ModularProgram>
#include <components/misc/rng.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/vfs/manager.hpp>
#include <components/fallback/fallback.hpp>
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/statesetupdater.hpp>
#include <components/sceneutil/controller.hpp>
#include <components/sceneutil/visitor.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "vismask.hpp"
#include "renderbin.hpp"
namespace
{
osg::ref_ptr<osg::Material> createAlphaTrackingUnlitMaterial()
{
osg::ref_ptr<osg::Material> mat = new osg::Material;
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f));
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f));
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f));
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f));
mat->setColorMode(osg::Material::DIFFUSE);
return mat;
}
osg::ref_ptr<osg::Material> createUnlitMaterial()
{
osg::ref_ptr<osg::Material> mat = new osg::Material;
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f));
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f));
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f));
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f));
mat->setColorMode(osg::Material::OFF);
return mat;
}
osg::ref_ptr<osg::Geometry> createTexturedQuad(int numUvSets=1)
{
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
osg::ref_ptr<osg::Vec3Array> verts = new osg::Vec3Array;
verts->push_back(osg::Vec3f(-0.5, -0.5, 0));
verts->push_back(osg::Vec3f(-0.5, 0.5, 0));
verts->push_back(osg::Vec3f(0.5, 0.5, 0));
verts->push_back(osg::Vec3f(0.5, -0.5, 0));
geom->setVertexArray(verts);
osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
texcoords->push_back(osg::Vec2f(0, 0));
texcoords->push_back(osg::Vec2f(0, 1));
texcoords->push_back(osg::Vec2f(1, 1));
texcoords->push_back(osg::Vec2f(1, 0));
osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f));
geom->setColorArray(colors, osg::Array::BIND_OVERALL);
for (int i=0; i<numUvSets; ++i)
geom->setTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX);
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4));
return geom;
}
}
namespace MWRender
{
class AtmosphereUpdater : public SceneUtil::StateSetUpdater
{
public:
void setEmissionColor(const osg::Vec4f& emissionColor)
{
mEmissionColor = emissionColor;
}
protected:
virtual void setDefaults(osg::StateSet* stateset)
{
stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
}
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/)
{
osg::Material* mat = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor);
}
private:
osg::Vec4f mEmissionColor;
};
class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater
{
public:
AtmosphereNightUpdater(Resource::ImageManager* imageManager)
{
// we just need a texture, its contents don't really matter
mTexture = new osg::Texture2D(imageManager->getWarningImage());
}
void setFade(const float fade)
{
mColor.a() = fade;
}
protected:
virtual void setDefaults(osg::StateSet* stateset)
{
osg::ref_ptr<osg::TexEnvCombine> texEnv (new osg::TexEnvCombine);
texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE);
texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS);
texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT);
texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE);
texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
}
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/)
{
osg::TexEnvCombine* texEnv = static_cast<osg::TexEnvCombine*>(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV));
texEnv->setConstantColor(mColor);
}
osg::ref_ptr<osg::Texture2D> mTexture;
osg::Vec4f mColor;
};
class CloudUpdater : public SceneUtil::StateSetUpdater
{
public:
CloudUpdater()
: mAnimationTimer(0.f)
, mOpacity(0.f)
{
}
void setAnimationTimer(float timer)
{
mAnimationTimer = timer;
}
void setTexture(osg::ref_ptr<osg::Texture2D> texture)
{
mTexture = texture;
}
void setEmissionColor(const osg::Vec4f& emissionColor)
{
mEmissionColor = emissionColor;
}
void setOpacity(float opacity)
{
mOpacity = opacity;
}
protected:
virtual void setDefaults(osg::StateSet *stateset)
{
osg::ref_ptr<osg::TexMat> texmat (new osg::TexMat);
stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON);
stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
// need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already
osg::ref_ptr<osg::TexEnvCombine> texEnvCombine (new osg::TexEnvCombine);
texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS);
texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT);
texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,1));
texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE);
texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE);
stateset->setTextureAttributeAndModes(1, texEnvCombine, osg::StateAttribute::ON);
stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
}
virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
{
osg::TexMat* texMat = static_cast<osg::TexMat*>(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT));
texMat->setMatrix(osg::Matrix::translate(osg::Vec3f(0, mAnimationTimer, 0.f)));
stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
osg::Material* mat = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor);
osg::TexEnvCombine* texEnvCombine = static_cast<osg::TexEnvCombine*>(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV));
texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,mOpacity));
}
private:
float mAnimationTimer;
osg::ref_ptr<osg::Texture2D> mTexture;
osg::Vec4f mEmissionColor;
float mOpacity;
};
/// Transform that removes the eyepoint of the modelview matrix,
/// i.e. its children are positioned relative to the camera.
class CameraRelativeTransform : public osg::Transform
{
public:
CameraRelativeTransform()
{
// Culling works in node-local space, not in camera space, so we can't cull this node correctly
// That's not a problem though, children of this node can be culled just fine
// Just make sure you do not place a CameraRelativeTransform deep in the scene graph
setCullingActive(false);
addCullCallback(new CullCallback);
}
CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop)
: osg::Transform(copy, copyop)
{
}
META_Node(MWRender, CameraRelativeTransform)
const osg::Vec3f& getLastEyePoint() const
{
return mEyePoint;
}
virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const
{
if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
{
mEyePoint = static_cast<osgUtil::CullVisitor*>(nv)->getEyePoint();
}
if (_referenceFrame==RELATIVE_RF)
{
matrix.setTrans(osg::Vec3f(0.f,0.f,0.f));
return false;
}
else // absolute
{
matrix.makeIdentity();
return true;
}
}
osg::BoundingSphere computeBound() const
{
return osg::BoundingSphere(osg::Vec3f(0,0,0), 0);
}
class CullCallback : public osg::NodeCallback
{
public:
virtual void operator() (osg::Node* node, osg::NodeVisitor* nv)
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
// XXX have to remove unwanted culling plane of the water reflection camera
// Remove all planes that aren't from the standard frustum
unsigned int numPlanes = 4;
if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING)
++numPlanes;
if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING)
++numPlanes;
int mask = 0x1;
int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask();
for (unsigned int i=0; i<cv->getProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i)
{
if (i >= numPlanes)
{
// turn off this culling plane
resultMask &= (~mask);
}
mask <<= 1;
}
cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask);
cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask);
cv->getProjectionCullingStack().back().pushCurrentMask();
cv->getCurrentCullingSet().pushCurrentMask();
traverse(node, nv);
cv->getProjectionCullingStack().back().popCurrentMask();
cv->getCurrentCullingSet().popCurrentMask();
}
};
private:
// eyePoint for the current frame
mutable osg::Vec3f mEyePoint;
};
class ModVertexAlphaVisitor : public osg::NodeVisitor
{
public:
ModVertexAlphaVisitor(int meshType)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mMeshType(meshType)
{
}
void apply(osg::Drawable& drw)
{
osg::Geometry* geom = drw.asGeometry();
if (!geom)
return;
osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(geom->getVertexArray()->getNumElements());
for (unsigned int i=0; i<colors->size(); ++i)
{
float alpha = 1.f;
if (mMeshType == 0) alpha = (i%2) ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row
else if (mMeshType == 1)
{
if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row
else if (i>= 33 && i <= 48) alpha = 0.25098; // second row
else alpha = 1.f;
}
else if (mMeshType == 2)
{
if (geom->getColorArray())
{
osg::Vec4Array* origColors = static_cast<osg::Vec4Array*>(geom->getColorArray());
alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f;
}
else
alpha = 1.f;
}
(*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha);
}
geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
}
private:
int mMeshType;
};
/// @brief Hides the node subgraph if the eye point is below water.
/// @note Must be added as cull callback.
/// @note Meant to be used on a node that is child of a CameraRelativeTransform.
/// The current eye point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space.
class UnderwaterSwitchCallback : public osg::NodeCallback
{
public:
UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform)
: mCameraRelativeTransform(cameraRelativeTransform)
, mEnabled(true)
, mWaterLevel(0.f)
{
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::Vec3f eyePoint = mCameraRelativeTransform->getLastEyePoint();
if (mEnabled && eyePoint.z() < mWaterLevel)
return;
traverse(node, nv);
}
void setEnabled(bool enabled)
{
mEnabled = enabled;
}
void setWaterLevel(float waterLevel)
{
mWaterLevel = waterLevel;
}
private:
osg::ref_ptr<CameraRelativeTransform> mCameraRelativeTransform;
bool mEnabled;
float mWaterLevel;
};
/// A base class for the sun and moons.
class CelestialBody
{
public:
CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask=~0)
: mVisibleMask(visibleMask)
{
mGeom = createTexturedQuad(numUvSets);
mTransform = new osg::PositionAttitudeTransform;
mTransform->setNodeMask(mVisibleMask);
mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor);
mTransform->addChild(mGeom);
parentNode->addChild(mTransform);
}
virtual ~CelestialBody() {}
virtual void adjustTransparency(const float ratio) = 0;
void setVisible(bool visible)
{
mTransform->setNodeMask(visible ? mVisibleMask : 0);
}
protected:
unsigned int mVisibleMask;
static const float mDistance;
osg::ref_ptr<osg::PositionAttitudeTransform> mTransform;
osg::ref_ptr<osg::Geometry> mGeom;
};
const float CelestialBody::mDistance = 1000.0f;
class Sun : public CelestialBody
{
public:
Sun(osg::Group* parentNode, Resource::ImageManager& imageManager)
: CelestialBody(parentNode, 1.0f, 1, Mask_Sun)
, mUpdater(new Updater)
{
mTransform->addUpdateCallback(mUpdater);
osg::ref_ptr<osg::Texture2D> sunTex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds")));
sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON);
osg::ref_ptr<osg::Group> queryNode (new osg::Group);
// Need to render after the world geometry so we can correctly test for occlusions
osg::StateSet* stateset = queryNode->getOrCreateStateSet();
stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin");
stateset->setNestRenderBins(false);
// Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun
osg::ref_ptr<osg::AlphaFunc> alphaFunc (new osg::AlphaFunc);
alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8);
stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON);
stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON);
// Disable writing to the color buffer. We are using this geometry for visibility tests only.
osg::ref_ptr<osg::ColorMask> colormask (new osg::ColorMask(0, 0, 0, 0));
stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON);
osg::ref_ptr<osg::PolygonOffset> po (new osg::PolygonOffset( -1., -1. ));
stateset->setAttributeAndModes(po, osg::StateAttribute::ON);
mTransform->addChild(queryNode);
mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true);
mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false);
createSunFlash(imageManager);
createSunGlare();
}
~Sun()
{
mTransform->removeUpdateCallback(mUpdater);
destroySunFlash();
destroySunGlare();
}
void setColor(const osg::Vec4f& color)
{
mUpdater->mColor.r() = color.r();
mUpdater->mColor.g() = color.g();
mUpdater->mColor.b() = color.b();
}
virtual void adjustTransparency(const float ratio)
{
mUpdater->mColor.a() = ratio;
if (mSunGlareCallback)
mSunGlareCallback->setGlareView(ratio);
if (mSunFlashCallback)
mSunFlashCallback->setGlareView(ratio);
}
void setDirection(const osg::Vec3f& direction)
{
osg::Vec3f normalizedDirection = direction / direction.length();
mTransform->setPosition(normalizedDirection * mDistance);
osg::Quat quat;
quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection);
mTransform->setAttitude(quat);
}
void setGlareTimeOfDayFade(float val)
{
if (mSunGlareCallback)
mSunGlareCallback->setTimeOfDayFade(val);
}
private:
class DummyComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback
{
public:
virtual osg::BoundingSphere computeBound(const osg::Node& node) const { return osg::BoundingSphere(); }
};
/// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels.
osg::ref_ptr<osg::OcclusionQueryNode> createOcclusionQueryNode(osg::Group* parent, bool queryVisible)
{
osg::ref_ptr<osg::OcclusionQueryNode> oqn = new osg::OcclusionQueryNode;
oqn->setQueriesEnabled(true);
// Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry,
// so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry
// is only called once.
// Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal.
oqn->getQueryGeometry()->setDataVariance(osg::Object::STATIC);
// Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this,
// normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to
// circumvent this.
osg::Geometry* queryGeom = oqn->getQueryGeometry();
queryGeom->setVertexArray(mGeom->getVertexArray());
queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX);
queryGeom->removePrimitiveSet(0, oqn->getQueryGeometry()->getNumPrimitiveSets());
queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0));
// Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound.
oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback);
// Still need a proper bounding sphere.
oqn->setInitialBound(queryGeom->getBound());
osg::StateSet* queryStateSet = new osg::StateSet;
if (queryVisible)
{
osg::ref_ptr<osg::Depth> depth (new osg::Depth);
depth->setFunction(osg::Depth::LESS);
// This is a trick to make fragments written by the query always use the maximum depth value,
// without having to retrieve the current far clipping distance.
// We want the sun glare to be "infinitely" far away.
depth->setZNear(1.0);
depth->setZFar(1.0);
depth->setWriteMask(false);
queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON);
}
else
{
queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
}
oqn->setQueryStateSet(queryStateSet);
parent->addChild(oqn);
return oqn;
}
void createSunFlash(Resource::ImageManager& imageManager)
{
osg::ref_ptr<osg::Texture2D> tex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds")));
tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
osg::ref_ptr<osg::PositionAttitudeTransform> transform (new osg::PositionAttitudeTransform);
const float scale = 2.6f;
transform->setScale(osg::Vec3f(scale,scale,scale));
mTransform->addChild(transform);
osg::ref_ptr<osg::Geometry> geom = createTexturedQuad();
transform->addChild(geom);
osg::StateSet* stateset = geom->getOrCreateStateSet();
stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON);
stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin");
stateset->setNestRenderBins(false);
mSunFlashNode = transform;
mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels);
mSunFlashNode->addCullCallback(mSunFlashCallback);
}
void destroySunFlash()
{
if (mSunFlashNode)
{
mSunFlashNode->removeCullCallback(mSunFlashCallback);
mSunFlashCallback = NULL;
}
}
void createSunGlare()
{
osg::ref_ptr<osg::Camera> camera (new osg::Camera);
camera->setProjectionMatrix(osg::Matrix::identity());
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead?
camera->setViewMatrix(osg::Matrix::identity());
camera->setClearMask(0);
camera->setRenderOrder(osg::Camera::NESTED_RENDER);
camera->setAllowEventFocus(false);
osg::ref_ptr<osg::Geometry> geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0));
camera->addChild(geom);
osg::StateSet* stateset = geom->getOrCreateStateSet();
stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin");
stateset->setNestRenderBins(false);
stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
// set up additive blending
osg::ref_ptr<osg::BlendFunc> blendFunc (new osg::BlendFunc);
blendFunc->setSource(osg::BlendFunc::SRC_ALPHA);
blendFunc->setDestination(osg::BlendFunc::ONE);
stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON);
mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform);
mSunGlareNode = camera;
mSunGlareNode->addCullCallback(mSunGlareCallback);
mTransform->addChild(camera);
}
void destroySunGlare()
{
if (mSunGlareNode)
{
mSunGlareNode->removeCullCallback(mSunGlareCallback);
mSunGlareCallback = NULL;
}
}
class Updater : public SceneUtil::StateSetUpdater
{
public:
osg::Vec4f mColor;
Updater()
: mColor(1.f, 1.f, 1.f, 1.f)
{
}
virtual void setDefaults(osg::StateSet* stateset)
{
stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON);
}
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor*)
{
osg::Material* mat = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a()));
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1));
}
};
class OcclusionCallback : public osg::NodeCallback
{
public:
OcclusionCallback(osg::ref_ptr<osg::OcclusionQueryNode> oqnVisible, osg::ref_ptr<osg::OcclusionQueryNode> oqnTotal)
: mOcclusionQueryVisiblePixels(oqnVisible)
, mOcclusionQueryTotalPixels(oqnTotal)
{
}
protected:
float getVisibleRatio (osg::Camera* camera)
{
int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera);
int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera);
float visibleRatio = 0.f;
if (total > 0)
visibleRatio = static_cast<float>(visible) / static_cast<float>(total);
float dt = MWBase::Environment::get().getFrameDuration();
float lastRatio = mLastRatio[osg::observer_ptr<osg::Camera>(camera)];
float change = dt*10;
if (visibleRatio > lastRatio)
visibleRatio = std::min(visibleRatio, lastRatio + change);
else
visibleRatio = std::max(visibleRatio, lastRatio - change);
mLastRatio[osg::observer_ptr<osg::Camera>(camera)] = visibleRatio;
return visibleRatio;
}
private:
osg::ref_ptr<osg::OcclusionQueryNode> mOcclusionQueryVisiblePixels;
osg::ref_ptr<osg::OcclusionQueryNode> mOcclusionQueryTotalPixels;
std::map<osg::observer_ptr<osg::Camera>, float> mLastRatio;
};
/// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback.
class SunFlashCallback : public OcclusionCallback
{
public:
SunFlashCallback(osg::ref_ptr<osg::OcclusionQueryNode> oqnVisible, osg::ref_ptr<osg::OcclusionQueryNode> oqnTotal)
: OcclusionCallback(oqnVisible, oqnTotal)
, mGlareView(1.f)
{
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
float visibleRatio = getVisibleRatio(cv->getCurrentCamera());
osg::ref_ptr<osg::StateSet> stateset;
if (visibleRatio > 0.f)
{
const float fadeThreshold = 0.1;
if (visibleRatio < fadeThreshold)
{
float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold;
osg::ref_ptr<osg::Material> mat (createUnlitMaterial());
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView));
stateset = new osg::StateSet;
stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
}
const float threshold = 0.6;
visibleRatio = visibleRatio * (1.f - threshold) + threshold;
}
float scale = visibleRatio;
if (scale == 0.f)
{
// no traverse
return;
}
else
{
osg::Matrix modelView = *cv->getModelViewMatrix();
modelView.preMultScale(osg::Vec3f(visibleRatio, visibleRatio, visibleRatio));
if (stateset)
cv->pushStateSet(stateset);
cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF);
traverse(node, nv);
cv->popModelViewMatrix();
if (stateset)
cv->popStateSet();
}
}
void setGlareView(float value)
{
mGlareView = value;
}
private:
float mGlareView;
};
/// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera.
/// Must be attached as a cull callback to the node above the glare node.
class SunGlareCallback : public OcclusionCallback
{
public:
SunGlareCallback(osg::ref_ptr<osg::OcclusionQueryNode> oqnVisible, osg::ref_ptr<osg::OcclusionQueryNode> oqnTotal,
osg::ref_ptr<osg::PositionAttitudeTransform> sunTransform)
: OcclusionCallback(oqnVisible, oqnTotal)
, mSunTransform(sunTransform)
, mTimeOfDayFade(1.f)
, mGlareView(1.f)
{
const Fallback::Map* fallback = MWBase::Environment::get().getWorld()->getFallback();
mColor = fallback->getFallbackColour("Weather_Sun_Glare_Fader_Color");
mSunGlareFaderMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Max");
mSunGlareFaderAngleMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Angle_Max");
// Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two,
// then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped,
// so the resulting color looks more orange than red.
mColor *= 2;
for (int i=0; i<3; ++i)
mColor[i] = std::min(1.f, mColor[i]);
}
virtual void operator ()(osg::Node* node, osg::NodeVisitor* nv)
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix());
float visibleRatio = getVisibleRatio(cv->getCurrentCamera());
const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax);
float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians);
float fade = value * mSunGlareFaderMax;
fade *= mTimeOfDayFade * mGlareView * visibleRatio;
if (fade == 0.f)
{
// no traverse
return;
}
else
{
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
osg::ref_ptr<osg::Material> mat (createUnlitMaterial());
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade));
mat->setEmission(osg::Material::FRONT_AND_BACK, mColor);
stateset->setAttributeAndModes(mat, osg::StateAttribute::ON);
cv->pushStateSet(stateset);
traverse(node, nv);
cv->popStateSet();
}
}
void setTimeOfDayFade(float val)
{
mTimeOfDayFade = val;
}
void setGlareView(float glareView)
{
mGlareView = glareView;
}
private:
float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const
{
osg::Vec3d eye, center, up;
viewMatrix.getLookAt(eye, center, up);
osg::Vec3d forward = center - eye;
osg::Vec3d sun = mSunTransform->getPosition();
forward.normalize();
sun.normalize();
float angleRadians = std::acos(forward * sun);
return angleRadians;
}
osg::ref_ptr<osg::PositionAttitudeTransform> mSunTransform;
float mTimeOfDayFade;
float mGlareView;
osg::Vec4f mColor;
float mSunGlareFaderMax;
float mSunGlareFaderAngleMax;
};
osg::ref_ptr<Updater> mUpdater;
osg::ref_ptr<SunFlashCallback> mSunFlashCallback;
osg::ref_ptr<osg::Node> mSunFlashNode;
osg::ref_ptr<SunGlareCallback> mSunGlareCallback;
osg::ref_ptr<osg::Node> mSunGlareNode;
osg::ref_ptr<osg::OcclusionQueryNode> mOcclusionQueryVisiblePixels;
osg::ref_ptr<osg::OcclusionQueryNode> mOcclusionQueryTotalPixels;
};
class Moon : public CelestialBody
{
public:
enum Type
{
Type_Masser = 0,
Type_Secunda
};
Moon(osg::Group* parentNode, Resource::ImageManager& imageManager, float scaleFactor, Type type)
: CelestialBody(parentNode, scaleFactor, 2)
, mType(type)
, mPhase(MoonState::Phase_Unspecified)
, mUpdater(new Updater(imageManager))
{
setPhase(MoonState::Phase_Full);
setVisible(true);
mGeom->addUpdateCallback(mUpdater);
}
~Moon()
{
mGeom->removeUpdateCallback(mUpdater);
}
virtual void adjustTransparency(const float ratio)
{
mUpdater->mTransparency *= ratio;
}
void setState(const MoonState& state)
{
float radsX = ((state.mRotationFromHorizon) * static_cast<float>(osg::PI)) / 180.0f;
float radsZ = ((state.mRotationFromNorth) * static_cast<float>(osg::PI)) / 180.0f;
osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f));
osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f));
osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f);
mTransform->setPosition(direction * mDistance);
// The moon quad is initially oriented facing down, so we need to offset its X-axis
// rotation to rotate it to face the camera when sitting at the horizon.
osg::Quat attX((-static_cast<float>(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f));
mTransform->setAttitude(attX * rotZ);
setPhase(state.mPhase);
mUpdater->mTransparency = state.mMoonAlpha;
mUpdater->mShadowBlend = state.mShadowBlend;
}
void setAtmosphereColor(const osg::Vec4f& color)
{
mUpdater->mAtmosphereColor = color;
}
void setColor(const osg::Vec4f& color)
{
mUpdater->mMoonColor = color;
}
unsigned int getPhaseInt() const
{
if (mPhase == MoonState::Phase_New) return 0;
else if (mPhase == MoonState::Phase_WaxingCrescent) return 1;
else if (mPhase == MoonState::Phase_WaningCrescent) return 1;
else if (mPhase == MoonState::Phase_FirstQuarter) return 2;
else if (mPhase == MoonState::Phase_ThirdQuarter) return 2;
else if (mPhase == MoonState::Phase_WaxingGibbous) return 3;
else if (mPhase == MoonState::Phase_WaningGibbous) return 3;
else if (mPhase == MoonState::Phase_Full) return 4;
return 0;
}
private:
struct Updater : public SceneUtil::StateSetUpdater
{
Resource::ImageManager& mImageManager;
osg::ref_ptr<osg::Texture2D> mPhaseTex;
osg::ref_ptr<osg::Texture2D> mCircleTex;
float mTransparency;
float mShadowBlend;
osg::Vec4f mAtmosphereColor;
osg::Vec4f mMoonColor;
Updater(Resource::ImageManager& imageManager)
: mImageManager(imageManager)
, mPhaseTex()
, mCircleTex()
, mTransparency(1.0f)
, mShadowBlend(1.0f)
, mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f)
, mMoonColor(1.0f, 1.0f, 1.0f, 1.0f)
{
}
virtual void setDefaults(osg::StateSet* stateset)
{
stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON);
osg::ref_ptr<osg::TexEnvCombine> texEnv = new osg::TexEnvCombine;
texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE);
texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT);
texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE);
texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor
stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON);
osg::ref_ptr<osg::TexEnvCombine> texEnv2 = new osg::TexEnvCombine;
texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD);
texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE);
texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE);
texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT);
texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT);
texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency
stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON);
stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
}
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor*)
{
osg::TexEnvCombine* texEnv = static_cast<osg::TexEnvCombine*>(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV));
texEnv->setConstantColor(mMoonColor * mShadowBlend);
osg::TexEnvCombine* texEnv2 = static_cast<osg::TexEnvCombine*>(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV));
texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency));
}
void setTextures(const std::string& phaseTex, const std::string& circleTex)
{
mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex));
mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex));
mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
reset();
}
};
Type mType;
MoonState::Phase mPhase;
osg::ref_ptr<Updater> mUpdater;
void setPhase(const MoonState::Phase& phase)
{
if(mPhase == phase)
return;
mPhase = phase;
std::string textureName = "textures/tx_";
if (mType == Moon::Type_Secunda)
textureName += "secunda_";
else
textureName += "masser_";
if (phase == MoonState::Phase_New) textureName += "new";
else if(phase == MoonState::Phase_WaxingCrescent) textureName += "one_wax";
else if(phase == MoonState::Phase_FirstQuarter) textureName += "half_wax";
else if(phase == MoonState::Phase_WaxingGibbous) textureName += "three_wax";
else if(phase == MoonState::Phase_WaningCrescent) textureName += "one_wan";
else if(phase == MoonState::Phase_ThirdQuarter) textureName += "half_wan";
else if(phase == MoonState::Phase_WaningGibbous) textureName += "three_wan";
else if(phase == MoonState::Phase_Full) textureName += "full";
textureName += ".dds";
if (mType == Moon::Type_Secunda)
mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds");
else
mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds");
}
};
SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager)
: mSceneManager(sceneManager)
, mCamera(NULL)
, mRainIntensityUniform(NULL)
, mAtmosphereNightRoll(0.f)
, mCreated(false)
, mIsStorm(false)
, mDay(0)
, mMonth(0)
, mCloudAnimationTimer(0.f)
, mRainTimer(0.f)
, mStormDirection(0,-1,0)
, mClouds()
, mNextClouds()
, mCloudBlendFactor(0.0f)
, mCloudSpeed(0.0f)
, mStarsOpacity(0.0f)
, mRemainingTransitionTime(0.0f)
, mRainEnabled(false)
, mRainSpeed(0)
, mRainFrequency(1)
, mWindSpeed(0.f)
, mEnabled(true)
, mSunEnabled(true)
{
osg::ref_ptr<CameraRelativeTransform> skyroot (new CameraRelativeTransform);
skyroot->setName("Sky Root");
// Assign empty program to specify we don't want shaders
// The shaders generated by the SceneManager can't handle everything we need
skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE);
skyroot->setNodeMask(Mask_Sky);
parentNode->addChild(skyroot);
mRootNode = skyroot;
mEarlyRenderBinRoot = new osg::Group;
// render before the world is rendered
mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin");
// Prevent unwanted clipping by water reflection camera's clipping plane
mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF);
mRootNode->addChild(mEarlyRenderBinRoot);
mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot);
}
void SkyManager::setRainIntensityUniform(osg::Uniform *uniform)
{
mRainIntensityUniform = uniform;
}
void SkyManager::create()
{
assert(!mCreated);
mAtmosphereDay = mSceneManager->getInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot);
ModVertexAlphaVisitor modAtmosphere(0);
mAtmosphereDay->accept(modAtmosphere);
mAtmosphereUpdater = new AtmosphereUpdater;
mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater);
mAtmosphereNightNode = new osg::PositionAttitudeTransform;
mAtmosphereNightNode->setNodeMask(0);
mEarlyRenderBinRoot->addChild(mAtmosphereNightNode);
osg::ref_ptr<osg::Node> atmosphereNight;
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_02.nif", mAtmosphereNightNode);
else
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_01.nif", mAtmosphereNightNode);
atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
ModVertexAlphaVisitor modStars(2);
atmosphereNight->accept(modStars);
mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager());
atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater);
mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager()));
const Fallback::Map* fallback=MWBase::Environment::get().getWorld()->getFallback();
mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser));
mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda));
mCloudNode = new osg::PositionAttitudeTransform;
mEarlyRenderBinRoot->addChild(mCloudNode);
mCloudMesh = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
ModVertexAlphaVisitor modClouds(1);
mCloudMesh->accept(modClouds);
mCloudUpdater = new CloudUpdater;
mCloudUpdater->setOpacity(1.f);
mCloudMesh->addUpdateCallback(mCloudUpdater);
mCloudMesh2 = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
mCloudMesh2->accept(modClouds);
mCloudUpdater2 = new CloudUpdater;
mCloudUpdater2->setOpacity(0.f);
mCloudMesh2->addUpdateCallback(mCloudUpdater2);
mCloudMesh2->setNodeMask(0);
osg::ref_ptr<osg::Depth> depth = new osg::Depth;
depth->setWriteMask(false);
mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON);
mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON);
mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF);
mMoonScriptColor = fallback->getFallbackColour("Moons_Script_Color");
mCreated = true;
}
class RainCounter : public osgParticle::ConstantRateCounter
{
public:
virtual int numParticlesToCreate(double dt) const
{
// limit dt to avoid large particle emissions if there are jumps in the simulation time
// 0.2 seconds is the same cap as used in Engine's frame loop
dt = std::min(dt, 0.2);
return ConstantRateCounter::numParticlesToCreate(dt);
}
};
class RainShooter : public osgParticle::Shooter
{
public:
RainShooter()
: mAngle(0.f)
{
}
virtual void shoot(osgParticle::Particle* particle) const
{
particle->setVelocity(mVelocity);
particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI));
}
void setVelocity(const osg::Vec3f& velocity)
{
mVelocity = velocity;
}
void setAngle(float angle)
{
mAngle = angle;
}
virtual osg::Object* cloneType() const
{
return new RainShooter;
}
virtual osg::Object* clone(const osg::CopyOp &) const
{
return new RainShooter(*this);
}
private:
osg::Vec3f mVelocity;
float mAngle;
};
// Updater for alpha value on a node's StateSet. Assumes the node has an existing Material StateAttribute.
class AlphaFader : public SceneUtil::StateSetUpdater
{
public:
/// @param alphaUpdate variable which to update with alpha value
AlphaFader(float *alphaUpdate)
: mAlpha(1.f)
{
mAlphaUpdate = alphaUpdate;
}
void setAlpha(float alpha)
{
mAlpha = alpha;
}
virtual void setDefaults(osg::StateSet* stateset)
{
// need to create a deep copy of StateAttributes we will modify
osg::Material* mat = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON);
}
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv)
{
osg::Material* mat = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mAlpha));
if (mAlphaUpdate)
*mAlphaUpdate = mAlpha;
}
// Helper for adding AlphaFaders to a subgraph
class SetupVisitor : public osg::NodeVisitor
{
public:
SetupVisitor(float *alphaUpdate)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
{
mAlphaUpdate = alphaUpdate;
}
virtual void apply(osg::Node &node)
{
if (osg::StateSet* stateset = node.getStateSet())
{
if (stateset->getAttribute(osg::StateAttribute::MATERIAL))
{
SceneUtil::CompositeStateSetUpdater* composite = NULL;
osg::Callback* callback = node.getUpdateCallback();
while (callback)
{
if ((composite = dynamic_cast<SceneUtil::CompositeStateSetUpdater*>(callback)))
break;
callback = callback->getNestedCallback();
}
osg::ref_ptr<AlphaFader> alphaFader (new AlphaFader(mAlphaUpdate));
if (composite)
composite->addController(alphaFader);
else
node.addUpdateCallback(alphaFader);
mAlphaFaders.push_back(alphaFader);
}
}
traverse(node);
}
std::vector<osg::ref_ptr<AlphaFader> > getAlphaFaders()
{
return mAlphaFaders;
}
private:
std::vector<osg::ref_ptr<AlphaFader> > mAlphaFaders;
float *mAlphaUpdate;
};
protected:
float mAlpha;
float *mAlphaUpdate;
};
class RainFader : public AlphaFader
{
public:
RainFader(float *alphaUpdate): AlphaFader(alphaUpdate)
{
}
virtual void setDefaults(osg::StateSet* stateset)
{
osg::ref_ptr<osg::Material> mat (new osg::Material);
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1));
mat->setColorMode(osg::Material::OFF);
stateset->setAttributeAndModes(mat, osg::StateAttribute::ON);
}
virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
{
AlphaFader::apply(stateset,nv);
*mAlphaUpdate = mAlpha * 2.0; // mAlpha is limited to 0.6 so multiply by 2 to reach full intensity
}
};
void SkyManager::setCamera(osg::Camera *camera)
{
mCamera = camera;
}
class WrapAroundOperator : public osgParticle::Operator
{
public:
WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange): osgParticle::Operator()
{
mCamera = camera;
mWrapRange = wrapRange;
mHalfWrapRange = mWrapRange / 2.0;
mPreviousCameraPosition = getCameraPosition();
}
virtual osg::Object *cloneType() const override
{
return NULL;
}
virtual osg::Object *clone(const osg::CopyOp &op) const override
{
return NULL;
}
virtual void operate(osgParticle::Particle *P, double dt) override
{
}
virtual void operateParticles(osgParticle::ParticleSystem *ps, double dt) override
{
osg::Vec3 position = getCameraPosition();
osg::Vec3 positionDifference = position - mPreviousCameraPosition;
osg::Matrix toWorld, toLocal;
std::vector<osg::Matrix> worldMatrices = ps->getWorldMatrices();
if (!worldMatrices.empty())
{
toWorld = worldMatrices[0];
toLocal.invert(toWorld);
}
for (int i = 0; i < ps->numParticles(); ++i)
{
osgParticle::Particle *p = ps->getParticle(i);
p->setPosition(toWorld.preMult(p->getPosition()));
p->setPosition(p->getPosition() - positionDifference);
for (int j = 0; j < 3; ++j) // wrap-around in all 3 dimensions
{
osg::Vec3 pos = p->getPosition();
if (pos[j] < -mHalfWrapRange[j])
pos[j] = mHalfWrapRange[j] + fmod(pos[j] - mHalfWrapRange[j],mWrapRange[j]);
else if (pos[j] > mHalfWrapRange[j])
pos[j] = fmod(pos[j] + mHalfWrapRange[j],mWrapRange[j]) - mHalfWrapRange[j];
p->setPosition(pos);
}
p->setPosition(toLocal.preMult(p->getPosition()));
}
mPreviousCameraPosition = position;
}
protected:
osg::Camera *mCamera;
osg::Vec3 mPreviousCameraPosition;
osg::Vec3 mWrapRange;
osg::Vec3 mHalfWrapRange;
osg::Vec3 getCameraPosition()
{
return mCamera->getInverseViewMatrix().getTrans();
}
};
void SkyManager::createRain()
{
if (mRainNode)
return;
mRainNode = new osg::Group;
mRainParticleSystem = new osgParticle::ParticleSystem;
osg::Vec3 rainRange = osg::Vec3(600,600,600);
mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED);
mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0));
mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,1));
osg::ref_ptr<osg::StateSet> stateset (mRainParticleSystem->getOrCreateStateSet());
osg::ref_ptr<osg::Texture2D> raindropTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds")));
raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON);
stateset->setNestRenderBins(false);
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate();
particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f));
particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f));
particleTemplate.setLifeTime(1);
osg::ref_ptr<osgParticle::ModularEmitter> emitter (new osgParticle::ModularEmitter);
emitter->setParticleSystem(mRainParticleSystem);
osg::ref_ptr<osgParticle::BoxPlacer> placer (new osgParticle::BoxPlacer);
placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); // Rain_Diameter
placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2);
placer->setZRange(300, 300);
emitter->setPlacer(placer);
osg::ref_ptr<RainCounter> counter (new RainCounter);
counter->setNumberOfParticlesPerSecondToCreate(600.0);
emitter->setCounter(counter);
osg::ref_ptr<RainShooter> shooter (new RainShooter);
mRainShooter = shooter;
emitter->setShooter(shooter);
osg::ref_ptr<osgParticle::ParticleSystemUpdater> updater (new osgParticle::ParticleSystemUpdater);
updater->addParticleSystem(mRainParticleSystem);
osg::ref_ptr<osgParticle::ModularProgram> program (new osgParticle::ModularProgram);
program->addOperator(new WrapAroundOperator(mCamera,rainRange));
program->setParticleSystem(mRainParticleSystem);
mRainNode->addChild(program);
mRainNode->addChild(emitter);
mRainNode->addChild(mRainParticleSystem);
mRainNode->addChild(updater);
mRainFader = new RainFader(&mWeatherAlpha);
mRainNode->addUpdateCallback(mRainFader);
mRainNode->addCullCallback(mUnderwaterSwitch);
mRainNode->setNodeMask(Mask_WeatherParticles);
mRootNode->addChild(mRainNode);
}
void SkyManager::destroyRain()
{
if (!mRainNode)
return;
mRootNode->removeChild(mRainNode);
mRainNode = NULL;
mRainParticleSystem = NULL;
mRainShooter = NULL;
mRainFader = NULL;
}
SkyManager::~SkyManager()
{
if (mRootNode)
{
mRootNode->getParent(0)->removeChild(mRootNode);
mRootNode = NULL;
}
}
int SkyManager::getMasserPhase() const
{
if (!mCreated) return 0;
return mMasser->getPhaseInt();
}
int SkyManager::getSecundaPhase() const
{
if (!mCreated) return 0;
return mSecunda->getPhaseInt();
}
bool SkyManager::isEnabled()
{
return mEnabled;
}
bool SkyManager::hasRain()
{
return mRainNode != NULL;
}
void SkyManager::update(float duration)
{
if (!mEnabled)
{
if (mRainIntensityUniform)
mRainIntensityUniform->set((float) 0.0);
return;
}
if (mRainIntensityUniform)
{
if (mIsStorm || (!hasRain() && !mParticleNode))
mRainIntensityUniform->set((float) 0.0);
else
mRainIntensityUniform->set((float) mWeatherAlpha);
}
if (mIsStorm)
{
osg::Quat quat;
quat.makeRotate(osg::Vec3f(0,1,0), mStormDirection);
if (mParticleNode)
mParticleNode->setAttitude(quat);
mCloudNode->setAttitude(quat);
}
else
mCloudNode->setAttitude(osg::Quat());
// UV Scroll the clouds
mCloudAnimationTimer += duration * mCloudSpeed * 0.003;
mCloudUpdater->setAnimationTimer(mCloudAnimationTimer);
mCloudUpdater2->setAnimationTimer(mCloudAnimationTimer);
// rotate the stars by 360 degrees every 4 days
mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*osg::DegreesToRadians(360.f) / (3600*96.f);
if (mAtmosphereNightNode->getNodeMask() != 0)
mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0,0,1)));
}
void SkyManager::setEnabled(bool enabled)
{
if (enabled && !mCreated)
create();
mRootNode->setNodeMask(enabled ? Mask_Sky : 0);
mEnabled = enabled;
}
void SkyManager::setMoonColour (bool red)
{
if (!mCreated) return;
mSecunda->setColor(red ? mMoonScriptColor : osg::Vec4f(1,1,1,1));
}
void SkyManager::updateRainParameters()
{
if (mRainShooter)
{
float windFactor = mWindSpeed/3.f;
float angle = windFactor * osg::PI/4;
mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed * windFactor, -mRainSpeed));
mRainShooter->setAngle(angle);
}
}
void SkyManager::setWeather(const WeatherResult& weather)
{
if (!mCreated) return;
if (mRainEffect != weather.mRainEffect)
{
mRainEffect = weather.mRainEffect;
if (!mRainEffect.empty())
{
createRain();
}
else
{
destroyRain();
}
}
mRainFrequency = weather.mRainFrequency;
mRainSpeed = weather.mRainSpeed;
mWindSpeed = weather.mWindSpeed;
updateRainParameters();
mIsStorm = weather.mIsStorm;
if (mCurrentParticleEffect != weather.mParticleEffect)
{
mCurrentParticleEffect = weather.mParticleEffect;
// cleanup old particles
if (mParticleEffect)
{
mParticleNode->removeChild(mParticleEffect);
mParticleEffect = NULL;
mParticleFaders.clear();
}
if (mCurrentParticleEffect.empty())
{
if (mParticleNode)
{
mRootNode->removeChild(mParticleNode);
mParticleNode = NULL;
}
}
else
{
if (!mParticleNode)
{
mParticleNode = new osg::PositionAttitudeTransform;
mParticleNode->addCullCallback(mUnderwaterSwitch);
mParticleNode->setNodeMask(Mask_WeatherParticles);
mRootNode->addChild(mParticleNode);
}
mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode);
SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr<SceneUtil::ControllerSource>(new SceneUtil::FrameTimeSource));
mParticleEffect->accept(assignVisitor);
AlphaFader::SetupVisitor alphaFaderSetupVisitor(&mWeatherAlpha);
mParticleEffect->accept(alphaFaderSetupVisitor);
mParticleFaders = alphaFaderSetupVisitor.getAlphaFaders();
SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor;
mParticleEffect->accept(disableFreezeOnCullVisitor);
if (!weather.mIsStorm)
{
SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem"));
mParticleEffect->accept(findPSVisitor);
for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i)
{
osgParticle::ParticleSystem *ps = static_cast<osgParticle::ParticleSystem *>(findPSVisitor.mFoundNodes[i]);
osg::ref_ptr<osgParticle::ModularProgram> program (new osgParticle::ModularProgram);
program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800)));
program->setParticleSystem(ps);
mParticleNode->addChild(program);
}
}
}
}
if (mClouds != weather.mCloudTexture)
{
mClouds = weather.mCloudTexture;
std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS());
osg::ref_ptr<osg::Texture2D> cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)));
cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
mCloudUpdater->setTexture(cloudTex);
}
if (mNextClouds != weather.mNextCloudTexture)
{
mNextClouds = weather.mNextCloudTexture;
if (!mNextClouds.empty())
{
std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS());
osg::ref_ptr<osg::Texture2D> cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)));
cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
mCloudUpdater2->setTexture(cloudTex);
}
}
if (mCloudBlendFactor != weather.mCloudBlendFactor)
{
mCloudBlendFactor = weather.mCloudBlendFactor;
mCloudUpdater->setOpacity((1.f-mCloudBlendFactor));
mCloudUpdater2->setOpacity(mCloudBlendFactor);
mCloudMesh2->setNodeMask(mCloudBlendFactor > 0.f ? ~0 : 0);
}
if (mCloudColour != weather.mFogColor)
{
osg::Vec4f clr (weather.mFogColor);
clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f);
mCloudUpdater->setEmissionColor(clr);
mCloudUpdater2->setEmissionColor(clr);
mCloudColour = weather.mFogColor;
}
if (mSkyColour != weather.mSkyColor)
{
mSkyColour = weather.mSkyColor;
mAtmosphereUpdater->setEmissionColor(mSkyColour);
mMasser->setAtmosphereColor(mSkyColour);
mSecunda->setAtmosphereColor(mSkyColour);
}
if (mFogColour != weather.mFogColor)
{
mFogColour = weather.mFogColor;
}
mCloudSpeed = weather.mCloudSpeed;
mMasser->adjustTransparency(weather.mGlareView);
mSecunda->adjustTransparency(weather.mGlareView);
mSun->setColor(weather.mSunDiscColor);
mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a());
float nextStarsOpacity = weather.mNightFade * weather.mGlareView;
if (weather.mNight && mStarsOpacity != nextStarsOpacity)
{
mStarsOpacity = nextStarsOpacity;
mAtmosphereNightUpdater->setFade(mStarsOpacity);
}
mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0 : 0);
if (mRainFader)
mRainFader->setAlpha(weather.mEffectFade * 0.6); // * Rain_Threshold?
for (std::vector<osg::ref_ptr<AlphaFader> >::const_iterator it = mParticleFaders.begin(); it != mParticleFaders.end(); ++it)
(*it)->setAlpha(weather.mEffectFade);
}
void SkyManager::sunEnable()
{
if (!mCreated) return;
mSun->setVisible(true);
}
void SkyManager::sunDisable()
{
if (!mCreated) return;
mSun->setVisible(false);
}
void SkyManager::setStormDirection(const osg::Vec3f &direction)
{
mStormDirection = direction;
}
void SkyManager::setSunDirection(const osg::Vec3f& direction)
{
if (!mCreated) return;
mSun->setDirection(direction);
}
void SkyManager::setMasserState(const MoonState& state)
{
if(!mCreated) return;
mMasser->setState(state);
}
void SkyManager::setSecundaState(const MoonState& state)
{
if(!mCreated) return;
mSecunda->setState(state);
}
void SkyManager::setDate(int day, int month)
{
mDay = day;
mMonth = month;
}
void SkyManager::setGlareTimeOfDayFade(float val)
{
mSun->setGlareTimeOfDayFade(val);
}
void SkyManager::setWaterHeight(float height)
{
mUnderwaterSwitch->setWaterLevel(height);
}
void SkyManager::listAssetsToPreload(std::vector<std::string>& models, std::vector<std::string>& textures)
{
models.push_back("meshes/sky_atmosphere.nif");
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
models.push_back("meshes/sky_night_02.nif");
models.push_back("meshes/sky_night_01.nif");
models.push_back("meshes/sky_clouds_01.nif");
models.push_back("meshes\\ashcloud.nif");
models.push_back("meshes\\blightcloud.nif");
models.push_back("meshes\\snow.nif");
models.push_back("meshes\\blizzard.nif");
textures.push_back("textures/tx_mooncircle_full_s.dds");
textures.push_back("textures/tx_mooncircle_full_m.dds");
textures.push_back("textures/tx_masser_new.dds");
textures.push_back("textures/tx_masser_one_wax.dds");
textures.push_back("textures/tx_masser_half_wax.dds");
textures.push_back("textures/tx_masser_three_wax.dds");
textures.push_back("textures/tx_masser_one_wan.dds");
textures.push_back("textures/tx_masser_half_wan.dds");
textures.push_back("textures/tx_masser_three_wan.dds");
textures.push_back("textures/tx_masser_full.dds");
textures.push_back("textures/tx_secunda_new.dds");
textures.push_back("textures/tx_secunda_one_wax.dds");
textures.push_back("textures/tx_secunda_half_wax.dds");
textures.push_back("textures/tx_secunda_three_wax.dds");
textures.push_back("textures/tx_secunda_one_wan.dds");
textures.push_back("textures/tx_secunda_half_wan.dds");
textures.push_back("textures/tx_secunda_three_wan.dds");
textures.push_back("textures/tx_secunda_full.dds");
textures.push_back("textures/tx_sun_05.dds");
textures.push_back("textures/tx_sun_flash_grey_05.dds");
textures.push_back("textures/tx_raindrop_01.dds");
}
void SkyManager::setWaterEnabled(bool enabled)
{
mUnderwaterSwitch->setEnabled(enabled);
}
}