mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-25 11:26:37 +00:00 
			
		
		
		
	Merge branch 'master' into 'undead_intelligence'
# Conflicts: # CHANGELOG.md
This commit is contained in:
		
						commit
						908d196fee
					
				
					 22 changed files with 295 additions and 204 deletions
				
			
		|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -32,7 +32,6 @@ namespace CSVWidget | ||||||
| 
 | 
 | ||||||
| namespace CSVWorld | namespace CSVWorld | ||||||
| { | { | ||||||
|     class Table; |  | ||||||
|     class TableBottomBox; |     class TableBottomBox; | ||||||
|     class CreatorFactoryBase; |     class CreatorFactoryBase; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
							
								
								
									
										64
									
								
								apps/opencs/view/world/tableheadermouseeventhandler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								apps/opencs/view/world/tableheadermouseeventhandler.cpp
									
									
									
									
									
										Normal 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
 | ||||||
							
								
								
									
										25
									
								
								apps/opencs/view/world/tableheadermouseeventhandler.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								apps/opencs/view/world/tableheadermouseeventhandler.hpp
									
									
									
									
									
										Normal 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
 | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|                 } |                 } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
|  | @ -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 ¢erCell) const; |             osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) 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: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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)) | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|  |  | ||||||
|  | @ -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
 | ||||||
|  |  | ||||||
|  | @ -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() | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue