Merge branch 'master' into 'the_goal_is_elevation'

# Conflicts:
#   CHANGELOG.md
pull/3130/head
psi29a 3 years ago
commit 47eda85b9f

@ -38,7 +38,8 @@
Bug #6197: Infinite Casting Loop
Bug #6273: Respawning NPCs rotation is inconsistent
Bug #6282: Laura craft doesn't follow the player character
Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters
Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters
Feature #890: OpenMW-CS: Column filtering
Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record
Feature #2780: A way to see current OpenMW version in the console
Feature #3616: Allow Zoom levels on the World Map

@ -71,7 +71,7 @@ opencs_units (view/world
cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable
dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator
bodypartcreator landtexturecreator landcreator
bodypartcreator landtexturecreator landcreator tableheadermouseeventhandler
)
opencs_units (view/world

@ -32,7 +32,6 @@ namespace CSVWidget
namespace CSVWorld
{
class Table;
class TableBottomBox;
class CreatorFactoryBase;

@ -28,6 +28,7 @@
#include "../../model/prefs/shortcut.hpp"
#include "tableeditidaction.hpp"
#include "tableheadermouseeventhandler.hpp"
#include "util.hpp"
void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event)
@ -422,6 +423,8 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)),
this, SLOT (settingChanged (const CSMPrefs::Setting *)));
CSMPrefs::get()["ID Tables"].update();
new TableHeaderMouseEventHandler(this);
}
void CSVWorld::Table::setEditLock (bool locked)

@ -0,0 +1,64 @@
#include "tableheadermouseeventhandler.hpp"
#include "dragrecordtable.hpp"
#include <QMenu>
#include <QPoint>
namespace CSVWorld
{
TableHeaderMouseEventHandler::TableHeaderMouseEventHandler(DragRecordTable * parent)
: QWidget(parent)
, table(*parent)
, header(*table.horizontalHeader())
{
header.setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
connect(
&header, &QHeaderView::customContextMenuRequested, [=](const QPoint & position) { showContextMenu(position); });
header.viewport()->installEventFilter(this);
}
bool TableHeaderMouseEventHandler::eventFilter(QObject * tableWatched, QEvent * event)
{
if (event->type() == QEvent::Type::MouseButtonPress)
{
auto & clickEvent = static_cast<QMouseEvent &>(*event);
if ((clickEvent.button() == Qt::MiddleButton))
{
const auto & index = table.indexAt(clickEvent.pos());
table.setColumnHidden(index.column(), true);
clickEvent.accept();
return true;
}
}
return false;
}
void TableHeaderMouseEventHandler::showContextMenu(const QPoint & position)
{
auto & menu{createContextMenu()};
menu.popup(header.viewport()->mapToGlobal(position));
}
QMenu & TableHeaderMouseEventHandler::createContextMenu()
{
auto * menu = new QMenu(this);
for (int i = 0; i < table.model()->columnCount(); ++i)
{
const auto & name = table.model()->headerData(i, Qt::Horizontal, Qt::DisplayRole);
QAction * action{new QAction(name.toString(), this)};
action->setCheckable(true);
action->setChecked(!table.isColumnHidden(i));
menu->addAction(action);
connect(action, &QAction::triggered, [=]() {
table.setColumnHidden(i, !action->isChecked());
action->setChecked(!action->isChecked());
action->toggle();
});
}
return *menu;
}
} // namespace CSVWorld

@ -0,0 +1,25 @@
#pragma once
#include <QHeaderView>
#include <QtGui>
namespace CSVWorld
{
class DragRecordTable;
class TableHeaderMouseEventHandler : public QWidget
{
public:
explicit TableHeaderMouseEventHandler(DragRecordTable * parent);
void showContextMenu(const QPoint &);
private:
DragRecordTable & table;
QHeaderView & header;
QMenu & createContextMenu();
bool eventFilter(QObject *, QEvent *) override;
}; // class TableHeaderMouseEventHandler
} // namespace CSVWorld

@ -624,7 +624,7 @@ namespace MWPhysics
mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback);
const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld);
const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(projectile->getHitPosition());
projectile->setPosition(newpos);
mTaskScheduler->updateSingleAabb(foundProjectile->second);
}
@ -686,7 +686,7 @@ namespace MWPhysics
mActors.emplace(ptr.mRef, std::move(actor));
}
int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater)
int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius)
{
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
assert(shapeInstance);
@ -694,7 +694,7 @@ namespace MWPhysics
mProjectileId++;
auto projectile = std::make_shared<Projectile>(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this);
auto projectile = std::make_shared<Projectile>(caster, position, radius, mTaskScheduler.get(), this);
mProjectiles.emplace(mProjectileId, std::move(projectile));
return mProjectileId;

@ -131,7 +131,7 @@ namespace MWPhysics
void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World, bool skipAnimated = false);
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh);
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater);
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius);
void setCaster(int projectileId, const MWWorld::Ptr& caster);
void updateProjectile(const int projectileId, const osg::Vec3f &position) const;
void removeProjectile(const int projectileId);

@ -15,12 +15,10 @@
namespace MWPhysics
{
Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
: mCanCrossWaterSurface(canCrossWaterSurface)
, mCrossedWaterSurface(false)
Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
: mHitWater(false)
, mActive(true)
, mHitTarget(nullptr)
, mWaterHitPosition(std::nullopt)
, mPhysics(physicssystem)
, mTaskScheduler(scheduler)
{
@ -75,11 +73,6 @@ osg::Vec3f Projectile::getPosition() const
return mPosition;
}
bool Projectile::canTraverseWater() const
{
return mCanCrossWaterSurface;
}
void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal)
{
bool active = true;
@ -143,17 +136,4 @@ bool Projectile::isValidTarget(const btCollisionObject* target) const
[target](const btCollisionObject* actor) { return target == actor; });
}
std::optional<btVector3> Projectile::getWaterHitPosition()
{
return std::exchange(mWaterHitPosition, std::nullopt);
}
void Projectile::setWaterHitPosition(btVector3 pos)
{
if (mCrossedWaterSurface)
return;
mCrossedWaterSurface = true;
mWaterHitPosition = pos;
}
}

@ -4,7 +4,6 @@
#include <atomic>
#include <memory>
#include <mutex>
#include <optional>
#include <LinearMath/btVector3.h>
@ -32,7 +31,7 @@ namespace MWPhysics
class Projectile final : public PtrHolder
{
public:
Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
~Projectile() override;
btConvexShape* getConvexShape() const { return mConvexShape; }
@ -56,15 +55,25 @@ namespace MWPhysics
return mCasterColObj;
}
bool canTraverseWater() const;
void setHitWater()
{
mHitWater = true;
}
bool getHitWater() const
{
return mHitWater;
}
void hit(const btCollisionObject* target, btVector3 pos, btVector3 normal);
void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
bool isValidTarget(const btCollisionObject* target) const;
std::optional<btVector3> getWaterHitPosition();
void setWaterHitPosition(btVector3 pos);
btVector3 getHitPosition() const
{
return mHitPosition;
}
private:
@ -72,13 +81,11 @@ namespace MWPhysics
btConvexShape* mConvexShape;
bool mTransformUpdatePending;
bool mCanCrossWaterSurface;
bool mCrossedWaterSurface;
bool mHitWater;
std::atomic<bool> mActive;
MWWorld::Ptr mCaster;
const btCollisionObject* mCasterColObj;
const btCollisionObject* mHitTarget;
std::optional<btVector3> mWaterHitPosition;
osg::Vec3f mPosition;
btVector3 mHitPosition;
btVector3 mHitNormal;

@ -49,9 +49,7 @@ namespace MWPhysics
}
case CollisionType_Water:
{
mProjectile->setWaterHitPosition(m_hitPointWorld);
if (mProjectile->canTraverseWater())
return 1.f;
mProjectile->setHitWater();
break;
}
}

@ -651,7 +651,7 @@ namespace MWRender
}
optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback);
unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY;
mSceneManager->shareState(mergeGroup);
optimizer.optimize(mergeGroup, options);
group->addChild(mergeGroup);

@ -317,7 +317,7 @@ namespace MWWorld
// in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape
if (state.mIdMagic.size() > 1)
model = "meshes\\" + MWBase::Environment::get().getWorld()->getStore().get<ESM::Weapon>().find(state.mIdMagic[1])->mModel;
state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true, false);
state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true);
state.mToDelete = false;
mMagicBolts.push_back(state);
}
@ -342,7 +342,7 @@ namespace MWWorld
if (!ptr.getClass().getEnchantment(ptr).empty())
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false, true);
state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false);
state.mToDelete = false;
mProjectiles.push_back(state);
}
@ -493,9 +493,6 @@ namespace MWWorld
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (const auto hitWaterPos = projectile->getWaterHitPosition())
mRendering->emitWaterRipple(Misc::Convert::toOsg(*hitWaterPos));
const auto pos = projectile->getPosition();
projectileState.mNode->setPosition(pos);
@ -519,6 +516,8 @@ namespace MWWorld
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt;
}
if (projectile->getHitWater())
mRendering->emitWaterRipple(pos);
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
projectileState.mToDelete = true;
@ -663,7 +662,7 @@ namespace MWWorld
int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown;
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false, true);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false);
}
catch(...)
{
@ -716,7 +715,7 @@ namespace MWWorld
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true, false);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (const std::string &soundid : state.mSoundIds)

@ -200,13 +200,12 @@ namespace
struct InsertVisitor
{
MWWorld::CellStore& mCell;
Loading::Listener& mLoadingListener;
Loading::Listener* mLoadingListener;
bool mOnlyObjects;
bool mTest;
std::vector<MWWorld::Ptr> mToInsert;
InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyObjects, bool test);
InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects);
bool operator() (const MWWorld::Ptr& ptr);
@ -214,8 +213,8 @@ namespace
void insert(AddObject&& addObject);
};
InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyObjects, bool test)
: mCell (cell), mLoadingListener (loadingListener), mOnlyObjects(onlyObjects), mTest(test)
InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects)
: mCell(cell), mLoadingListener(loadingListener), mOnlyObjects(onlyObjects)
{}
bool InsertVisitor::operator() (const MWWorld::Ptr& ptr)
@ -244,8 +243,8 @@ namespace
}
}
if (!mTest)
mLoadingListener.increaseProgress (1);
if (mLoadingListener != nullptr)
mLoadingListener->increaseProgress(1);
}
}
@ -325,12 +324,12 @@ namespace MWWorld
mRendering.update (duration, paused);
}
void Scene::unloadInactiveCell (CellStore* cell, bool test)
void Scene::unloadInactiveCell (CellStore* cell)
{
assert(mActiveCells.find(cell) == mActiveCells.end());
assert(mInactiveCells.find(cell) != mInactiveCells.end());
if (!test)
Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription();
Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription();
ListObjectsVisitor visitor;
@ -351,13 +350,13 @@ namespace MWWorld
mInactiveCells.erase(cell);
}
void Scene::deactivateCell(CellStore* cell, bool test)
void Scene::deactivateCell(CellStore* cell)
{
assert(mInactiveCells.find(cell) != mInactiveCells.end());
if (mActiveCells.find(cell) == mActiveCells.end())
return;
if (!test)
Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription();
Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription();
ListAndResetObjectsVisitor visitor;
@ -409,7 +408,7 @@ namespace MWWorld
mActiveCells.erase(cell);
}
void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test)
void Scene::activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn)
{
using DetourNavigator::HeightfieldShape;
@ -417,17 +416,14 @@ namespace MWWorld
assert(mInactiveCells.find(cell) != mInactiveCells.end());
mActiveCells.insert(cell);
if (test)
Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription();
else
Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription();
Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription();
const auto world = MWBase::Environment::get().getWorld();
const int cellX = cell->getCell()->getGridX();
const int cellY = cell->getCell()->getGridY();
if (!test && cell->getCell()->isExterior())
if (cell->getCell()->isExterior())
{
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
{
@ -466,69 +462,64 @@ namespace MWWorld
if (respawn)
cell->respawn();
insertCell (*cell, loadingListener, false, test);
insertCell(*cell, loadingListener, false);
mRendering.addCell(cell);
if (!test)
MWBase::Environment::get().getWindowManager()->addCell(cell);
bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior();
float waterLevel = cell->getWaterLevel();
mRendering.setWaterEnabled(waterEnabled);
if (waterEnabled)
{
MWBase::Environment::get().getWindowManager()->addCell(cell);
bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior();
float waterLevel = cell->getWaterLevel();
mRendering.setWaterEnabled(waterEnabled);
if (waterEnabled)
{
mPhysics->enableWater(waterLevel);
mRendering.setWaterHeight(waterLevel);
mPhysics->enableWater(waterLevel);
mRendering.setWaterHeight(waterLevel);
if (cell->getCell()->isExterior())
{
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
{
const btTransform& transform =heightField->getCollisionObject()->getWorldTransform();
mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE,
osg::Vec3f(static_cast<float>(transform.getOrigin().x()),
static_cast<float>(transform.getOrigin().y()),
waterLevel));
}
}
else
if (cell->getCell()->isExterior())
{
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
{
mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(),
osg::Vec3f(0, 0, waterLevel));
const btTransform& transform =heightField->getCollisionObject()->getWorldTransform();
mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE,
osg::Vec3f(static_cast<float>(transform.getOrigin().x()),
static_cast<float>(transform.getOrigin().y()),
waterLevel));
}
}
else
mPhysics->disableWater();
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
// The player is loaded before the scene and by default it is grounded, with the scene fully loaded, we validate and correct this.
if (player.mCell == cell) // Only run once, during initial cell load.
{
mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f);
mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(),
osg::Vec3f(0, 0, waterLevel));
}
}
else
mPhysics->disableWater();
mNavigator.update(player.getRefData().getPosition().asVec3());
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
mRendering.configureAmbient(cell->getCell());
// The player is loaded before the scene and by default it is grounded, with the scene fully loaded, we validate and correct this.
if (player.mCell == cell) // Only run once, during initial cell load.
{
mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f);
}
mNavigator.update(player.getRefData().getPosition().asVec3());
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
mRendering.configureAmbient(cell->getCell());
mPreloader->notifyLoaded(cell);
}
void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test)
void Scene::loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener)
{
assert(mActiveCells.find(cell) == mActiveCells.end());
assert(mInactiveCells.find(cell) == mInactiveCells.end());
mInactiveCells.insert(cell);
if (test)
Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription();
else
Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription();
Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription();
if (!test && cell->getCell()->isExterior())
if (cell->getCell()->isExterior())
{
float verts = ESM::Land::LAND_SIZE;
float worldsize = ESM::Land::REAL_SIZE;
@ -550,7 +541,7 @@ namespace MWWorld
}
}
insertCell (*cell, loadingListener, true, test);
insertCell(*cell, loadingListener, true);
}
void Scene::clear()
@ -746,8 +737,8 @@ namespace MWWorld
loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")...");
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY);
loadInactiveCell (cell, loadingListener, true);
activateCell (cell, loadingListener, false, true);
loadInactiveCell(cell, nullptr);
activateCell(cell, nullptr, false);
auto iter = mInactiveCells.begin();
while (iter != mInactiveCells.end())
@ -755,8 +746,8 @@ namespace MWWorld
if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() &&
it->mData.mY == (*iter)->getCell()->getGridY())
{
deactivateCell(*iter, true);
unloadInactiveCell (*iter, true);
deactivateCell(*iter);
unloadInactiveCell(*iter);
break;
}
@ -794,8 +785,8 @@ namespace MWWorld
loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")...");
CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName);
loadInactiveCell (cell, loadingListener, true);
activateCell (cell, loadingListener, false, true);
loadInactiveCell(cell, nullptr);
activateCell(cell, nullptr, false);
auto iter = mInactiveCells.begin();
while (iter != mInactiveCells.end())
@ -804,8 +795,8 @@ namespace MWWorld
if (it->mName == (*iter)->getCell()->mName)
{
deactivateCell(*iter, true);
unloadInactiveCell (*iter, true);
deactivateCell(*iter);
unloadInactiveCell(*iter);
break;
}
@ -988,9 +979,9 @@ namespace MWWorld
mCellChanged = false;
}
void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects, bool test)
void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects)
{
InsertVisitor insertVisitor (cell, *loadingListener, onlyObjects, test);
InsertVisitor insertVisitor(cell, loadingListener, onlyObjects);
cell.forEach (insertVisitor);
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyObjects); });
if (!onlyObjects)

@ -100,7 +100,7 @@ namespace MWWorld
std::vector<osg::ref_ptr<SceneUtil::WorkItem>> mWorkItems;
void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects, bool test = false);
void insertCell(CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects);
osg::Vec2i mCurrentGridCenter;
// Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center
@ -116,10 +116,10 @@ namespace MWWorld
osg::Vec4i gridCenterToBounds(const osg::Vec2i &centerCell) const;
osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const;
void unloadInactiveCell (CellStore* cell, bool test = false);
void deactivateCell (CellStore* cell, bool test = false);
void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false);
void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false);
void unloadInactiveCell(CellStore* cell);
void deactivateCell(CellStore* cell);
void activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn);
void loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener);
public:

@ -15,6 +15,7 @@
#include <components/esm/cellref.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/mathutil.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/rng.hpp>
#include <components/misc/convert.hpp>
@ -76,21 +77,6 @@
#include "contentloader.hpp"
#include "esmloader.hpp"
namespace
{
// Wraps a value to (-PI, PI]
void wrap(float& rad)
{
const float pi = static_cast<float>(osg::PI);
if (rad>0)
rad = std::fmod(rad+pi, 2.0f*pi)-pi;
else
rad = std::fmod(rad-pi, 2.0f*pi)+pi;
}
}
namespace MWWorld
{
struct GameContentLoader : public ContentLoader
@ -1290,8 +1276,6 @@ namespace MWWorld
void World::rotateObject(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags)
{
const float pi = static_cast<float>(osg::PI);
ESM::Position pos = ptr.getRefData().getPosition();
float *objRot = pos.rot;
if (flags & MWBase::RotationFlag_adjust)
@ -1313,13 +1297,9 @@ namespace MWWorld
* currently it's done so for rotating the camera, which needs
* clamping.
*/
const float half_pi = pi/2.f;
if(objRot[0] < -half_pi) objRot[0] = -half_pi;
else if(objRot[0] > half_pi) objRot[0] = half_pi;
wrap(objRot[1]);
wrap(objRot[2]);
objRot[0] = osg::clampBetween(objRot[0], -osg::PIf / 2, osg::PIf / 2);
objRot[1] = Misc::normalizeAngle(objRot[1]);
objRot[2] = Misc::normalizeAngle(objRot[2]);
}
ptr.getRefData().setPosition(pos);
@ -3145,6 +3125,7 @@ namespace MWWorld
bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos);
if (underwater)
{
MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength);
mRendering->emitWaterRipple(worldPos);
return;
}

@ -659,22 +659,18 @@ namespace Resource
osg::ref_ptr<Shader::ShaderVisitor> shaderVisitor (createShaderVisitor());
loaded->accept(*shaderVisitor);
// share state
// do this before optimizing so the optimizer will be able to combine nodes more aggressively
// note, because StateSets will be shared at this point, StateSets can not be modified inside the optimizer
mSharedStateMutex.lock();
mSharedStateManager->share(loaded.get());
mSharedStateMutex.unlock();
if (canOptimize(normalized))
{
SceneUtil::Optimizer optimizer;
optimizer.setSharedStateManager(mSharedStateManager, &mSharedStateMutex);
optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback);
static const unsigned int options = getOptimizationOptions();
static const unsigned int options = getOptimizationOptions()|SceneUtil::Optimizer::SHARE_DUPLICATE_STATE;
optimizer.optimize(loaded, options);
}
else
shareState(loaded);
if (compile && mIncrementalCompileOperation)
mIncrementalCompileOperation->add(loaded);

@ -30,6 +30,8 @@
#include <osg/io_utils>
#include <osg/Depth>
#include <osgDB/SharedStateManager>
#include <osgUtil/TransformAttributeFunctor>
#include <osgUtil/Statistics>
#include <osgUtil/MeshOptimizers>
@ -84,6 +86,13 @@ void Optimizer::optimize(osg::Node* node, unsigned int options)
cstv.removeTransforms(node);
}
if (options & SHARE_DUPLICATE_STATE && _sharedStateManager)
{
if (_sharedStateMutex) _sharedStateMutex->lock();
_sharedStateManager->share(node);
if (_sharedStateMutex) _sharedStateMutex->unlock();
}
if (options & REMOVE_REDUNDANT_NODES)
{
OSG_INFO<<"Optimizer::optimize() doing REMOVE_REDUNDANT_NODES"<<std::endl;
@ -741,7 +750,8 @@ bool Optimizer::CombineStaticTransformsVisitor::removeTransforms(osg::Node* node
if (transform->getNumChildren()==1 &&
transform->getChild(0)->asTransform()!=0 &&
transform->getChild(0)->asTransform()->asMatrixTransform()!=0 &&
transform->getChild(0)->asTransform()->getDataVariance()==osg::Object::STATIC)
(!transform->getChild(0)->getStateSet() || transform->getChild(0)->getStateSet()->referenceCount()==1) &&
transform->getChild(0)->getDataVariance()==osg::Object::STATIC)
{
// now combine with its child.
osg::MatrixTransform* child = transform->getChild(0)->asTransform()->asMatrixTransform();

@ -25,6 +25,12 @@
//#include <osgUtil/Export>
#include <set>
#include <mutex>
namespace osgDB
{
class SharedStateManager;
}
//namespace osgUtil {
namespace SceneUtil {
@ -65,7 +71,7 @@ class Optimizer
public:
Optimizer() : _mergeAlphaBlending(false) {}
Optimizer() : _mergeAlphaBlending(false), _sharedStateManager(nullptr), _sharedStateMutex(nullptr) {}
virtual ~Optimizer() {}
enum OptimizationOptions
@ -121,6 +127,8 @@ class Optimizer
void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; }
void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; }
void setSharedStateManager(osgDB::SharedStateManager* sharedStateManager, std::mutex* sharedStateMutex) { _sharedStateMutex = sharedStateMutex; _sharedStateManager = sharedStateManager; }
/** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/
void reset();
@ -258,6 +266,9 @@ class Optimizer
osg::Vec3f _viewPoint;
bool _mergeAlphaBlending;
osgDB::SharedStateManager* _sharedStateManager;
mutable std::mutex* _sharedStateMutex;
public:
/** Flatten Static Transform nodes by applying their transform to the

@ -38,27 +38,41 @@ import termtables
@click.option('--timeseries_sum', is_flag=True,
help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.')
@click.option('--commulative_timeseries_sum', is_flag=True,
help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.')
help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.')
@click.option('--stats_sum', is_flag=True,
help='Add a row to stats table for a sum per frame of all given stats metrics.')
@click.option('--begin_frame', type=int, default=0,
help='Start processing from this frame.')
@click.option('--end_frame', type=int, default=sys.maxsize,
help='End processing at this frame.')
@click.option('--frame_number_name', type=str, default='FrameNumber',
help='Frame number metric name.')
@click.option('--hist_threshold', type=str, multiple=True,
help='Show a histogram for given metric only for frames with threshold_name metric over threshold_value.')
@click.option('--threshold_name', type=str, default='Frame duration',
help='Frame duration metric name.')
@click.option('--threshold_value', type=float, default=1.05/60,
help='Threshold for hist_over.')
@click.argument('path', type=click.Path(), nargs=-1)
def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats,
timeseries_sum, stats_sum, begin_frame, end_frame, path,
commulative_timeseries, commulative_timeseries_sum):
commulative_timeseries, commulative_timeseries_sum, frame_number_name,
hist_threshold, threshold_name, threshold_value):
sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))}
keys = collect_unique_keys(sources)
frames = collect_per_frame(sources=sources, keys=keys, begin_frame=begin_frame, end_frame=end_frame)
frames, begin_frame, end_frame = collect_per_frame(
sources=sources, keys=keys, begin_frame=begin_frame,
end_frame=end_frame, frame_number_name=frame_number_name,
)
if print_keys:
for v in keys:
print(v)
if timeseries:
draw_timeseries(sources=frames, keys=timeseries, add_sum=timeseries_sum)
draw_timeseries(sources=frames, keys=timeseries, add_sum=timeseries_sum,
begin_frame=begin_frame, end_frame=end_frame)
if commulative_timeseries:
draw_commulative_timeseries(sources=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum)
draw_commulative_timeseries(sources=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum,
begin_frame=begin_frame, end_frame=end_frame)
if hist:
draw_hists(sources=frames, keys=hist)
if hist_ratio:
@ -69,6 +83,9 @@ def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats,
draw_plots(sources=frames, plots=plot)
if stats:
print_stats(sources=frames, keys=stats, stats_sum=stats_sum)
if hist_threshold:
draw_hist_threshold(sources=frames, keys=hist_threshold, begin_frame=begin_frame,
threshold_name=threshold_name, threshold_value=threshold_value)
matplotlib.pyplot.show()
@ -92,19 +109,26 @@ def read_data(path):
frame[key] = to_number(value)
def collect_per_frame(sources, keys, begin_frame, end_frame):
def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name):
assert begin_frame < end_frame
result = collections.defaultdict(lambda: collections.defaultdict(list))
begin_frame = max(begin_frame, min(v[0][frame_number_name] for v in sources.values()))
end_frame = min(end_frame, begin_frame + max(len(v) for v in sources.values()))
for name in sources.keys():
for key in keys:
result[name][key] = [0] * (end_frame - begin_frame)
for name, frames in sources.items():
for frame in frames:
for key in keys:
if key in frame:
result[name][key].append(frame[key])
else:
result[name][key].append(None)
for name, sources in result.items():
for key, values in sources.items():
result[name][key] = numpy.array(values[begin_frame:end_frame])
return result
number = frame[frame_number_name]
if begin_frame <= number < end_frame:
index = number - begin_frame
for key in keys:
if key in frame:
result[name][key][index] = frame[key]
for name in result.keys():
for key in keys:
result[name][key] = numpy.array(result[name][key])
return result, begin_frame, end_frame
def collect_unique_keys(sources):
@ -116,12 +140,11 @@ def collect_unique_keys(sources):
return sorted(result)
def draw_timeseries(sources, keys, add_sum):
def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame):
fig, ax = matplotlib.pyplot.subplots()
x = numpy.array(range(begin_frame, end_frame))
for name, frames in sources.items():
x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys)))
for key in keys:
print(key, name)
ax.plot(x, frames[key], label=f'{key}:{name}')
if add_sum:
ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label=f'sum:{name}')
@ -130,10 +153,10 @@ def draw_timeseries(sources, keys, add_sum):
fig.canvas.set_window_title('timeseries')
def draw_commulative_timeseries(sources, keys, add_sum):
def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame):
fig, ax = matplotlib.pyplot.subplots()
x = numpy.array(range(begin_frame, end_frame))
for name, frames in sources.items():
x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys)))
for key in keys:
ax.plot(x, numpy.cumsum(frames[key]), label=f'{key}:{name}')
if add_sum:
@ -227,7 +250,6 @@ def print_stats(sources, keys, stats_sum):
if stats_sum:
stats.append(make_stats(source=name, key='sum', values=sum_multiple(frames, keys)))
metrics = list(stats[0].keys())
max_key_size = max(len(tuple(v.values())[0]) for v in stats)
termtables.print(
[list(v.values()) for v in stats],
header=metrics,
@ -235,6 +257,27 @@ def print_stats(sources, keys, stats_sum):
)
def draw_hist_threshold(sources, keys, begin_frame, threshold_name, threshold_value):
for name, frames in sources.items():
indices = [n for n, v in enumerate(frames[threshold_name]) if v > threshold_value]
numbers = [v + begin_frame for v in indices]
x = [v for v in range(0, len(indices))]
fig, ax = matplotlib.pyplot.subplots()
ax.set_title(f'Frames with "{threshold_name}" > {threshold_value} ({len(indices)})')
ax.bar(x, [frames[threshold_name][v] for v in indices], label=threshold_name, color='black', alpha=0.2)
prev = 0
for key in keys:
values = [frames[key][v] for v in indices]
ax.bar(x, values, bottom=prev, label=key)
prev = values
ax.hlines(threshold_value, x[0] - 1, x[-1] + 1, color='black', label='threshold', linestyles='dashed')
ax.xaxis.set_major_locator(matplotlib.pyplot.FixedLocator(x))
ax.xaxis.set_major_formatter(matplotlib.pyplot.FixedFormatter(numbers))
ax.grid(True)
ax.legend()
fig.canvas.set_window_title(f'hist_threshold:{name}')
def filter_not_none(values):
return [v for v in values if v is not None]
@ -269,5 +312,6 @@ def to_number(value):
except ValueError:
return float(value)
if __name__ == '__main__':
main()

Loading…
Cancel
Save