1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-28 19:21:04 +00:00

Merge branch 'master' into 'undead_intelligence'

# Conflicts:
#   CHANGELOG.md
This commit is contained in:
psi29a 2021-09-27 19:12:13 +00:00
commit 908d196fee
22 changed files with 295 additions and 204 deletions

View File

@ -37,8 +37,10 @@
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
Bug #6197: Infinite Casting Loop Bug #6197: Infinite Casting Loop
Bug #6273: Respawning NPCs rotation is inconsistent Bug #6273: Respawning NPCs rotation is inconsistent
Bug #6282: Laura craft doesn't follow the player character
Bug #6283: Avis Dorsey follows you after her death Bug #6283: Avis Dorsey follows you after her death
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 #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 #2780: A way to see current OpenMW version in the console
Feature #3616: Allow Zoom levels on the World Map Feature #3616: Allow Zoom levels on the World Map

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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

View File

@ -624,7 +624,7 @@ namespace MWPhysics
mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); 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); projectile->setPosition(newpos);
mTaskScheduler->updateSingleAabb(foundProjectile->second); mTaskScheduler->updateSingleAabb(foundProjectile->second);
} }
@ -686,7 +686,7 @@ namespace MWPhysics
mActors.emplace(ptr.mRef, std::move(actor)); 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); osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
assert(shapeInstance); assert(shapeInstance);
@ -694,7 +694,7 @@ namespace MWPhysics
mProjectileId++; 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)); mProjectiles.emplace(mProjectileId, std::move(projectile));
return mProjectileId; return mProjectileId;

View File

@ -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 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); 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 setCaster(int projectileId, const MWWorld::Ptr& caster);
void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void updateProjectile(const int projectileId, const osg::Vec3f &position) const;
void removeProjectile(const int projectileId); void removeProjectile(const int projectileId);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -164,8 +164,6 @@ namespace Compiler
std::string value; std::string value;
c.appendTo(value); c.appendTo(value);
bool error = false;
while (get (c)) while (get (c))
{ {
if (c.isDigit()) if (c.isDigit())
@ -174,16 +172,11 @@ namespace Compiler
} }
else if (!c.isMinusSign() && isStringCharacter (c)) else if (!c.isMinusSign() && isStringCharacter (c))
{ {
error = true; /// workaround that allows names to begin with digits
c.appendTo(value); return scanName(c, parser, cont, value);
} }
else if (c=='.') else if (c=='.')
{ {
if (error)
{
putback (c);
break;
}
return scanFloat (value, parser, cont); return scanFloat (value, parser, cont);
} }
else else
@ -193,17 +186,6 @@ namespace Compiler
} }
} }
if (error)
{
/// workaround that allows names to begin with digits
/// \todo disable
TokenLoc loc (mLoc);
mLoc.mLiteral.clear();
cont = parser.parseName (value, loc, *this);
return true;
// return false;
}
TokenLoc loc (mLoc); TokenLoc loc (mLoc);
mLoc.mLiteral.clear(); mLoc.mLiteral.clear();
@ -268,9 +250,8 @@ namespace Compiler
nullptr nullptr
}; };
bool Scanner::scanName (MultiChar& c, Parser& parser, bool& cont) bool Scanner::scanName (MultiChar& c, Parser& parser, bool& cont, std::string name)
{ {
std::string name;
c.appendTo(name); c.appendTo(name);
if (!scanName (name)) if (!scanName (name))

View File

@ -236,7 +236,7 @@ namespace Compiler
bool scanFloat (const std::string& intValue, Parser& parser, bool& cont); bool scanFloat (const std::string& intValue, Parser& parser, bool& cont);
bool scanName (MultiChar& c, Parser& parser, bool& cont); bool scanName (MultiChar& c, Parser& parser, bool& cont, std::string name = {});
/// \param name May contain the start of the name (one or more characters) /// \param name May contain the start of the name (one or more characters)
bool scanName (std::string& name); bool scanName (std::string& name);

View File

@ -659,22 +659,18 @@ namespace Resource
osg::ref_ptr<Shader::ShaderVisitor> shaderVisitor (createShaderVisitor()); osg::ref_ptr<Shader::ShaderVisitor> shaderVisitor (createShaderVisitor());
loaded->accept(*shaderVisitor); 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)) if (canOptimize(normalized))
{ {
SceneUtil::Optimizer optimizer; SceneUtil::Optimizer optimizer;
optimizer.setSharedStateManager(mSharedStateManager, &mSharedStateMutex);
optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); 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); optimizer.optimize(loaded, options);
} }
else
shareState(loaded);
if (compile && mIncrementalCompileOperation) if (compile && mIncrementalCompileOperation)
mIncrementalCompileOperation->add(loaded); mIncrementalCompileOperation->add(loaded);

View File

@ -30,6 +30,8 @@
#include <osg/io_utils> #include <osg/io_utils>
#include <osg/Depth> #include <osg/Depth>
#include <osgDB/SharedStateManager>
#include <osgUtil/TransformAttributeFunctor> #include <osgUtil/TransformAttributeFunctor>
#include <osgUtil/Statistics> #include <osgUtil/Statistics>
#include <osgUtil/MeshOptimizers> #include <osgUtil/MeshOptimizers>
@ -84,6 +86,13 @@ void Optimizer::optimize(osg::Node* node, unsigned int options)
cstv.removeTransforms(node); 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) if (options & REMOVE_REDUNDANT_NODES)
{ {
OSG_INFO<<"Optimizer::optimize() doing REMOVE_REDUNDANT_NODES"<<std::endl; 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 && if (transform->getNumChildren()==1 &&
transform->getChild(0)->asTransform()!=0 && transform->getChild(0)->asTransform()!=0 &&
transform->getChild(0)->asTransform()->asMatrixTransform()!=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. // now combine with its child.
osg::MatrixTransform* child = transform->getChild(0)->asTransform()->asMatrixTransform(); osg::MatrixTransform* child = transform->getChild(0)->asTransform()->asMatrixTransform();

View File

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

View File

@ -38,27 +38,41 @@ import termtables
@click.option('--timeseries_sum', is_flag=True, @click.option('--timeseries_sum', is_flag=True,
help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.') help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.')
@click.option('--commulative_timeseries_sum', is_flag=True, @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, @click.option('--stats_sum', is_flag=True,
help='Add a row to stats table for a sum per frame of all given stats metrics.') 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, @click.option('--begin_frame', type=int, default=0,
help='Start processing from this frame.') help='Start processing from this frame.')
@click.option('--end_frame', type=int, default=sys.maxsize, @click.option('--end_frame', type=int, default=sys.maxsize,
help='End processing at this frame.') 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) @click.argument('path', type=click.Path(), nargs=-1)
def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats,
timeseries_sum, stats_sum, begin_frame, end_frame, path, 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))} sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))}
keys = collect_unique_keys(sources) 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: if print_keys:
for v in keys: for v in keys:
print(v) print(v)
if timeseries: 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: 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: if hist:
draw_hists(sources=frames, keys=hist) draw_hists(sources=frames, keys=hist)
if hist_ratio: 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) draw_plots(sources=frames, plots=plot)
if stats: if stats:
print_stats(sources=frames, keys=stats, stats_sum=stats_sum) 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() matplotlib.pyplot.show()
@ -92,19 +109,26 @@ def read_data(path):
frame[key] = to_number(value) 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)) 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 name, frames in sources.items():
for frame in frames: for frame in frames:
for key in keys: number = frame[frame_number_name]
if key in frame: if begin_frame <= number < end_frame:
result[name][key].append(frame[key]) index = number - begin_frame
else: for key in keys:
result[name][key].append(None) if key in frame:
for name, sources in result.items(): result[name][key][index] = frame[key]
for key, values in sources.items(): for name in result.keys():
result[name][key] = numpy.array(values[begin_frame:end_frame]) for key in keys:
return result result[name][key] = numpy.array(result[name][key])
return result, begin_frame, end_frame
def collect_unique_keys(sources): def collect_unique_keys(sources):
@ -116,12 +140,11 @@ def collect_unique_keys(sources):
return sorted(result) 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() fig, ax = matplotlib.pyplot.subplots()
x = numpy.array(range(begin_frame, end_frame))
for name, frames in sources.items(): 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: for key in keys:
print(key, name)
ax.plot(x, frames[key], label=f'{key}:{name}') ax.plot(x, frames[key], label=f'{key}:{name}')
if add_sum: if add_sum:
ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label=f'sum:{name}') 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') 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() fig, ax = matplotlib.pyplot.subplots()
x = numpy.array(range(begin_frame, end_frame))
for name, frames in sources.items(): 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: for key in keys:
ax.plot(x, numpy.cumsum(frames[key]), label=f'{key}:{name}') ax.plot(x, numpy.cumsum(frames[key]), label=f'{key}:{name}')
if add_sum: if add_sum:
@ -227,7 +250,6 @@ def print_stats(sources, keys, stats_sum):
if stats_sum: if stats_sum:
stats.append(make_stats(source=name, key='sum', values=sum_multiple(frames, keys))) stats.append(make_stats(source=name, key='sum', values=sum_multiple(frames, keys)))
metrics = list(stats[0].keys()) metrics = list(stats[0].keys())
max_key_size = max(len(tuple(v.values())[0]) for v in stats)
termtables.print( termtables.print(
[list(v.values()) for v in stats], [list(v.values()) for v in stats],
header=metrics, 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): def filter_not_none(values):
return [v for v in values if v is not None] return [v for v in values if v is not None]
@ -269,5 +312,6 @@ def to_number(value):
except ValueError: except ValueError:
return float(value) return float(value)
if __name__ == '__main__': if __name__ == '__main__':
main() main()