Add OpenMW commits up to 4 Feb 2021
# Conflicts: # apps/openmw/engine.cpp # apps/openmw/mwmechanics/npcstats.hpp # apps/openmw/mwrender/globalmap.cpppull/593/head
commit
e1259fdc41
@ -0,0 +1,18 @@
|
||||
#ifndef CSV_WIDGET_INSTANCEDRAGMODES_H
|
||||
#define CSV_WIDGET_INSTANCEDRAGMODES_H
|
||||
|
||||
namespace CSVRender
|
||||
{
|
||||
enum DragMode
|
||||
{
|
||||
DragMode_None,
|
||||
DragMode_Move,
|
||||
DragMode_Rotate,
|
||||
DragMode_Scale,
|
||||
DragMode_Select_Only,
|
||||
DragMode_Select_Add,
|
||||
DragMode_Select_Remove,
|
||||
DragMode_Select_Invert
|
||||
};
|
||||
}
|
||||
#endif
|
@ -0,0 +1,69 @@
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "actor.hpp"
|
||||
#include "collisiontype.hpp"
|
||||
#include "projectile.hpp"
|
||||
#include "projectileconvexcallback.hpp"
|
||||
#include "ptrholder.hpp"
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj)
|
||||
: btCollisionWorld::ClosestConvexResultCallback(from, to)
|
||||
, mMe(me), mProjectile(proj)
|
||||
{
|
||||
assert(mProjectile);
|
||||
}
|
||||
|
||||
btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace)
|
||||
{
|
||||
// don't hit the caster
|
||||
if (result.m_hitCollisionObject == mMe)
|
||||
return 1.f;
|
||||
|
||||
// don't hit the projectile
|
||||
if (result.m_hitCollisionObject == mProjectile->getCollisionObject())
|
||||
return 1.f;
|
||||
|
||||
btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace);
|
||||
switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup)
|
||||
{
|
||||
case CollisionType_Actor:
|
||||
{
|
||||
auto* target = static_cast<Actor*>(result.m_hitCollisionObject->getUserPointer());
|
||||
if (!mProjectile->isValidTarget(target->getPtr()))
|
||||
return 1.f;
|
||||
mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal);
|
||||
break;
|
||||
}
|
||||
case CollisionType_Projectile:
|
||||
{
|
||||
auto* target = static_cast<Projectile*>(result.m_hitCollisionObject->getUserPointer());
|
||||
if (!mProjectile->isValidTarget(target->getCaster()))
|
||||
return 1.f;
|
||||
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||
break;
|
||||
}
|
||||
case CollisionType_Water:
|
||||
{
|
||||
mProjectile->setWaterHitPosition(m_hitPointWorld);
|
||||
if (mProjectile->canTraverseWater())
|
||||
return 1.f;
|
||||
mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
auto* target = static_cast<PtrHolder*>(result.m_hitCollisionObject->getUserPointer());
|
||||
auto ptr = target ? target->getPtr() : MWWorld::Ptr();
|
||||
mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result.m_hitFraction;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
#ifndef OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H
|
||||
#define OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
class btCollisionObject;
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class Projectile;
|
||||
|
||||
class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback
|
||||
{
|
||||
public:
|
||||
ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj);
|
||||
|
||||
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override;
|
||||
|
||||
private:
|
||||
const btCollisionObject* mMe;
|
||||
Projectile* mProjectile;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,282 @@
|
||||
#include "groundcover.hpp"
|
||||
|
||||
#include <osg/Geometry>
|
||||
#include <osg/VertexAttribDivisor>
|
||||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
|
||||
#include "apps/openmw/mwworld/esmstore.hpp"
|
||||
#include "apps/openmw/mwbase/environment.hpp"
|
||||
#include "apps/openmw/mwbase/world.hpp"
|
||||
|
||||
#include "vismask.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ESM::REC_STAT:
|
||||
return store.get<ESM::Static>().searchStatic(id)->mModel;
|
||||
default:
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::setWindSpeed(float windSpeed)
|
||||
{
|
||||
mWindSpeed = windSpeed;
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos)
|
||||
{
|
||||
mPlayerPos = playerPos;
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::setDefaults(osg::StateSet *stateset)
|
||||
{
|
||||
osg::ref_ptr<osg::Uniform> windUniform = new osg::Uniform("windSpeed", 0.0f);
|
||||
stateset->addUniform(windUniform.get());
|
||||
|
||||
osg::ref_ptr<osg::Uniform> playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f));
|
||||
stateset->addUniform(playerPosUniform.get());
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
|
||||
{
|
||||
osg::ref_ptr<osg::Uniform> windUniform = stateset->getUniform("windSpeed");
|
||||
if (windUniform != nullptr)
|
||||
windUniform->set(mWindSpeed);
|
||||
|
||||
osg::ref_ptr<osg::Uniform> playerPosUniform = stateset->getUniform("playerPos");
|
||||
if (playerPosUniform != nullptr)
|
||||
playerPosUniform->set(mPlayerPos);
|
||||
}
|
||||
|
||||
class InstancingVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
InstancingVisitor(std::vector<Groundcover::GroundcoverEntry>& instances, osg::Vec3f& chunkPosition)
|
||||
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||
, mInstances(instances)
|
||||
, mChunkPosition(chunkPosition)
|
||||
{
|
||||
}
|
||||
|
||||
void apply(osg::Node& node) override
|
||||
{
|
||||
osg::ref_ptr<osg::StateSet> ss = node.getStateSet();
|
||||
if (ss != nullptr)
|
||||
{
|
||||
ss->removeAttribute(osg::StateAttribute::MATERIAL);
|
||||
removeAlpha(ss);
|
||||
}
|
||||
|
||||
traverse(node);
|
||||
}
|
||||
|
||||
void apply(osg::Geometry& geom) override
|
||||
{
|
||||
for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i)
|
||||
{
|
||||
geom.getPrimitiveSet(i)->setNumInstances(mInstances.size());
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Vec4Array> transforms = new osg::Vec4Array(mInstances.size());
|
||||
osg::BoundingBox box;
|
||||
float radius = geom.getBoundingBox().radius();
|
||||
for (unsigned int i = 0; i < transforms->getNumElements(); i++)
|
||||
{
|
||||
osg::Vec3f pos(mInstances[i].mPos.asVec3());
|
||||
osg::Vec3f relativePos = pos - mChunkPosition;
|
||||
(*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale);
|
||||
|
||||
// Use an additional margin due to groundcover animation
|
||||
float instanceRadius = radius * mInstances[i].mScale * 1.1f;
|
||||
osg::BoundingSphere instanceBounds(relativePos, instanceRadius);
|
||||
box.expandBy(instanceBounds);
|
||||
}
|
||||
|
||||
geom.setInitialBound(box);
|
||||
|
||||
osg::ref_ptr<osg::Vec3Array> rotations = new osg::Vec3Array(mInstances.size());
|
||||
for (unsigned int i = 0; i < rotations->getNumElements(); i++)
|
||||
{
|
||||
(*rotations)[i] = mInstances[i].mPos.asRotationVec3();
|
||||
}
|
||||
|
||||
// Display lists do not support instancing in OSG 3.4
|
||||
geom.setUseDisplayList(false);
|
||||
|
||||
geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX);
|
||||
geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX);
|
||||
|
||||
osg::ref_ptr<osg::StateSet> ss = geom.getOrCreateStateSet();
|
||||
ss->setAttribute(new osg::VertexAttribDivisor(6, 1));
|
||||
ss->setAttribute(new osg::VertexAttribDivisor(7, 1));
|
||||
|
||||
ss->removeAttribute(osg::StateAttribute::MATERIAL);
|
||||
removeAlpha(ss);
|
||||
|
||||
traverse(geom);
|
||||
}
|
||||
private:
|
||||
std::vector<Groundcover::GroundcoverEntry> mInstances;
|
||||
osg::Vec3f mChunkPosition;
|
||||
|
||||
void removeAlpha(osg::StateSet* stateset)
|
||||
{
|
||||
// MGE uses default alpha settings for groundcover, so we can not rely on alpha properties
|
||||
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
|
||||
stateset->removeMode(GL_ALPHA_TEST);
|
||||
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
|
||||
stateset->removeMode(GL_BLEND);
|
||||
stateset->setRenderBinToInherit();
|
||||
}
|
||||
};
|
||||
|
||||
class DensityCalculator
|
||||
{
|
||||
public:
|
||||
DensityCalculator(float density)
|
||||
: mDensity(density)
|
||||
{
|
||||
}
|
||||
|
||||
bool isInstanceEnabled()
|
||||
{
|
||||
if (mDensity >= 1.f) return true;
|
||||
|
||||
mCurrentGroundcover += mDensity;
|
||||
if (mCurrentGroundcover < 1.f) return false;
|
||||
|
||||
mCurrentGroundcover -= 1.f;
|
||||
|
||||
return true;
|
||||
}
|
||||
void reset() { mCurrentGroundcover = 0.f; }
|
||||
|
||||
private:
|
||||
float mCurrentGroundcover = 0.f;
|
||||
float mDensity = 0.f;
|
||||
};
|
||||
|
||||
inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound)
|
||||
{
|
||||
osg::Vec2f size = maxBound - minBound;
|
||||
if (size.x() >=1 && size.y() >=1) return true;
|
||||
|
||||
osg::Vec3f pos = ref.mPos.asVec3();
|
||||
osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE;
|
||||
if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y())
|
||||
|| (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
{
|
||||
ChunkId id = std::make_tuple(center, size, activeGrid);
|
||||
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
|
||||
if (obj)
|
||||
return obj->asNode();
|
||||
else
|
||||
{
|
||||
InstanceMap instances;
|
||||
collectInstances(instances, size, center);
|
||||
osg::ref_ptr<osg::Node> node = createChunk(instances, center);
|
||||
mCache->addEntryToObjectCache(id, node.get());
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density)
|
||||
: GenericResourceManager<ChunkId>(nullptr)
|
||||
, mSceneManager(sceneManager)
|
||||
, mDensity(density)
|
||||
{
|
||||
}
|
||||
|
||||
void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center)
|
||||
{
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f));
|
||||
osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f));
|
||||
DensityCalculator calculator(mDensity);
|
||||
std::vector<ESM::ESMReader> esm;
|
||||
osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
|
||||
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
|
||||
{
|
||||
for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY)
|
||||
{
|
||||
const ESM::Cell* cell = store.get<ESM::Cell>().searchStatic(cellX, cellY);
|
||||
if (!cell) continue;
|
||||
|
||||
calculator.reset();
|
||||
for (size_t i=0; i<cell->mContextList.size(); ++i)
|
||||
{
|
||||
unsigned int index = cell->mContextList.at(i).index;
|
||||
if (esm.size() <= index)
|
||||
esm.resize(index+1);
|
||||
cell->restore(esm[index], i);
|
||||
ESM::CellRef ref;
|
||||
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
|
||||
bool deleted = false;
|
||||
while(cell->getNextRef(esm[index], ref, deleted))
|
||||
{
|
||||
if (deleted) continue;
|
||||
if (!ref.mRefNum.fromGroundcoverFile()) continue;
|
||||
|
||||
if (!calculator.isInstanceEnabled()) continue;
|
||||
if (!isInChunkBorders(ref, minBound, maxBound)) continue;
|
||||
|
||||
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
||||
int type = store.findStatic(ref.mRefID);
|
||||
std::string model = getGroundcoverModel(type, ref.mRefID, store);
|
||||
if (model.empty()) continue;
|
||||
model = "meshes/" + model;
|
||||
|
||||
instances[model].emplace_back(ref, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center)
|
||||
{
|
||||
osg::ref_ptr<osg::Group> group = new osg::Group;
|
||||
osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE;
|
||||
for (auto& pair : instances)
|
||||
{
|
||||
const osg::Node* temp = mSceneManager->getTemplate(pair.first);
|
||||
osg::ref_ptr<osg::Node> node = static_cast<osg::Node*>(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES)));
|
||||
|
||||
// Keep link to original mesh to keep it in cache
|
||||
group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp));
|
||||
|
||||
InstancingVisitor visitor(pair.second, worldCenter);
|
||||
node->accept(visitor);
|
||||
group->addChild(node);
|
||||
}
|
||||
|
||||
group->getBound();
|
||||
group->setNodeMask(Mask_Groundcover);
|
||||
mSceneManager->recreateShaders(group, "groundcover", false, true);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
unsigned int Groundcover::getNodeMask()
|
||||
{
|
||||
return Mask_Groundcover;
|
||||
}
|
||||
|
||||
void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const
|
||||
{
|
||||
stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize());
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
#ifndef OPENMW_MWRENDER_GROUNDCOVER_H
|
||||
#define OPENMW_MWRENDER_GROUNDCOVER_H
|
||||
|
||||
#include <components/terrain/quadtreeworld.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class GroundcoverUpdater : public SceneUtil::StateSetUpdater
|
||||
{
|
||||
public:
|
||||
GroundcoverUpdater()
|
||||
: mWindSpeed(0.f)
|
||||
, mPlayerPos(osg::Vec3f())
|
||||
{
|
||||
}
|
||||
|
||||
void setWindSpeed(float windSpeed);
|
||||
void setPlayerPos(osg::Vec3f playerPos);
|
||||
|
||||
protected:
|
||||
void setDefaults(osg::StateSet *stateset) override;
|
||||
void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override;
|
||||
|
||||
private:
|
||||
float mWindSpeed;
|
||||
osg::Vec3f mPlayerPos;
|
||||
};
|
||||
|
||||
typedef std::tuple<osg::Vec2f, float, bool> ChunkId; // Center, Size, ActiveGrid
|
||||
class Groundcover : public Resource::GenericResourceManager<ChunkId>, public Terrain::QuadTreeWorld::ChunkManager
|
||||
{
|
||||
public:
|
||||
Groundcover(Resource::SceneManager* sceneManager, float density);
|
||||
~Groundcover() = default;
|
||||
|
||||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
|
||||
|
||||
unsigned int getNodeMask() override;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
|
||||
|
||||
struct GroundcoverEntry
|
||||
{
|
||||
ESM::Position mPos;
|
||||
float mScale;
|
||||
std::string mModel;
|
||||
|
||||
GroundcoverEntry(const ESM::CellRef& ref, const std::string& model)
|
||||
{
|
||||
mPos = ref.mPos;
|
||||
mScale = ref.mScale;
|
||||
mModel = model;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Resource::SceneManager* mSceneManager;
|
||||
float mDensity;
|
||||
|
||||
typedef std::map<std::string, std::vector<GroundcoverEntry>> InstanceMap;
|
||||
osg::ref_ptr<osg::Node> createChunk(InstanceMap& instances, const osg::Vec2f& center);
|
||||
void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,324 @@
|
||||
#include "screenshotmanager.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include <osg/ImageUtils>
|
||||
#include <osg/ShapeDrawable>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/TextureCubeMap>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwgui/loadingscreen.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "util.hpp"
|
||||
#include "vismask.hpp"
|
||||
#include "water.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
enum Screenshot360Type
|
||||
{
|
||||
Spherical,
|
||||
Cylindrical,
|
||||
Planet,
|
||||
RawCubemap
|
||||
};
|
||||
|
||||
class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback
|
||||
{
|
||||
public:
|
||||
NotifyDrawCompletedCallback(unsigned int frame)
|
||||
: mDone(false), mFrame(frame)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () (osg::RenderInfo& renderInfo) const override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame)
|
||||
{
|
||||
mDone = true;
|
||||
mCondition.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void waitTillDone()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mDone)
|
||||
return;
|
||||
mCondition.wait(lock);
|
||||
}
|
||||
|
||||
mutable std::condition_variable mCondition;
|
||||
mutable std::mutex mMutex;
|
||||
mutable bool mDone;
|
||||
unsigned int mFrame;
|
||||
};
|
||||
|
||||
class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback
|
||||
{
|
||||
public:
|
||||
ReadImageFromFramebufferCallback(osg::Image* image, int width, int height)
|
||||
: mWidth(width), mHeight(height), mImage(image)
|
||||
{
|
||||
}
|
||||
void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override
|
||||
{
|
||||
int screenW = renderInfo.getCurrentCamera()->getViewport()->width();
|
||||
int screenH = renderInfo.getCurrentCamera()->getViewport()->height();
|
||||
double imageaspect = (double)mWidth/(double)mHeight;
|
||||
int leftPadding = std::max(0, static_cast<int>(screenW - screenH * imageaspect) / 2);
|
||||
int topPadding = std::max(0, static_cast<int>(screenH - screenW / imageaspect) / 2);
|
||||
int width = screenW - leftPadding*2;
|
||||
int height = screenH - topPadding*2;
|
||||
mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE);
|
||||
mImage->scaleImage(mWidth, mHeight, 1);
|
||||
}
|
||||
private:
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
osg::ref_ptr<osg::Image> mImage;
|
||||
};
|
||||
|
||||
ScreenshotManager::ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, osg::ref_ptr<osg::Group> sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water)
|
||||
: mViewer(viewer)
|
||||
, mRootNode(rootNode)
|
||||
, mSceneRoot(sceneRoot)
|
||||
, mResourceSystem(resourceSystem)
|
||||
, mWater(water)
|
||||
{
|
||||
}
|
||||
|
||||
void ScreenshotManager::screenshot(osg::Image* image, int w, int h)
|
||||
{
|
||||
osg::Camera* camera = mViewer->getCamera();
|
||||
osg::ref_ptr<osg::Drawable> tempDrw = new osg::Drawable;
|
||||
tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h));
|
||||
tempDrw->setCullingActive(false);
|
||||
tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera
|
||||
camera->addChild(tempDrw);
|
||||
osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber()));
|
||||
camera->setFinalDrawCallback(callback);
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
mViewer->renderingTraversals();
|
||||
callback->waitTillDone();
|
||||
// now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed
|
||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||
camera->removeChild(tempDrw);
|
||||
camera->setFinalDrawCallback(nullptr);
|
||||
}
|
||||
|
||||
bool ScreenshotManager::screenshot360(osg::Image* image)
|
||||
{
|
||||
int screenshotW = mViewer->getCamera()->getViewport()->width();
|
||||
int screenshotH = mViewer->getCamera()->getViewport()->height();
|
||||
Screenshot360Type screenshotMapping = Spherical;
|
||||
|
||||
const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video");
|
||||
std::vector<std::string> settingArgs;
|
||||
Misc::StringUtils::split(settingStr, settingArgs);
|
||||
|
||||
if (settingArgs.size() > 0)
|
||||
{
|
||||
std::string typeStrings[4] = {"spherical", "cylindrical", "planet", "cubemap"};
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (settingArgs[0].compare(typeStrings[i]) == 0)
|
||||
{
|
||||
screenshotMapping = static_cast<Screenshot360Type>(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << ".";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// planet mapping needs higher resolution
|
||||
int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2;
|
||||
|
||||
if (settingArgs.size() > 1)
|
||||
screenshotW = std::min(10000, std::atoi(settingArgs[1].c_str()));
|
||||
|
||||
if (settingArgs.size() > 2)
|
||||
screenshotH = std::min(10000, std::atoi(settingArgs[2].c_str()));
|
||||
|
||||
if (settingArgs.size() > 3)
|
||||
cubeSize = std::min(5000, std::atoi(settingArgs[3].c_str()));
|
||||
|
||||
bool rawCubemap = screenshotMapping == RawCubemap;
|
||||
|
||||
if (rawCubemap)
|
||||
screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row
|
||||
else if (screenshotMapping == Planet)
|
||||
screenshotH = screenshotW; // use square resolution for planet mapping
|
||||
|
||||
std::vector<osg::ref_ptr<osg::Image>> images;
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
images.push_back(new osg::Image);
|
||||
|
||||
osg::Vec3 directions[6] = {
|
||||
rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1),
|
||||
osg::Vec3(0,0,-1),
|
||||
osg::Vec3(-1,0,0),
|
||||
rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0),
|
||||
osg::Vec3(0,1,0),
|
||||
osg::Vec3(0,-1,0)};
|
||||
|
||||
double rotations[] = {
|
||||
-osg::PI / 2.0,
|
||||
osg::PI / 2.0,
|
||||
osg::PI,
|
||||
0,
|
||||
osg::PI / 2.0,
|
||||
osg::PI / 2.0 };
|
||||
|
||||
for (int i = 0; i < 6; ++i) // for each cubemap side
|
||||
{
|
||||
osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1), directions[i]);
|
||||
|
||||
if (!rawCubemap)
|
||||
transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1));
|
||||
|
||||
osg::Image *sideImage = images[i].get();
|
||||
makeCubemapScreenshot(sideImage, cubeSize, cubeSize, transform);
|
||||
|
||||
if (!rawCubemap)
|
||||
sideImage->flipHorizontal();
|
||||
}
|
||||
|
||||
if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images
|
||||
{
|
||||
image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType());
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// run on GPU now:
|
||||
osg::ref_ptr<osg::TextureCubeMap> cubeTexture (new osg::TextureCubeMap);
|
||||
cubeTexture->setResizeNonPowerOfTwoHint(false);
|
||||
|
||||
cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST);
|
||||
cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST);
|
||||
|
||||
cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
cubeTexture->setImage(i, images[i].get());
|
||||
|
||||
osg::ref_ptr<osg::Camera> screenshotCamera(new osg::Camera);
|
||||
osg::ref_ptr<osg::ShapeDrawable> quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 2.0)));
|
||||
|
||||
std::map<std::string, std::string> defineMap;
|
||||
|
||||
Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager();
|
||||
osg::ref_ptr<osg::Shader> fragmentShader(shaderMgr.getShader("s360_fragment.glsl", defineMap,osg::Shader::FRAGMENT));
|
||||
osg::ref_ptr<osg::Shader> vertexShader(shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX));
|
||||
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
|
||||
|
||||
osg::ref_ptr<osg::Program> program(new osg::Program);
|
||||
program->addShader(fragmentShader);
|
||||
program->addShader(vertexShader);
|
||||
stateset->setAttributeAndModes(program, osg::StateAttribute::ON);
|
||||
|
||||
stateset->addUniform(new osg::Uniform("cubeMap", 0));
|
||||
stateset->addUniform(new osg::Uniform("mapping", screenshotMapping));
|
||||
stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON);
|
||||
|
||||
quad->setStateSet(stateset);
|
||||
quad->setUpdateCallback(nullptr);
|
||||
|
||||
screenshotCamera->addChild(quad);
|
||||
|
||||
renderCameraToImage(screenshotCamera, image, screenshotW, screenshotH);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenshotManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h)
|
||||
{
|
||||
camera->setNodeMask(Mask_RenderToTexture);
|
||||
camera->attach(osg::Camera::COLOR_BUFFER, image);
|
||||
camera->setRenderOrder(osg::Camera::PRE_RENDER);
|
||||
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
|
||||
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT);
|
||||
|
||||
camera->setViewport(0, 0, w, h);
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
|
||||
texture->setInternalFormat(GL_RGB);
|
||||
texture->setTextureSize(w,h);
|
||||
texture->setResizeNonPowerOfTwoHint(false);
|
||||
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
camera->attach(osg::Camera::COLOR_BUFFER,texture);
|
||||
|
||||
image->setDataType(GL_UNSIGNED_BYTE);
|
||||
image->setPixelFormat(texture->getInternalFormat());
|
||||
|
||||
mRootNode->addChild(camera);
|
||||
|
||||
// The draw needs to complete before we can copy back our image.
|
||||
osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback(0));
|
||||
camera->setFinalDrawCallback(callback);
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false);
|
||||
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
mViewer->renderingTraversals();
|
||||
callback->waitTillDone();
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff();
|
||||
|
||||
// now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed
|
||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||
|
||||
camera->removeChildren(0, camera->getNumChildren());
|
||||
mRootNode->removeChild(camera);
|
||||
}
|
||||
|
||||
void ScreenshotManager::makeCubemapScreenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform)
|
||||
{
|
||||
osg::ref_ptr<osg::Camera> rttCamera (new osg::Camera);
|
||||
float nearClip = Settings::Manager::getFloat("near clip", "Camera");
|
||||
float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera");
|
||||
// each cubemap side sees 90 degrees
|
||||
rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance);
|
||||
rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform);
|
||||
|
||||
rttCamera->setUpdateCallback(new NoTraverseCallback);
|
||||
rttCamera->addChild(mSceneRoot);
|
||||
|
||||
rttCamera->addChild(mWater->getReflectionCamera());
|
||||
rttCamera->addChild(mWater->getRefractionCamera());
|
||||
|
||||
rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI));
|
||||
|
||||
rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
renderCameraToImage(rttCamera.get(),image,w,h);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
#ifndef MWRENDER_SCREENSHOTMANAGER_H
|
||||
#define MWRENDER_SCREENSHOTMANAGER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <osg/Group>
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class ResourceSystem;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class Water;
|
||||
|
||||
class ScreenshotManager
|
||||
{
|
||||
public:
|
||||
ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, osg::ref_ptr<osg::Group> sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water);
|
||||
|
||||
void screenshot(osg::Image* image, int w, int h);
|
||||
bool screenshot360(osg::Image* image);
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osgViewer::Viewer> mViewer;
|
||||
osg::ref_ptr<osg::Group> mRootNode;
|
||||
osg::ref_ptr<osg::Group> mSceneRoot;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
Water* mWater;
|
||||
|
||||
void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h);
|
||||
void makeCubemapScreenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd());
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,152 @@
|
||||
#include "sound_buffer.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct AudioParams
|
||||
{
|
||||
float mAudioDefaultMinDistance;
|
||||
float mAudioDefaultMaxDistance;
|
||||
float mAudioMinDistanceMult;
|
||||
float mAudioMaxDistanceMult;
|
||||
};
|
||||
|
||||
AudioParams makeAudioParams(const MWBase::World& world)
|
||||
{
|
||||
const auto& settings = world.getStore().get<ESM::GameSetting>();
|
||||
AudioParams params;
|
||||
params.mAudioDefaultMinDistance = settings.find("fAudioDefaultMinDistance")->mValue.getFloat();
|
||||
params.mAudioDefaultMaxDistance = settings.find("fAudioDefaultMaxDistance")->mValue.getFloat();
|
||||
params.mAudioMinDistanceMult = settings.find("fAudioMinDistanceMult")->mValue.getFloat();
|
||||
params.mAudioMaxDistanceMult = settings.find("fAudioMaxDistanceMult")->mValue.getFloat();
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
SoundBufferPool::SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output) :
|
||||
mVfs(&vfs),
|
||||
mOutput(&output),
|
||||
mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024),
|
||||
mBufferCacheMin(std::min(static_cast<std::size_t>(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) * 1024 * 1024, mBufferCacheMax))
|
||||
{
|
||||
}
|
||||
|
||||
SoundBufferPool::~SoundBufferPool()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
Sound_Buffer* SoundBufferPool::lookup(const std::string& soundId) const
|
||||
{
|
||||
const auto it = mBufferNameMap.find(soundId);
|
||||
if (it != mBufferNameMap.end())
|
||||
{
|
||||
Sound_Buffer* sfx = it->second;
|
||||
if (sfx->getHandle() != nullptr)
|
||||
return sfx;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Sound_Buffer* SoundBufferPool::load(const std::string& soundId)
|
||||
{
|
||||
if (mBufferNameMap.empty())
|
||||
{
|
||||
for (const ESM::Sound& sound : MWBase::Environment::get().getWorld()->getStore().get<ESM::Sound>())
|
||||
insertSound(Misc::StringUtils::lowerCase(sound.mId), sound);
|
||||
}
|
||||
|
||||
Sound_Buffer* sfx;
|
||||
const auto it = mBufferNameMap.find(soundId);
|
||||
if (it != mBufferNameMap.end())
|
||||
sfx = it->second;
|
||||
else
|
||||
{
|
||||
const ESM::Sound *sound = MWBase::Environment::get().getWorld()->getStore().get<ESM::Sound>().search(soundId);
|
||||
if (sound == nullptr)
|
||||
return {};
|
||||
sfx = insertSound(soundId, *sound);
|
||||
}
|
||||
|
||||
if (sfx->getHandle() == nullptr)
|
||||
{
|
||||
auto [handle, size] = mOutput->loadSound(sfx->getResourceName());
|
||||
if (handle == nullptr)
|
||||
return {};
|
||||
|
||||
sfx->mHandle = handle;
|
||||
|
||||
mBufferCacheSize += size;
|
||||
if (mBufferCacheSize > mBufferCacheMax)
|
||||
{
|
||||
unloadUnused();
|
||||
if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax)
|
||||
Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!";
|
||||
}
|
||||
mUnusedBuffers.push_front(sfx);
|
||||
}
|
||||
|
||||
return sfx;
|
||||
}
|
||||
|
||||
void SoundBufferPool::clear()
|
||||
{
|
||||
for (auto &sfx : mSoundBuffers)
|
||||
{
|
||||
if(sfx.mHandle)
|
||||
mOutput->unloadSound(sfx.mHandle);
|
||||
sfx.mHandle = nullptr;
|
||||
}
|
||||
mUnusedBuffers.clear();
|
||||
}
|
||||
|
||||
Sound_Buffer* SoundBufferPool::insertSound(const std::string& soundId, const ESM::Sound& sound)
|
||||
{
|
||||
static const AudioParams audioParams = makeAudioParams(*MWBase::Environment::get().getWorld());
|
||||
|
||||
float volume = static_cast<float>(std::pow(10.0, (sound.mData.mVolume / 255.0 * 3348.0 - 3348.0) / 2000.0));
|
||||
float min = sound.mData.mMinRange;
|
||||
float max = sound.mData.mMaxRange;
|
||||
if (min == 0 && max == 0)
|
||||
{
|
||||
min = audioParams.mAudioDefaultMinDistance;
|
||||
max = audioParams.mAudioDefaultMaxDistance;
|
||||
}
|
||||
|
||||
min *= audioParams.mAudioMinDistanceMult;
|
||||
max *= audioParams.mAudioMaxDistanceMult;
|
||||
min = std::max(min, 1.0f);
|
||||
max = std::max(min, max);
|
||||
|
||||
Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max);
|
||||
mVfs->normalizeFilename(sfx.mResourceName);
|
||||
|
||||
mBufferNameMap.emplace(soundId, &sfx);
|
||||
return &sfx;
|
||||
}
|
||||
|
||||
void SoundBufferPool::unloadUnused()
|
||||
{
|
||||
while (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMin)
|
||||
{
|
||||
Sound_Buffer* const unused = mUnusedBuffers.back();
|
||||
|
||||
mBufferCacheSize -= mOutput->unloadSound(unused->getHandle());
|
||||
unused->mHandle = nullptr;
|
||||
|
||||
mUnusedBuffers.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue