diff --git a/AUTHORS.md b/AUTHORS.md index c48647f4e4..d9782ccf39 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -34,6 +34,7 @@ Programmers Ben Shealy (bentsherman) Bret Curtis (psi29a) Britt Mathis (galdor557) + Capostrophic cc9cii Chris Boyce (slothlife) Chris Robinson (KittyCat) @@ -60,6 +61,7 @@ Programmers Gašper Sedej gugus/gus Hallfaer Tuilinn + Haoda Wang (h313) hristoast Internecine Jacob Essex (Yacoby) diff --git a/CMakeLists.txt b/CMakeLists.txt index 884dd622d3..0050104cf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,40 @@ +# Apps and tools +option(BUILD_OPENMW "build OpenMW" ON) +option(BUILD_BSATOOL "build BSA extractor" ON) +option(BUILD_ESMTOOL "build ESM inspector" ON) +option(BUILD_LAUNCHER "build Launcher" ON) +option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) +option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON) +option(BUILD_OPENCS "build OpenMW Construction Set" ON) +option(BUILD_WIZARD "build Installation Wizard" ON) +option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) +option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) +option(BUILD_NIFTEST "build nif file tester" OFF) +option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON) +option(BUILD_DOCS "build documentation." OFF ) + +if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) + set(USE_QT FALSE) +else() + set(USE_QT TRUE) +endif() + +if (USE_QT) + set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") + set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5) +endif() + +if (APPLE) + # OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db + cmake_minimum_required(VERSION 3.1.0) +elseif (USE_QT AND DESIRED_QT_VERSION MATCHES 5) + # 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows. + cmake_minimum_required(VERSION 2.8.11) +else() + # We probably support older versions than this. + cmake_minimum_required(VERSION 2.6) +endif() + project(OpenMW) # If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them. @@ -59,21 +96,6 @@ option(QT_STATIC "Link static build of QT into the binaries" FALSE) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) -# Apps and tools -option(BUILD_OPENMW "build OpenMW" ON) -option(BUILD_BSATOOL "build BSA extractor" ON) -option(BUILD_ESMTOOL "build ESM inspector" ON) -option(BUILD_LAUNCHER "build Launcher" ON) -option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) -option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON) -option(BUILD_OPENCS "build OpenMW Construction Set" ON) -option(BUILD_WIZARD "build Installation Wizard" ON) -option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) -option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) -option(BUILD_NIFTEST "build nif file tester" OFF) -option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON) -option(BUILD_DOCS "build documentation." OFF ) - # what is necessary to build documentation IF( BUILD_DOCS ) # Builds the documentation. @@ -120,16 +142,8 @@ if (WIN32) option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) endif() -if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) - set(USE_QT FALSE) -else() - set(USE_QT TRUE) -endif() - # Dependencies if (USE_QT) - set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") - set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5) message(STATUS "Using Qt${DESIRED_QT_VERSION}") if (DESIRED_QT_VERSION MATCHES 4) @@ -144,17 +158,6 @@ if (USE_QT) endif() endif() -if (APPLE) - # OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db - cmake_minimum_required(VERSION 3.1.0) -elseif (USE_QT AND DESIRED_QT_VERSION MATCHES 5) - # 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows. - cmake_minimum_required(VERSION 2.8.11) -else() - # We probably support older versions than this. - cmake_minimum_required(VERSION 2.6) -endif() - # Sound setup find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE) # Required for building the FFmpeg headers @@ -206,8 +209,8 @@ if(NOT HAVE_STDINT_H) message(FATAL_ERROR "stdint.h was not found" ) endif() -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX osgShadow) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) set(USED_OSG_PLUGINS diff --git a/apps/bsatool/CMakeLists.txt b/apps/bsatool/CMakeLists.txt index 27baff8157..ec0615ff9c 100644 --- a/apps/bsatool/CMakeLists.txt +++ b/apps/bsatool/CMakeLists.txt @@ -4,7 +4,7 @@ set(BSATOOL source_group(apps\\bsatool FILES ${BSATOOL}) # Main executable -add_executable(bsatool +openmw_add_executable(bsatool ${BSATOOL} ) diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 1d5e662d83..122ca2f3af 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -8,7 +8,7 @@ set(ESMTOOL source_group(apps\\esmtool FILES ${ESMTOOL}) # Main executable -add_executable(esmtool +openmw_add_executable(esmtool ${ESMTOOL} ) diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index 93f53d0e8a..82182b7fae 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -28,7 +28,7 @@ set(ESSIMPORTER_FILES convertplayer.cpp ) -add_executable(openmw-essimporter +openmw_add_executable(openmw-essimporter ${ESSIMPORTER_FILES} ) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 8cbe18d513..aac076404f 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -78,7 +78,7 @@ if(NOT WIN32) endif(NOT WIN32) # Main executable -add_executable(openmw-launcher +openmw_add_executable(openmw-launcher ${GUI_TYPE} ${LAUNCHER} ${LAUNCHER_HEADER} diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 4bd6616859..e83656708b 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -9,7 +9,7 @@ set(MWINIIMPORT_HEADER source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER}) -add_executable(openmw-iniimporter +openmw_add_executable(openmw-iniimporter ${MWINIIMPORT} ) diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt index d7f0200d2a..3cbee2b7e8 100644 --- a/apps/niftest/CMakeLists.txt +++ b/apps/niftest/CMakeLists.txt @@ -4,7 +4,7 @@ set(NIFTEST source_group(components\\nif\\tests FILES ${NIFTEST}) # Main executable -add_executable(niftest +openmw_add_executable(niftest ${NIFTEST} ) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0a146dc06b..281921c81e 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -174,7 +174,7 @@ else() set (OPENCS_OPENMW_CFG "") endif(APPLE) -add_executable(openmw-cs +openmw_add_executable(openmw-cs MACOSX_BUNDLE ${OPENCS_SRC} ${OPENCS_UI_HDR} @@ -199,7 +199,7 @@ if(APPLE) RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}" OUTPUT_NAME ${OPENCS_BUNDLE_NAME} MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns" - MACOSX_BUNDLE_BUNDLE_NAME "OpenCS" + MACOSX_BUNDLE_BUNDLE_NAME "OpenMW-CS" MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index 6ae778fffe..c4b46958d7 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -132,7 +132,7 @@ namespace CSMPrefs if (mods && i == 0) { if (mods & Qt::ControlModifier) - result.append("Ctl+"); + result.append("Ctrl+"); if (mods & Qt::ShiftModifier) result.append("Shift+"); if (mods & Qt::AltModifier) @@ -196,7 +196,7 @@ namespace CSMPrefs std::string name = value.substr(start, end - start); - if (name == "Ctl") + if (name == "Ctrl") { mods |= Qt::ControlModifier; } diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 153de773cf..765c5b3161 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -275,12 +275,14 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int void CSVRender::Cell::pathgridModified() { - mPathgrid->recreateGeometry(); + if (mPathgrid) + mPathgrid->recreateGeometry(); } void CSVRender::Cell::pathgridRemoved() { - mPathgrid->removeGeometry(); + if (mPathgrid) + mPathgrid->removeGeometry(); } void CSVRender::Cell::reloadAssets() @@ -320,7 +322,7 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode) iter->second->setSelected (selected); } } - if (elementMask & Mask_Pathgrid) + if (mPathgrid && elementMask & Mask_Pathgrid) { // Only one pathgrid may be selected, so some operations will only have an effect // if the pathgrid is already focused @@ -420,7 +422,7 @@ std::vector > CSVRender::Cell::getSelection (un iter!=mObjects.end(); ++iter) if (iter->second->getSelected()) result.push_back (iter->second->getTag()); - if (elementMask & Mask_Pathgrid) + if (mPathgrid && elementMask & Mask_Pathgrid) if (mPathgrid->isSelected()) result.push_back(mPathgrid->getTag()); @@ -457,6 +459,6 @@ void CSVRender::Cell::reset (unsigned int elementMask) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) iter->second->reset(); - if (elementMask & Mask_Pathgrid) + if (mPathgrid && elementMask & Mask_Pathgrid) mPathgrid->resetIndicators(); } diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 228b2b5e76..a9cce02007 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -72,12 +72,15 @@ namespace CSVRender } else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos)) { - // Add node - QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); - QString description = "Add node"; + if (cell->getPathgrid()) + { + // Add node + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Add node"; - CSMWorld::CommandMacro macro(undoStack, description); - cell->getPathgrid()->applyPoint(macro, hitResult.worldPos); + CSMWorld::CommandMacro macro(undoStack, description); + cell->getPathgrid()->applyPoint(macro, hitResult.worldPos); + } } } @@ -205,7 +208,7 @@ namespace CSVRender WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); Cell* cell = getWorldspaceWidget().getCell(hit.worldPos); - if (cell) + if (cell && cell->getPathgrid()) { PathgridTag* tag = 0; if (hit.tag && (tag = dynamic_cast(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 3a801ffc3a..11c7f59264 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -221,8 +221,8 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste SceneWidget::~SceneWidget() { - // Since we're holding on to the scene templates past the existence of this graphics context, we'll need to manually release the created objects - mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); + // Since we're holding on to the resources past the existence of this graphics context, we'll need to manually release the created objects + mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); } void SceneWidget::setLighting(Lighting *lighting) diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index f68c69094e..2f1615c876 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -32,13 +32,19 @@ std::string CSVWorld::InfoCreator::getId() const void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const { - int index = - dynamic_cast (*getData().getTableModel (getCollectionId())). - findColumnIndex ( - getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? - CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal); + CSMWorld::IdTable& table = dynamic_cast (*getData().getTableModel (getCollectionId())); - command.addValue (index, mTopic->text()); + if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos) + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); + } + else + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + } } CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5fd52abae9..46eafe9b8c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -99,7 +99,7 @@ add_openmw_dir (mwbase # Main executable if (NOT ANDROID) - add_executable(openmw + openmw_add_executable(openmw ${OPENMW_FILES} ${GAME} ${GAME_HEADER} ${APPLE_BUNDLE_RESOURCES} diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index c921e17d07..f5ec86cc31 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -84,7 +84,6 @@ void OMW::Engine::frame(float frametime) try { mStartTick = mViewer->getStartTick(); - mEnvironment.setFrameDuration (frametime); // update input mEnvironment.getInputManager()->update(frametime, false); @@ -651,6 +650,8 @@ void OMW::Engine::go() Settings::Manager::getString("screenshot format", "General"))); mViewer->addEventHandler(mScreenCaptureHandler); + mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); + // Create encoder ToUTF8::Utf8Encoder encoder (mEncoding); mEncoder = &encoder; @@ -684,7 +685,6 @@ void OMW::Engine::go() // Start the main rendering loop osg::Timer frameTimer; double simulationTime = 0.0; - float framerateLimit = Settings::Manager::getFloat("framerate limit", "Video"); while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) { double dt = frameTimer.time_s(); @@ -697,6 +697,8 @@ void OMW::Engine::go() mViewer->advance(simulationTime); + mEnvironment.setFrameDuration(dt); + frame(dt); if (!mEnvironment.getInputManager()->isWindowVisible()) @@ -714,15 +716,7 @@ void OMW::Engine::go() mViewer->renderingTraversals(); } - if (framerateLimit > 0.f) - { - double thisFrameTime = frameTimer.time_s(); - double minFrameTime = 1.0 / framerateLimit; - if (thisFrameTime < minFrameTime) - { - OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime)); - } - } + mEnvironment.limitFrameRate(frameTimer.time_s()); } // Save user settings diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 4efa7c2737..5d01525b96 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -2,6 +2,8 @@ #include +#include + #include "world.hpp" #include "scriptmanager.hpp" #include "dialoguemanager.hpp" @@ -17,7 +19,7 @@ MWBase::Environment *MWBase::Environment::sThis = 0; MWBase::Environment::Environment() : mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0), mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mStateManager (0), - mFrameDuration (0) + mFrameDuration (0), mFrameRateLimit(0.f) { assert (!sThis); sThis = this; @@ -79,6 +81,29 @@ void MWBase::Environment::setFrameDuration (float duration) mFrameDuration = duration; } +void MWBase::Environment::setFrameRateLimit(float limit) +{ + mFrameRateLimit = limit; +} + +float MWBase::Environment::getFrameRateLimit() const +{ + return mFrameRateLimit; +} + +void MWBase::Environment::limitFrameRate(double dt) const +{ + if (mFrameRateLimit > 0.f) + { + double thisFrameTime = dt; + double minFrameTime = 1.0 / static_cast(mFrameRateLimit); + if (thisFrameTime < minFrameTime) + { + OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime)); + } + } +} + MWBase::World *MWBase::Environment::getWorld() const { assert (mWorld); diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 7f7919f813..9163b21f3c 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -33,6 +33,7 @@ namespace MWBase InputManager *mInputManager; StateManager *mStateManager; float mFrameDuration; + float mFrameRateLimit; Environment (const Environment&); ///< not implemented @@ -67,6 +68,10 @@ namespace MWBase void setFrameDuration (float duration); ///< Set length of current frame in seconds. + void setFrameRateLimit(float frameRateLimit); + float getFrameRateLimit() const; + void limitFrameRate(double dt) const; + World *getWorld() const; SoundManager *getSoundManager() const; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index f5013a8f3d..84d43156e1 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -223,6 +223,7 @@ namespace MWBase virtual void keepPlayerAlive() = 0; virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0; + virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0; virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0; @@ -246,6 +247,7 @@ namespace MWBase virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0; + virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0; virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; }; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 416a7ad872..4560ab2700 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -212,7 +212,10 @@ namespace MWBase virtual void setSpellVisibility(bool visible) = 0; virtual void setSneakVisibility(bool visible) = 0; - virtual void activateQuickKey (int index) = 0; + /// activate selected quick key + virtual void activateQuickKey (int index) = 0; + /// update activated quick key state (if action executing was delayed for some reason) + virtual void updateActivatedQuickKey () = 0; virtual std::string getSelectedSpell() = 0; virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 0; @@ -357,7 +360,7 @@ namespace MWBase // In WindowManager for now since there isn't a VFS singleton virtual std::string correctIconPath(const std::string& path) = 0; - virtual std::string correctBookartPath(const std::string& path, int width, int height) = 0; + virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr) = 0; virtual std::string correctTexturePath(const std::string& path) = 0; virtual bool textureExists(const std::string& path) = 0; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index b8f6f5ecb2..15499c1a8a 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -3,6 +3,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -155,6 +156,16 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } + std::pair Lockpick::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + { + // Do not allow equip tools from inventory during attack + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) + && MWBase::Environment::get().getWindowManager()->isGuiMode()) + return std::make_pair(0, "#{sCantEquipWeapWarning}"); + + return std::make_pair(1, ""); + } + bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Picks) != 0; diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index efa675c950..1bcf7fb85d 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -51,6 +51,8 @@ namespace MWClass virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; + virtual std::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index ecaa056f9d..030ee3f8bf 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -3,6 +3,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -155,6 +156,16 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } + std::pair Probe::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const + { + // Do not allow equip tools from inventory during attack + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) + && MWBase::Environment::get().getWindowManager()->isGuiMode()) + return std::make_pair(0, "#{sCantEquipWeapWarning}"); + + return std::make_pair(1, ""); + } + bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Probes) != 0; diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index f67d8af863..9ac3ab0c9e 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -51,6 +51,8 @@ namespace MWClass virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; + virtual std::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index efb6248afa..9fb4a97678 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -4,6 +4,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -373,6 +374,11 @@ namespace MWClass if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) return std::make_pair(0, "#{sInventoryMessage1}"); + // Do not allow equip weapons from inventory during attack + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) + && MWBase::Environment::get().getWindowManager()->isGuiMode()) + return std::make_pair(0, "#{sCantEquipWeapWarning}"); + std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); if (slots_.first.empty()) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index f478dad7e6..2e80301d2d 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -634,7 +634,7 @@ namespace MWGui void DialogueWindow::onFrame() { - if(mMainWidget->getVisible() && mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name()) + if(mMainWidget->getVisible() && mPtr.getTypeName() == typeid(ESM::NPC).name()) { int disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr); mDispositionBar->setProgressRange(100); diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 72e1c09f3f..cf4a5b589a 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -275,8 +275,6 @@ namespace MWGui { case BookTextParser::Event_ImgTag: { - pag.setIgnoreLeadingEmptyLines(false); - const BookTextParser::Attributes & attr = parser.getAttributes(); if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end()) @@ -286,8 +284,19 @@ namespace MWGui int width = MyGUI::utility::parseInt(attr.at("width")); int height = MyGUI::utility::parseInt(attr.at("height")); + bool exists; + std::string correctedSrc = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, height, &exists); + + if (!exists) + { + std::cerr << "Warning: Could not find \"" << src << "\" referenced by an tag." << std::endl; + break; + } + + pag.setIgnoreLeadingEmptyLines(false); + ImageElement elem(paper, pag, mBlockStyle, - src, width, height); + correctedSrc, width, height); elem.paginate(); break; } @@ -471,8 +480,7 @@ namespace MWGui MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, parent->getName() + MyGUI::utility::toString(parent->getChildCount())); - std::string image = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, mImageHeight); - mImageBox->setImageTexture(image); + mImageBox->setImageTexture(src); mImageBox->setProperty("NeedMouse", "false"); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 84e5794e37..0e1b2cc89d 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -179,29 +179,33 @@ namespace MWGui void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { - int current = std::max(0, static_cast(value.getCurrent())); + int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified()); + // Fatigue can be negative + if (id != "FBar") + current = std::max(0, current); + MyGUI::Widget* w; std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); if (id == "HBar") { - mHealth->setProgressRange(modified); - mHealth->setProgressPosition(current); + mHealth->setProgressRange(std::max(0, modified)); + mHealth->setProgressPosition(std::max(0, current)); getWidget(w, "HealthFrame"); w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); } else if (id == "MBar") { - mMagicka->setProgressRange (modified); - mMagicka->setProgressPosition (current); + mMagicka->setProgressRange(std::max(0, modified)); + mMagicka->setProgressPosition(std::max(0, current)); getWidget(w, "MagickaFrame"); w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } else if (id == "FBar") { - mStamina->setProgressRange (modified); - mStamina->setProgressPosition (current); + mStamina->setProgressRange(std::max(0, modified)); + mStamina->setProgressPosition(std::max(0, current)); getWidget(w, "FatigueFrame"); w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 8b0f895d1f..6a2d3ff83a 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -29,6 +29,7 @@ #include "../mwrender/characterpreview.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "itemview.hpp" #include "inventoryitemmodel.hpp" @@ -254,6 +255,19 @@ namespace MWGui } } + // If we unequip weapon during attack, it can lead to unexpected behaviour + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr)) + { + bool isWeapon = item.mBase.getTypeName() == typeid(ESM::Weapon).name(); + MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); + + if (isWeapon && invStore.isEquipped(item.mBase)) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sCantEquipWeapWarning}"); + return; + } + } + if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); @@ -660,9 +674,18 @@ namespace MWGui void InventoryWindow::cycle(bool next) { + MWWorld::Ptr player = MWMechanics::getPlayer(); + + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)) + return; + + const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); + if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) + return; + ItemModel::ModelIndex selected = -1; // not using mSortFilterModel as we only need sorting, not filtering - SortFilterItemModel model(new InventoryItemModel(MWMechanics::getPlayer())); + SortFilterItemModel model(new InventoryItemModel(player)); model.setSortByType(false); model.update(); if (model.getItemCount() == 0) diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 362ad3b1c9..da8e932795 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -143,10 +143,10 @@ namespace MWGui mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); std::string levelupdescription; - if(level > 20) + levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level)); + + if (levelupdescription == "") levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default"); - else - levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level)); mLevelDescription->setCaption (levelupdescription); diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index ca6a0b0a48..c5836b6530 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -102,6 +102,15 @@ namespace MWGui mBackgroundImage->setVisible(visible); } + double LoadingScreen::getTargetFrameRate() const + { + double frameRateLimit = MWBase::Environment::get().getFrameRateLimit(); + if (frameRateLimit > 0) + return std::min(frameRateLimit, mTargetFrameRate); + else + return mTargetFrameRate; + } + class CopyFramebufferToTextureCallback : public osg::Camera::DrawCallback { public: @@ -141,7 +150,7 @@ namespace MWGui if (mViewer->getIncrementalCompileOperation()) { mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100); - mViewer->getIncrementalCompileOperation()->setTargetFrameRate(mTargetFrameRate); + mViewer->getIncrementalCompileOperation()->setTargetFrameRate(getTargetFrameRate()); } // Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after each frame of loading @@ -210,7 +219,7 @@ namespace MWGui void LoadingScreen::setProgress (size_t value) { // skip expensive update if there isn't enough visible progress - if (value - mProgress < mProgressBar->getScrollRange()/200.f) + if (mProgressBar->getWidth() <= 0 || value - mProgress < mProgressBar->getScrollRange()/mProgressBar->getWidth()) return; value = std::min(value, mProgressBar->getScrollRange()-1); mProgress = value; @@ -231,7 +240,7 @@ namespace MWGui bool LoadingScreen::needToDrawLoadingScreen() { - if ( mTimer.time_m() <= mLastRenderTime + (1.0/mTargetFrameRate) * 1000.0) + if ( mTimer.time_m() <= mLastRenderTime + (1.0/getTargetFrameRate()) * 1000.0) return false; // the minimal delay before a loading screen shows diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 100c17e11e..2f8831fdc5 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -43,6 +43,8 @@ namespace MWGui virtual void setVisible(bool visible); + double getTargetFrameRate() const; + private: void findSplashScreens(); bool needToDrawLoadingScreen(); @@ -73,8 +75,6 @@ namespace MWGui std::vector mSplashScreens; - // TODO: add releaseGLObjects() for mTexture - osg::ref_ptr mTexture; std::unique_ptr mGuiTexture; diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 619540cff3..e912193bf3 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -14,6 +14,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -36,6 +37,7 @@ namespace MWGui , mItemSelectionDialog(0) , mMagicSelectionDialog(0) , mSelectedIndex(-1) + , mActivatedIndex(-1) { getWidget(mOkButton, "OKButton"); getWidget(mInstructionLabel, "InstructionLabel"); @@ -69,6 +71,8 @@ namespace MWGui void QuickKeysMenu::clear() { + mActivatedIndex = -1; + for (int i=0; i<10; ++i) { unassign(mQuickKeyButtons[i], i); @@ -254,6 +258,15 @@ namespace MWGui mMagicSelectionDialog->setVisible(false); } + void QuickKeysMenu::updateActivatedQuickKey() + { + // there is no delayed action, nothing to do. + if (mActivatedIndex < 0) + return; + + activateQuickKey(mActivatedIndex); + } + void QuickKeysMenu::activateQuickKey(int index) { assert (index-1 >= 0); @@ -263,6 +276,27 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); + + // Delay action executing, + // if player is busy for now (casting a spell, attacking someone, etc.) + bool isDelayNeeded = MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player) + || playerStats.getKnockedDown() + || playerStats.getHitRecovery(); + + bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead(); + if (isReturnNeeded && type != Type_Item) + { + return; + } + + if (isDelayNeeded && type != Type_Item) + { + mActivatedIndex = index; + return; + } + else + mActivatedIndex = -1; if (type == Type_Item || type == Type_MagicItem) { @@ -309,6 +343,22 @@ namespace MWGui else if (type == Type_Item) { MWWorld::Ptr item = *button->getUserData(); + bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); + bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || item.getTypeName() == typeid(ESM::Lockpick).name(); + + // delay weapon switching if player is busy + if (isDelayNeeded && (isWeapon || isTool)) + { + mActivatedIndex = index; + return; + } + + // disable weapon switching if player is dead or paralyzed + if (isReturnNeeded && (isWeapon || isTool)) + { + return; + } + MWBase::Environment::get().getWindowManager()->useItem(item); MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); // change draw state only if the item is in player's right hand diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index afbcff001e..64db9043e0 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -36,6 +36,7 @@ namespace MWGui void onAssignMagicCancel (); void activateQuickKey(int index); + void updateActivatedQuickKey(); /// @note This enum is serialized, so don't move the items around! enum QuickKeyType @@ -64,7 +65,7 @@ namespace MWGui MagicSelectionDialog* mMagicSelectionDialog; int mSelectedIndex; - + int mActivatedIndex; void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 1a680b8017..bf18e7355e 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -180,7 +180,7 @@ namespace MWGui void ReviewDialog::setFatigue(const MWMechanics::DynamicStat& value) { - int current = std::max(0, static_cast(value.getCurrent())); + int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified()); mFatigue->setValue(current, modified); diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 0c3485e6ad..5ce3fd1fe6 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -9,6 +9,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -195,6 +196,15 @@ namespace MWGui void SpellWindow::cycle(bool next) { + MWWorld::Ptr player = MWMechanics::getPlayer(); + + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)) + return; + + const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); + if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) + return; + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); SpellModel::ModelIndex selected = 0; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 22140b8b2c..17e51e3383 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -102,12 +102,13 @@ namespace MWGui { MyGUI::ProgressBar* pt; getWidget(pt, name); - pt->setProgressRange(max); - pt->setProgressPosition(val); std::stringstream out; out << val << "/" << max; setText(tname, out.str().c_str()); + + pt->setProgressRange(std::max(0, max)); + pt->setProgressPosition(std::max(0, val)); } void StatsWindow::setPlayerName(const std::string& playerName) @@ -147,9 +148,13 @@ namespace MWGui void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { - int current = std::max(0, static_cast(value.getCurrent())); + int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified()); + // Fatigue can be negative + if (id != "FBar") + current = std::max(0, current); + setBar (id, id + "T", current, modified); // health, magicka, fatigue tooltip diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 695337cde4..744ef236fa 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -502,11 +502,10 @@ namespace MWGui if (mBarWidget) { - mBarWidget->setProgressRange(mMax); - mBarWidget->setProgressPosition(mValue); + mBarWidget->setProgressRange(std::max(0, mMax)); + mBarWidget->setProgressPosition(std::max(0, mValue)); } - if (mBarTextWidget) { std::stringstream out; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e4f6eafdee..4b7b3c3870 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -518,6 +518,8 @@ namespace MWGui cleanupGarbage(); mHud->update(); + + updateActivatedQuickKey (); } void WindowManager::updateVisible() @@ -904,19 +906,30 @@ namespace MWGui if (block) { + osg::Timer frameTimer; while (mMessageBoxManager->readPressedButton(false) == -1 && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - mMessageBoxManager->onFrame(0.f); - MWBase::Environment::get().getInputManager()->update(0, true, false); + double dt = frameTimer.time_s(); + frameTimer.setStartTick(); + mMessageBoxManager->onFrame(dt); + MWBase::Environment::get().getInputManager()->update(dt, true, false); + + if (!MWBase::Environment::get().getInputManager()->isWindowVisible()) + OpenThreads::Thread::microSleep(5000); + else + { + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + } // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + + MWBase::Environment::get().limitFrameRate(frameTimer.time_s()); } } } @@ -1528,6 +1541,11 @@ namespace MWGui mHud->setCrosshairVisible (show && mCrosshairEnabled); } + void WindowManager::updateActivatedQuickKey () + { + mQuickKeysMenu->updateActivatedQuickKey(); + } + void WindowManager::activateQuickKey (int index) { mQuickKeysMenu->activateQuickKey(index); @@ -1831,18 +1849,28 @@ namespace MWGui if (mVideoWidget->hasAudioStream()) MWBase::Environment::get().getSoundManager()->pauseSounds( MWBase::SoundManager::Play_TypeMask&(~MWBase::SoundManager::Play_TypeMovie)); - + osg::Timer frameTimer; while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - MWBase::Environment::get().getInputManager()->update(0, true, false); + double dt = frameTimer.time_s(); + frameTimer.setStartTick(); + MWBase::Environment::get().getInputManager()->update(dt, true, false); + + if (!MWBase::Environment::get().getInputManager()->isWindowVisible()) + OpenThreads::Thread::microSleep(5000); + else + { + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + } // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + + MWBase::Environment::get().limitFrameRate(frameTimer.time_s()); } mVideoWidget->stop(); @@ -2062,9 +2090,12 @@ namespace MWGui return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS()); } - std::string WindowManager::correctBookartPath(const std::string& path, int width, int height) + std::string WindowManager::correctBookartPath(const std::string& path, int width, int height, bool* exists) { - return Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS()); + std::string corrected = Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS()); + if (exists) + *exists = mResourceSystem->getVFS()->exists(corrected); + return corrected; } std::string WindowManager::correctTexturePath(const std::string& path) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index f74ba21a3e..4f06afb7d9 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -241,7 +241,10 @@ namespace MWGui virtual void setSpellVisibility(bool visible); virtual void setSneakVisibility(bool visible); - virtual void activateQuickKey (int index); + /// activate selected quick key + virtual void activateQuickKey (int index); + /// update activated quick key state (if action executing was delayed for some reason) + virtual void updateActivatedQuickKey (); virtual std::string getSelectedSpell() { return mSelectedSpell; } virtual void setSelectedSpell(const std::string& spellId, int successChancePercent); @@ -385,7 +388,7 @@ namespace MWGui // In WindowManager for now since there isn't a VFS singleton virtual std::string correctIconPath(const std::string& path); - virtual std::string correctBookartPath(const std::string& path, int width, int height); + virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr); virtual std::string correctTexturePath(const std::string& path); virtual bool textureExists(const std::string& path); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index a823ae0fd6..e4fa8fc2b4 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -20,6 +20,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -929,6 +930,9 @@ namespace MWInput inventory.getSelectedEnchantItem() == inventory.end()) return; + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer())) + return; + MWMechanics::DrawState_ state = mPlayer->getDrawState(); if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing) mPlayer->setDrawState(MWMechanics::DrawState_Spell); @@ -944,6 +948,13 @@ namespace MWInput if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"]) return; + // We want to interrupt animation only if attack is prepairing, but still is not triggered + // Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice + if (MWBase::Environment::get().getMechanicsManager()->isAttackPrepairing(mPlayer->getPlayer())) + mPlayer->setAttackingOrSpell(false); + else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer())) + return; + MWMechanics::DrawState_ state = mPlayer->getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) mPlayer->setDrawState(MWMechanics::DrawState_Weapon); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 630e18e803..d15e1a1a59 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -394,10 +394,15 @@ namespace MWMechanics aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); } } - + // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter if (actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc()) { + // Check if the creature is too far + static const float fAlarmRadius = MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->getFloat(); + if (sqrDist > fAlarmRadius * fAlarmRadius) + return; + bool followerOrEscorter = false; for (std::list::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it) { @@ -802,6 +807,16 @@ namespace MWMechanics } } + bool Actors::isAttackPrepairing(const MWWorld::Ptr& ptr) + { + PtrActorMap::iterator it = mActors.find(ptr); + if (it == mActors.end()) + return false; + CharacterController* ctrl = it->second->getCharacterController(); + + return ctrl->isAttackPrepairing(); + } + bool Actors::isRunning(const MWWorld::Ptr& ptr) { PtrActorMap::iterator it = mActors.find(ptr); @@ -1788,6 +1803,16 @@ namespace MWMechanics return it->second->getCharacterController()->isReadyToBlock(); } + bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const + { + PtrActorMap::const_iterator it = mActors.find(ptr); + if (it == mActors.end()) + return false; + CharacterController* ctrl = it->second->getCharacterController(); + + return ctrl->isAttackingOrSpell(); + } + void Actors::fastForwardAi() { if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 59e4229280..6eb3a29556 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -107,6 +107,7 @@ namespace MWMechanics int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. + bool isAttackPrepairing(const MWWorld::Ptr& ptr); bool isRunning(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr); @@ -150,6 +151,7 @@ namespace MWMechanics void clear(); // Clear death counter bool isReadyToBlock(const MWWorld::Ptr& ptr) const; + bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const; private: PtrActorMap mActors; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 1244686413..48705dc72b 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -262,22 +262,16 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co void MWMechanics::Alchemy::removeIngredients() { - bool needsUpdate = false; - for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) if (!iter->isEmpty()) { iter->getContainerStore()->remove(*iter, 1, mAlchemist); if (iter->getRefData().getCount()<1) - { - needsUpdate = true; *iter = MWWorld::Ptr(); - } } - if (needsUpdate) - updateEffects(); + updateEffects(); } void MWMechanics::Alchemy::addPotion (const std::string& name) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 25d04d1763..51dc37e185 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2218,6 +2218,12 @@ void CharacterController::setAttackTypeBasedOnMovement() mAttackType = "chop"; } +bool CharacterController::isAttackPrepairing() const +{ + return mUpperBodyState == UpperCharState_StartToMinAttack || + mUpperBodyState == UpperCharState_MinAttackToMaxAttack; +} + bool CharacterController::isReadyToBlock() const { return updateCarriedLeftVisible(mWeaponType); @@ -2228,6 +2234,12 @@ bool CharacterController::isKnockedOut() const return mHitState == CharState_KnockOut; } +bool CharacterController::isAttackingOrSpell() const +{ + return mUpperBodyState != UpperCharState_Nothing && + mUpperBodyState != UpperCharState_WeapEquiped; +} + bool CharacterController::isSneaking() const { return mIdleState == CharState_IdleSneak || diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 66039bf5d7..9bcad09949 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -263,10 +263,12 @@ public: void forceStateUpdate(); + bool isAttackPrepairing() const; bool isReadyToBlock() const; bool isKnockedOut() const; bool isSneaking() const; bool isRunning() const; + bool isAttackingOrSpell() const; void setAttackingOrSpell(bool attackingOrSpell); void setAIAttackType(const std::string& attackType); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 68115301f6..5f3dd58af3 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -423,6 +423,11 @@ namespace MWMechanics mObjects.update(duration, paused); } + bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr) + { + return mActors.isAttackPrepairing(ptr); + } + bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr) { return mActors.isRunning(ptr); @@ -1591,6 +1596,11 @@ namespace MWMechanics return mActors.isReadyToBlock(ptr); } + bool MechanicsManager::isAttackingOrSpell(const MWWorld::Ptr &ptr) const + { + return mActors.isAttackingOrSpell(ptr); + } + void MechanicsManager::setWerewolf(const MWWorld::Ptr& actor, bool werewolf) { MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index bba2c55d62..adad219164 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -188,6 +188,8 @@ namespace MWMechanics virtual void keepPlayerAlive(); virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const; + /// Is \a ptr casting spell or using weapon now? + virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const; virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer); @@ -208,8 +210,10 @@ namespace MWMechanics virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count); + virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr); virtual bool isRunning(const MWWorld::Ptr& ptr); virtual bool isSneaking(const MWWorld::Ptr& ptr); + private: void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 5956352069..f0fc7fb6e7 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -380,7 +380,7 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int for (int i=0; i<7; ++i) { if (faction.mData.mSkills[i] != -1) - skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getModified())); + skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getBase())); } if (skills.empty()) diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 07cf6ff5ff..d06e73c932 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -13,6 +13,7 @@ #include "combat.hpp" #include "aicombataction.hpp" #include "spellpriority.hpp" +#include "spellcasting.hpp" namespace MWMechanics { @@ -90,10 +91,13 @@ namespace MWMechanics if (!weapon->mEnchant.empty()) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(weapon->mEnchant); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes - && (item.getCellRef().getEnchantmentCharge() == -1 - || item.getCellRef().getEnchantmentCharge() >= enchantment->mData.mCost)) - rating += rateEffects(enchantment->mEffects, actor, enemy); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); + + if (item.getCellRef().getEnchantmentCharge() == -1 || item.getCellRef().getEnchantmentCharge() >= castCost) + rating += rateEffects(enchantment->mEffects, actor, enemy); + } } int skill = item.getClass().getEquipmentSkill(item); diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index e5e36def8a..79c6dcabfc 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -18,7 +18,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) : mCanWaterWalk(false), mWalkingOnWater(false) - , mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(false), mOnSlope(false) + , mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) , mCollisionWorld(world) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 98d29bfc9f..df9b8545a8 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -182,7 +182,10 @@ namespace void remove() { for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) - it->second->removeChild(it->first); + { + if (!it->second->removeChild(it->first)) + std::cerr << "error removing " << it->first->getName() << std::endl; + } } protected: @@ -1192,6 +1195,9 @@ namespace MWRender mObjectRoot->addChild(created); mInsert->addChild(mObjectRoot); } + osg::ref_ptr skel = dynamic_cast(mObjectRoot.get()); + if (skel) + mSkeleton = skel.get(); } else { diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 2ad362b335..735c0b66d3 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -118,7 +118,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item)); const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename)); + NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get()); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 73927e0ab5..e4d0abf7bb 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -23,6 +23,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" @@ -919,6 +920,9 @@ void NpcAnimation::showWeapons(bool showWeapon) else { removeIndividualPart(ESM::PRT_Weapon); + // If we remove/hide weapon from player, we should reset attack animation as well + if (mPtr == MWMechanics::getPlayer()) + MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 6b1953917e..70910ec2f0 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1189,6 +1189,14 @@ namespace MWScript if (mNegativeEffect != -1) currentValue -= effects.get(mNegativeEffect).getMagnitude(); + // GetResist* should take in account elemental shields + if (mPositiveEffect == ESM::MagicEffect::ResistFire) + currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude(); + if (mPositiveEffect == ESM::MagicEffect::ResistShock) + currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude(); + if (mPositiveEffect == ESM::MagicEffect::ResistFrost) + currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude(); + int ret = static_cast(currentValue); runtime.push(ret); } @@ -1215,6 +1223,14 @@ namespace MWScript if (mNegativeEffect != -1) currentValue -= effects.get(mNegativeEffect).getMagnitude(); + // SetResist* should take in account elemental shields + if (mPositiveEffect == ESM::MagicEffect::ResistFire) + currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude(); + if (mPositiveEffect == ESM::MagicEffect::ResistShock) + currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude(); + if (mPositiveEffect == ESM::MagicEffect::ResistFrost) + currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude(); + int arg = runtime[0].mInteger; runtime.pop(); effects.modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index c6f663059d..15b95b2339 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -271,7 +272,6 @@ namespace MWSound return sound; } - // Gets the combined volume settings for the given sound type float SoundManager::volumeFromType(PlayType type) const { @@ -298,7 +298,6 @@ namespace MWSound return volume; } - void SoundManager::stopMusic() { if(mMusic) @@ -349,6 +348,7 @@ namespace MWSound void SoundManager::startRandomTitle() { std::vector filelist; + auto &tracklist = mMusicToPlay[mCurrentPlaylist]; if (mMusicFiles.find(mCurrentPlaylist) == mMusicFiles.end()) { const std::map& index = mVFS->getIndex(); @@ -367,7 +367,6 @@ namespace MWSound } mMusicFiles[mCurrentPlaylist] = filelist; - } else filelist = mMusicFiles[mCurrentPlaylist]; @@ -375,15 +374,25 @@ namespace MWSound if(filelist.empty()) return; - int i = Misc::Rng::rollDice(filelist.size()); + // Do a Fisher-Yates shuffle - // Don't play the same music track twice in a row - if (filelist[i] == mLastPlayedMusic) + // Repopulate if playlist is empty + if(tracklist.empty()) { - i = (i+1) % filelist.size(); + tracklist.resize(filelist.size()); + std::iota(tracklist.begin(), tracklist.end(), 0); } - advanceMusic(filelist[i]); + int i = Misc::Rng::rollDice(tracklist.size()); + + // Reshuffle if last played music is the same after a repopulation + if(filelist[tracklist[i]] == mLastPlayedMusic) + i = (i+1) % tracklist.size(); + + // Remove music from list after advancing music + advanceMusic(filelist[tracklist[i]]); + tracklist[i] = tracklist.back(); + tracklist.pop_back(); } bool SoundManager::isMusicPlaying() @@ -570,6 +579,9 @@ namespace MWSound if((mode&Play_RemoveAtDistance) && (mListenerPos-objpos).length2() > 2000*2000) return MWBase::SoundPtr(); + // Only one copy of given sound can be played at time on ptr, so stop previous copy + stopSound3D(ptr, soundId); + if(!(mode&Play_NoPlayerLocal) && ptr == MWMechanics::getPlayer()) { sound.reset(new Sound(volume * sfx->mVolume, basevol, pitch, mode|type|Play_2D)); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 5d911d4bec..691e52932e 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -49,6 +50,7 @@ namespace MWSound // Caches available music tracks by std::map > mMusicFiles; + std::unordered_map> mMusicToPlay; // A list with music files not yet played std::string mLastPlayedMusic; // The music file that was last played float mMasterVolume; diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index 46bd7d3f96..0f2d807aae 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "components/loadinglistener/loadinglistener.hpp" @@ -24,7 +25,7 @@ struct ContentLoader virtual void load(const boost::filesystem::path& filepath, int& index) { std::cout << "Loading content file " << filepath.string() << std::endl; - mListener.setLabel(filepath.string()); + mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); } protected: diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index ea4b1209c6..9b09bc41f7 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -17,7 +17,7 @@ if (GTEST_FOUND) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) + openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) target_link_libraries(openmw_test_suite ${GTEST_BOTH_LIBRARIES} components) # Fix for not visible pthreads functions for linker with glibc 2.15 diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index d2b9ab0f6d..5f7338e520 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -96,7 +96,7 @@ if (OPENMW_USE_UNSHIELD) include_directories(${LIBUNSHIELD_INCLUDE_DIRS}) endif() -add_executable(openmw-wizard +openmw_add_executable(openmw-wizard ${GUI_TYPE} ${WIZARD} ${WIZARD_HEADER} diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index c5669fa70c..6573265bd7 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -142,3 +142,32 @@ foreach (u ${ARGN}) add_hdr (OPENCS ${dir} ${u}) endforeach (u) endmacro (opencs_hdrs_noqt) + +include(CMakeParseArguments) + +macro (openmw_add_executable target) + set(OMW_ADD_EXE_OPTIONS WIN32 MACOSX_BUNDLE EXCLUDE_FROM_ALL) + set(OMW_ADD_EXE_VALUES) + set(OMW_ADD_EXE_MULTI_VALUES) + cmake_parse_arguments(OMW_ADD_EXE "${OMW_ADD_EXE_OPTIONS}" "${OMW_ADD_EXE_VALUES}" "${OMW_ADD_EXE_MULTI_VALUES}" ${ARGN}) + + if (OMW_ADD_EXE_WIN32) + set(OMW_ADD_EXE_WIN32_VALUE WIN32) + endif (OMW_ADD_EXE_WIN32) + + if (OMW_ADD_EXE_MACOSX_BUNDLE) + set(OMW_ADD_EXE_MACOSX_BUNDLE_VALUE MACOSX_BUNDLE) + endif (OMW_ADD_EXE_MACOSX_BUNDLE) + + if (OMW_ADD_EXE_EXCLUDE_FROM_ALL) + set(OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE EXCLUDE_FROM_ALL) + endif (OMW_ADD_EXE_EXCLUDE_FROM_ALL) + + add_executable(${target} ${OMW_ADD_EXE_WIN32_VALUE} ${OMW_ADD_EXE_MACOSX_BUNDLE_VALUE} ${OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE} ${OMW_ADD_EXE_UNPARSED_ARGUMENTS}) + + if (MSVC) + if (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8) + set_target_properties(${target} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$(TargetDir)") + endif (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8) + endif (MSVC) +endmacro (openmw_add_executable) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6430dbfce8..88de179039 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -49,7 +49,7 @@ add_component_dir (shader ) add_component_dir (sceneutil - clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller + clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer ) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 87a0b830e5..262966e957 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -6,11 +6,10 @@ #include #include -#include - #include #include +#include #include "userdata.hpp" @@ -188,7 +187,7 @@ GeomMorpherController::GeomMorpherController(const Nif::NiMorphData *data) void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable) { - osgAnimation::MorphGeometry* morphGeom = static_cast(drawable); + SceneUtil::MorphGeometry* morphGeom = static_cast(drawable); if (hasInput()) { if (mKeyFrames.size() <= 1) @@ -202,7 +201,7 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable val = it->interpKey(input); val = std::max(0.f, std::min(1.f, val)); - osgAnimation::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i); + SceneUtil::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i); if (target.getWeight() != val) { target.setWeight(val); @@ -210,8 +209,6 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable } } } - - // morphGeometry::transformSoftwareMethod() done in cull callback i.e. only for visible morph geometries } UVController::UVController() diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 5fbb172f9b..0e87af44f1 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -31,11 +31,6 @@ namespace osgParticle class Emitter; } -namespace osgAnimation -{ - class MorphGeometry; -} - namespace NifOsg { @@ -172,7 +167,7 @@ namespace NifOsg virtual float getMaximum() const; }; - /// Must be set on an osgAnimation::MorphGeometry. + /// Must be set on a SceneUtil::MorphGeometry. class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller { public: diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index e514cca121..8810f171a2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -13,9 +13,6 @@ #include #include -// skel -#include - // particle #include #include @@ -39,6 +36,7 @@ #include #include #include +#include #include "particle.hpp" #include "userdata.hpp" @@ -83,35 +81,6 @@ namespace collectDrawableProperties(nifNode->parent, out); } - class FrameSwitch : public osg::Group - { - public: - FrameSwitch() - { - } - - FrameSwitch(const FrameSwitch& copy, const osg::CopyOp& copyop) - : osg::Group(copy, copyop) - { - } - - META_Object(NifOsg, FrameSwitch) - - virtual void traverse(osg::NodeVisitor& nv) - { - if (nv.getTraversalMode() != osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN && nv.getVisitorType() != osg::NodeVisitor::UPDATE_VISITOR) - osg::Group::traverse(nv); - else - { - for (unsigned int i=0; iaccept(nv); - } - } - } - }; - // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale // set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera. // Must be set as a cull callback. @@ -154,70 +123,6 @@ namespace } }; - struct UpdateMorphGeometry : public osg::Drawable::CullCallback - { - UpdateMorphGeometry() - : mLastFrameNumber(0) - { - } - - UpdateMorphGeometry(const UpdateMorphGeometry& copy, const osg::CopyOp& copyop) - : osg::Drawable::CullCallback(copy, copyop) - , mLastFrameNumber(0) - { - } - - META_Object(NifOsg, UpdateMorphGeometry) - - virtual bool cull(osg::NodeVisitor* nv, osg::Drawable * drw, osg::State *) const - { - osgAnimation::MorphGeometry* geom = static_cast(drw); - if (!geom) - return false; - - if (mLastFrameNumber == nv->getTraversalNumber()) - return false; - mLastFrameNumber = nv->getTraversalNumber(); - - geom->transformSoftwareMethod(); - return false; - } - - private: - mutable unsigned int mLastFrameNumber; - }; - - // Callback to return a static bounding box for a MorphGeometry. The idea is to not recalculate the bounding box - // every time the morph weights change. To do so we return a maximum containing box that is big enough for all possible combinations of morph targets. - class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback - { - public: - StaticBoundingBoxCallback() - { - } - - StaticBoundingBoxCallback(const osg::BoundingBox& bounds) - : mBoundingBox(bounds) - { - } - - StaticBoundingBoxCallback(const StaticBoundingBoxCallback& copy, const osg::CopyOp& copyop) - : osg::Drawable::ComputeBoundingBoxCallback(copy, copyop) - , mBoundingBox(copy.mBoundingBox) - { - } - - META_Object(NifOsg, StaticBoundingBoxCallback) - - virtual osg::BoundingBox computeBound(const osg::Drawable&) const - { - return mBoundingBox; - } - - private: - osg::BoundingBox mBoundingBox; - }; - void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys) { for(size_t i = 0;i < tk->list.size();i++) @@ -1107,106 +1012,49 @@ namespace NifOsg void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - osg::ref_ptr geometry; + osg::ref_ptr drawable; for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) continue; if(ctrl->recType == Nif::RC_NiGeomMorpherController) { - geometry = handleMorphGeometry(static_cast(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags); + drawable = handleMorphGeometry(static_cast(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags); osg::ref_ptr morphctrl = new GeomMorpherController( static_cast(ctrl.getPtr())->data.getPtr()); setupController(ctrl.getPtr(), morphctrl, animflags); - geometry->setUpdateCallback(morphctrl); + drawable->setUpdateCallback(morphctrl); break; } } - if (!geometry.get()) + if (!drawable.get()) { - geometry = new osg::Geometry; - triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); + osg::ref_ptr geom (new osg::Geometry); + drawable = geom; + triShapeToGeometry(triShape, geom, parentNode, composite, boundTextures, animflags); } - if (geometry->getDataVariance() == osg::Object::DYNAMIC) - { - // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch - // This is so we can set the DataVariance as STATIC, giving a huge performance boost - geometry->setDataVariance(osg::Object::STATIC); - osg::ref_ptr frameswitch = new FrameSwitch; + drawable->setName(triShape->name); - osg::ref_ptr geom2 = osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES); - frameswitch->addChild(geometry); - frameswitch->addChild(geom2); - - parentNode->addChild(frameswitch); - } - else - parentNode->addChild(geometry); + parentNode->addChild(drawable); } - osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - osg::ref_ptr morphGeom = new osgAnimation::MorphGeometry; - morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE); - // No normals available in the MorphData - morphGeom->setMorphNormals(false); + osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; - morphGeom->setUpdateCallback(NULL); - morphGeom->setCullCallback(new UpdateMorphGeometry); - morphGeom->setUseVertexBufferObjects(true); - - triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags); - - morphGeom->getOrCreateVertexBufferObject()->setUsage(GL_DYNAMIC_DRAW_ARB); + osg::ref_ptr sourceGeometry (new osg::Geometry); + triShapeToGeometry(triShape, sourceGeometry, parentNode, composite, boundTextures, animflags); + morphGeom->setSourceGeometry(sourceGeometry); const std::vector& morphs = morpher->data.getPtr()->mMorphs; if (morphs.empty()) return morphGeom; // Note we are not interested in morph 0, which just contains the original vertices for (unsigned int i = 1; i < morphs.size(); ++i) - { - osg::ref_ptr morphTarget = new osg::Geometry; - morphTarget->setVertexArray(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0])); - morphGeom->addMorphTarget(morphTarget, 0.f); - } - - // build the bounding box containing all possible morph combinations - - std::vector vertBounds(morphs[0].mVertices.size()); - - // Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex. - // The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position. - - // Start with zero offsets which will happen when no morphs are applied. - for (unsigned int i=0; igetBound(); - - // Now set up the callback so that we get properly enlarged bounds if/when the mesh starts animating - morphGeom->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box)); + morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]), 0.f); return morphGeom; } @@ -1219,6 +1067,7 @@ namespace NifOsg osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); + rig->setName(triShape->name); const Nif::NiSkinInstance *skin = triShape->skin.getPtr(); @@ -1233,7 +1082,6 @@ namespace NifOsg SceneUtil::RigGeometry::BoneInfluence influence; const std::vector &weights = data->bones[i].weights; - //influence.mWeights.reserve(weights.size()); for(size_t j = 0;j < weights.size();j++) { std::pair indexWeight = std::make_pair(weights[j].vertex, weights[j].weight); @@ -1246,17 +1094,7 @@ namespace NifOsg } rig->setInfluenceMap(map); - // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch - // This is so we can set the DataVariance as STATIC, giving a huge performance boost - rig->setDataVariance(osg::Object::STATIC); - - osg::ref_ptr frameswitch = new FrameSwitch; - - SceneUtil::RigGeometry* rig2 = osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES); - frameswitch->addChild(rig); - frameswitch->addChild(rig2); - - parentNode->addChild(frameswitch); + parentNode->addChild(rig); } osg::BlendFunc::BlendFuncMode getBlendMode(int mode) @@ -1674,6 +1512,8 @@ namespace NifOsg bool hasMatCtrl = false; + int lightmode = 1; + for (std::vector::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it) { const Nif::Property* property = *it; @@ -1706,19 +1546,22 @@ namespace NifOsg case Nif::RC_NiVertexColorProperty: { const Nif::NiVertexColorProperty* vertprop = static_cast(property); - if (!hasVertexColors) - break; - switch (vertprop->flags) + lightmode = vertprop->data.lightmode; + + switch (vertprop->data.vertmode) { - case 0: - mat->setColorMode(osg::Material::OFF); - break; - case 1: - mat->setColorMode(osg::Material::EMISSION); - break; - case 2: - mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - break; + case 0: + mat->setColorMode(osg::Material::OFF); + break; + case 1: + mat->setColorMode(osg::Material::EMISSION); + break; + case 2: + if (lightmode != 0) + mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + else + mat->setColorMode(osg::Material::OFF); + break; } break; } @@ -1772,6 +1615,35 @@ namespace NifOsg mat->setColorMode(osg::Material::AMBIENT); } + if (lightmode == 0) + { + osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); + diffuse = osg::Vec4f(0,0,0,diffuse.a()); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f()); + } + + // If we're told to use vertex colors but there are none to use, use a default color instead. + if (!hasVertexColors) + { + switch (mat->getColorMode()) + { + case osg::Material::AMBIENT: + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + break; + case osg::Material::AMBIENT_AND_DIFFUSE: + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + break; + case osg::Material::EMISSION: + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + break; + default: + break; + } + mat->setColorMode(osg::Material::OFF); + } + if (!hasMatCtrl && mat->getColorMode() == osg::Material::OFF && mat->getEmission(osg::Material::FRONT_AND_BACK) == osg::Vec4f(0,0,0,1) && mat->getDiffuse(osg::Material::FRONT_AND_BACK) == osg::Vec4f(1,1,1,1) diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp index 4e48d97459..c0e99674ec 100644 --- a/components/resource/resourcemanager.cpp +++ b/components/resource/resourcemanager.cpp @@ -38,4 +38,9 @@ namespace Resource return mVFS; } + void ResourceManager::releaseGLObjects(osg::State *state) + { + mCache->releaseGLObjects(state); + } + } diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index 58200993b9..6031ecc01d 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -11,6 +11,7 @@ namespace VFS namespace osg { class Stats; + class State; } namespace Resource @@ -38,6 +39,8 @@ namespace Resource virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {} + virtual void releaseGLObjects(osg::State* state); + protected: const VFS::Manager* mVFS; osg::ref_ptr mCache; diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index d7a6771c4e..4d61dce692 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -97,4 +97,10 @@ namespace Resource (*it)->reportStats(frameNumber, stats); } + void ResourceSystem::releaseGLObjects(osg::State *state) + { + for (std::vector::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + (*it)->releaseGLObjects(state); + } + } diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index efce50b977..396bdb8fa5 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -12,6 +12,7 @@ namespace VFS namespace osg { class Stats; + class State; } namespace Resource @@ -60,6 +61,9 @@ namespace Resource void reportStats(unsigned int frameNumber, osg::Stats* stats) const; + /// Call releaseGLObjects for each resource manager. + void releaseGLObjects(osg::State* state); + private: std::unique_ptr mSceneManager; std::unique_ptr mImageManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 69a54bf17a..ab801ab82f 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -628,6 +628,11 @@ namespace Resource { mCache->releaseGLObjects(state); mInstanceCache->releaseGLObjects(state); + + mShaderManager->releaseGLObjects(state); + + OpenThreads::ScopedLock lock(mSharedStateMutex); + mSharedStateManager->releaseGLObjects(state); } void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico) diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 14032df776..65524f76ed 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -116,7 +116,7 @@ namespace Resource /// Manually release created OpenGL objects for the given graphics context. This may be required /// in cases where multiple contexts are used over the lifetime of the application. - void releaseGLObjects(osg::State* state); + void releaseGLObjects(osg::State* state) override; /// Set up an IncrementalCompileOperation for background compiling of loaded scenes. void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico); diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 1385f771e5..1df1be27ff 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -32,29 +32,29 @@ namespace SceneUtil virtual void apply(osg::MatrixTransform& node) { - applyNode(node); - } - virtual void apply(osg::Geometry& node) - { - applyNode(node); + traverse(node); } virtual void apply(osg::Node& node) { - applyNode(node); + traverse(node); } virtual void apply(osg::Group& node) { - applyNode(node); + traverse(node); } - void applyNode(osg::Node& node) + virtual void apply(osg::Drawable& drawable) { - std::string lowerName = Misc::StringUtils::lowerCase(node.getName()); + std::string lowerName = Misc::StringUtils::lowerCase(drawable.getName()); if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0) || (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0)) - mToCopy.push_back(&node); - else - traverse(node); + { + osg::Node* node = &drawable; + while (node && node->getNumParents() && !node->getStateSet()) + node = node->getParent(0); + if (node) + mToCopy.push_back(node); + } } void doCopy() diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 738c7a30d6..08f36cfcf1 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include @@ -49,46 +49,12 @@ namespace SceneUtil { if (const osgParticle::ParticleSystem* partsys = dynamic_cast(drawable)) return operator()(partsys); - if (dynamic_cast(drawable)) - { - osg::CopyOp copyop = *this; - copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS); -#if OSG_VERSION_LESS_THAN(3,5,0) - /* - - Deep copy of primitives required to work around the following (bad?) code in osg::Geometry copy constructor: - - if ((copyop.getCopyFlags() & osg::CopyOp::DEEP_COPY_ARRAYS)) - { - if (_useVertexBufferObjects) - { - // copying of arrays doesn't set up buffer objects so we'll need to force - // Geometry to assign these, we'll do this by switching off VBO's then renabling them. - setUseVertexBufferObjects(false); - setUseVertexBufferObjects(true); - } - } - - In case of DEEP_COPY_PRIMITIVES=Off, DEEP_COPY_ARRAYS=On, the above code makes a modification to the original const Geometry& we copied from, - causing problems if we relied on the original Geometry to remain static such as when it was added to an osgUtil::IncrementalCompileOperation. - - Fixed in OSG 3.5 ( http://forum.openscenegraph.org/viewtopic.php?t=15217 ). - - */ - - copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_PRIMITIVES); -#endif - - osg::Drawable* cloned = osg::clone(drawable, copyop); - return cloned; - } - if (dynamic_cast(drawable)) + if (dynamic_cast(drawable) || dynamic_cast(drawable)) { return osg::clone(drawable, *this); } - return osg::CopyOp::operator()(drawable); } diff --git a/components/sceneutil/morphgeometry.cpp b/components/sceneutil/morphgeometry.cpp new file mode 100644 index 0000000000..1b7e4ca93e --- /dev/null +++ b/components/sceneutil/morphgeometry.cpp @@ -0,0 +1,190 @@ +#include "morphgeometry.hpp" + +#include + +namespace SceneUtil +{ + +MorphGeometry::MorphGeometry() + : mLastFrameNumber(0) + , mDirty(true) + , mMorphedBoundingBox(false) +{ + +} + +MorphGeometry::MorphGeometry(const MorphGeometry ©, const osg::CopyOp ©op) + : osg::Drawable(copy, copyop) + , mMorphTargets(copy.mMorphTargets) + , mLastFrameNumber(0) + , mDirty(true) + , mMorphedBoundingBox(false) +{ + setSourceGeometry(copy.getSourceGeometry()); +} + +void MorphGeometry::setSourceGeometry(osg::ref_ptr sourceGeom) +{ + mSourceGeometry = sourceGeom; + + for (unsigned int i=0; i<2; ++i) + { + mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY); + + const osg::Geometry& from = *mSourceGeometry; + osg::Geometry& to = *mGeometry[i]; + to.setSupportsDisplayList(false); + to.setUseVertexBufferObjects(true); + to.setCullingActive(false); // make sure to disable culling since that's handled by this class + + // vertices are modified every frame, so we need to deep copy them. + // assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO. + osg::ref_ptr vbo (new osg::VertexBufferObject); + vbo->setUsage(GL_DYNAMIC_DRAW_ARB); + + osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); + if (vertexArray) + { + vertexArray->setVertexBufferObject(vbo); + to.setVertexArray(vertexArray); + } + } +} + +void MorphGeometry::addMorphTarget(osg::Vec3Array *offsets, float weight) +{ + mMorphTargets.push_back(MorphTarget(offsets, weight)); + mMorphedBoundingBox = false; + dirty(); +} + +void MorphGeometry::dirty() +{ + mDirty = true; + if (!mMorphedBoundingBox) + dirtyBound(); +} + +osg::ref_ptr MorphGeometry::getSourceGeometry() const +{ + return mSourceGeometry; +} + +void MorphGeometry::accept(osg::NodeVisitor &nv) +{ + if (!nv.validNodeMask(*this)) + return; + + nv.pushOntoNodePath(this); + + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + cull(&nv); + else + nv.apply(*this); + + nv.popFromNodePath(); +} + +void MorphGeometry::accept(osg::PrimitiveFunctor& func) const +{ + getGeometry(mLastFrameNumber)->accept(func); +} + +osg::BoundingBox MorphGeometry::computeBoundingBox() const +{ + bool anyMorphTarget = false; + for (unsigned int i=0; i 0) + { + anyMorphTarget = true; + break; + } + + // before the MorphGeometry has started animating, we will use a regular bounding box (this is required + // for correct object placements, which uses the bounding box) + if (!mMorphedBoundingBox && !anyMorphTarget) + { + return mSourceGeometry->getBoundingBox(); + } + // once it animates, use a bounding box that encompasses all possible animations so as to avoid recalculating + else + { + mMorphedBoundingBox = true; + + osg::Vec3Array& sourceVerts = *static_cast(mSourceGeometry->getVertexArray()); + std::vector vertBounds(sourceVerts.size()); + + // Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex. + // The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position. + + // Start with zero offsets which will happen when no morphs are applied. + for (unsigned int i=0; igetTraversalNumber() || !mDirty) + { + osg::Geometry& geom = *getGeometry(mLastFrameNumber); + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); + return; + } + + mDirty = false; + mLastFrameNumber = nv->getTraversalNumber(); + osg::Geometry& geom = *getGeometry(mLastFrameNumber); + + const osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); + osg::Vec3Array* positionDst = static_cast(geom.getVertexArray()); + assert(positionSrc->size() == positionDst->size()); + for (unsigned int vertex=0; vertexsize(); ++vertex) + (*positionDst)[vertex] = (*positionSrc)[vertex]; + + for (unsigned int i=0; isize(); ++vertex) + (*positionDst)[vertex] += (*offsets)[vertex] * weight; + } + + positionDst->dirty(); + + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); +} + +osg::Geometry* MorphGeometry::getGeometry(unsigned int frame) const +{ + return mGeometry[frame%2]; +} + + +} diff --git a/components/sceneutil/morphgeometry.hpp b/components/sceneutil/morphgeometry.hpp new file mode 100644 index 0000000000..122c1456c5 --- /dev/null +++ b/components/sceneutil/morphgeometry.hpp @@ -0,0 +1,83 @@ +#ifndef OPENMW_COMPONENTS_MORPHGEOMETRY_H +#define OPENMW_COMPONENTS_MORPHGEOMETRY_H + +#include + +namespace SceneUtil +{ + + /// @brief Vertex morphing implementation. + /// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while + /// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext. + class MorphGeometry : public osg::Drawable + { + public: + MorphGeometry(); + MorphGeometry(const MorphGeometry& copy, const osg::CopyOp& copyop); + + META_Object(SceneUtil, MorphGeometry) + + /// Initialize this geometry from the source geometry. + /// @note The source geometry will not be modified. + void setSourceGeometry(osg::ref_ptr sourceGeom); + + class MorphTarget + { + protected: + osg::ref_ptr mOffsets; + float mWeight; + public: + MorphTarget(osg::Vec3Array* offsets, float w = 1.0) : mOffsets(offsets), mWeight(w) {} + void setWeight(float weight) { mWeight = weight; } + float getWeight() const { return mWeight; } + osg::Vec3Array* getOffsets() { return mOffsets.get(); } + const osg::Vec3Array* getOffsets() const { return mOffsets.get(); } + void setOffsets(osg::Vec3Array* offsets) { mOffsets = offsets; } + }; + + typedef std::vector MorphTargetList; + + virtual void addMorphTarget( osg::Vec3Array* offsets, float weight = 1.0 ); + + /** Set the MorphGeometry dirty.*/ + void dirty(); + + /** Get the list of MorphTargets.*/ + const MorphTargetList& getMorphTargetList() const { return mMorphTargets; } + + /** Get the list of MorphTargets. Warning if you modify this array you will have to call dirty() */ + MorphTargetList& getMorphTargetList() { return mMorphTargets; } + + /** Return the \c MorphTarget at position \c i.*/ + inline const MorphTarget& getMorphTarget( unsigned int i ) const { return mMorphTargets[i]; } + + /** Return the \c MorphTarget at position \c i.*/ + inline MorphTarget& getMorphTarget( unsigned int i ) { return mMorphTargets[i]; } + + osg::ref_ptr getSourceGeometry() const; + + virtual void accept(osg::NodeVisitor &nv); + virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } + virtual void accept(osg::PrimitiveFunctor&) const; + + virtual osg::BoundingBox computeBoundingBox() const; + + private: + void cull(osg::NodeVisitor* nv); + + MorphTargetList mMorphTargets; + + osg::ref_ptr mSourceGeometry; + + osg::ref_ptr mGeometry[2]; + osg::Geometry* getGeometry(unsigned int frame) const; + + unsigned int mLastFrameNumber; + bool mDirty; // Have any morph targets changed? + + mutable bool mMorphedBoundingBox; + }; + +} + +#endif diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 9adfdcddb0..7f148cf5e3 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -10,73 +10,17 @@ namespace SceneUtil { -class UpdateRigBounds : public osg::Drawable::UpdateCallback -{ -public: - UpdateRigBounds() - { - } - - UpdateRigBounds(const UpdateRigBounds& copy, const osg::CopyOp& copyop) - : osg::Drawable::UpdateCallback(copy, copyop) - { - } - - META_Object(SceneUtil, UpdateRigBounds) - - void update(osg::NodeVisitor* nv, osg::Drawable* drw) - { - RigGeometry* rig = static_cast(drw); - - rig->updateBounds(nv); - } -}; - -// TODO: make threadsafe for multiple cull threads -class UpdateRigGeometry : public osg::Drawable::CullCallback -{ -public: - UpdateRigGeometry() - { - } - - UpdateRigGeometry(const UpdateRigGeometry& copy, const osg::CopyOp& copyop) - : osg::Drawable::CullCallback(copy, copyop) - { - } - - META_Object(SceneUtil, UpdateRigGeometry) - - virtual bool cull(osg::NodeVisitor* nv, osg::Drawable* drw, osg::State*) const - { - RigGeometry* geom = static_cast(drw); - geom->update(nv); - return false; - } -}; - -// We can't compute the bounds without a NodeVisitor, since we need the current geomToSkelMatrix. -// So we return nothing. Bounds are updated every frame in the UpdateCallback. -class DummyComputeBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback -{ -public: - virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return osg::BoundingBox(); } -}; - RigGeometry::RigGeometry() : mSkeleton(NULL) , mLastFrameNumber(0) , mBoundsFirstFrame(true) { - setCullCallback(new UpdateRigGeometry); - setUpdateCallback(new UpdateRigBounds); - setSupportsDisplayList(false); - setUseVertexBufferObjects(true); - setComputeBoundingBoxCallback(new DummyComputeBoundCallback); + setUpdateCallback(new osg::Callback); // dummy to make sure getNumChildrenRequiringUpdateTraversal() is correct + // update done in accept(NodeVisitor&) } RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) - : osg::Geometry(copy, copyop) + : Drawable(copy, copyop) , mSkeleton(NULL) , mInfluenceMap(copy.mInfluenceMap) , mLastFrameNumber(0) @@ -89,57 +33,47 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) { mSourceGeometry = sourceGeometry; - osg::Geometry& from = *sourceGeometry; - - if (from.getStateSet()) - setStateSet(from.getStateSet()); - - // shallow copy primitive sets & vertex attributes that we will not modify - setPrimitiveSetList(from.getPrimitiveSetList()); - setColorArray(from.getColorArray()); - setSecondaryColorArray(from.getSecondaryColorArray()); - setFogCoordArray(from.getFogCoordArray()); - - // need to copy over texcoord list manually due to a missing null pointer check in setTexCoordArrayList(), this has been fixed in OSG 3.5 - osg::Geometry::ArrayList& texCoordList = from.getTexCoordArrayList(); - for (unsigned int i=0; i vbo (new osg::VertexBufferObject); - vbo->setUsage(GL_DYNAMIC_DRAW_ARB); - - osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); - if (vertexArray) + for (unsigned int i=0; i<2; ++i) { - vertexArray->setVertexBufferObject(vbo); - setVertexArray(vertexArray); - } + const osg::Geometry& from = *sourceGeometry; + mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY); + osg::Geometry& to = *mGeometry[i]; + to.setSupportsDisplayList(false); + to.setUseVertexBufferObjects(true); + to.setCullingActive(false); // make sure to disable culling since that's handled by this class - if (osg::Array* normals = from.getNormalArray()) - { - osg::ref_ptr normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL); - if (normalArray) + // vertices and normals are modified every frame, so we need to deep copy them. + // assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO. + osg::ref_ptr vbo (new osg::VertexBufferObject); + vbo->setUsage(GL_DYNAMIC_DRAW_ARB); + + osg::ref_ptr vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); + if (vertexArray) { - normalArray->setVertexBufferObject(vbo); - setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX); + vertexArray->setVertexBufferObject(vbo); + to.setVertexArray(vertexArray); } - } + if (const osg::Array* normals = from.getNormalArray()) + { + osg::ref_ptr normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL); + if (normalArray) + { + normalArray->setVertexBufferObject(vbo); + to.setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX); + } + } - if (osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) - { - mSourceTangents = tangents; - osg::ref_ptr tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); - tangentArray->setVertexBufferObject(vbo); - setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); + if (const osg::Vec4Array* tangents = dynamic_cast(from.getTexCoordArray(7))) + { + mSourceTangents = tangents; + osg::ref_ptr tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); + tangentArray->setVertexBufferObject(vbo); + to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); + } + else + mSourceTangents = NULL; } - else - mSourceTangents = NULL; } osg::ref_ptr RigGeometry::getSourceGeometry() @@ -228,7 +162,7 @@ void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& mat ptrresult[14] += ptr[14] * weight; } -void RigGeometry::update(osg::NodeVisitor* nv) +void RigGeometry::cull(osg::NodeVisitor* nv) { if (!mSkeleton) { @@ -238,23 +172,27 @@ void RigGeometry::update(osg::NodeVisitor* nv) return; } - if (!mSkeleton->getActive() && mLastFrameNumber != 0) - return; - - if (mLastFrameNumber == nv->getTraversalNumber()) + if ((!mSkeleton->getActive() && mLastFrameNumber != 0) || mLastFrameNumber == nv->getTraversalNumber()) + { + osg::Geometry& geom = *getGeometry(mLastFrameNumber); + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); return; + } mLastFrameNumber = nv->getTraversalNumber(); + osg::Geometry& geom = *getGeometry(mLastFrameNumber); mSkeleton->updateBoneMatrices(nv->getTraversalNumber()); // skinning - osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); - osg::Vec3Array* normalSrc = static_cast(mSourceGeometry->getNormalArray()); - osg::Vec4Array* tangentSrc = mSourceTangents; + const osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); + const osg::Vec3Array* normalSrc = static_cast(mSourceGeometry->getNormalArray()); + const osg::Vec4Array* tangentSrc = mSourceTangents; - osg::Vec3Array* positionDst = static_cast(getVertexArray()); - osg::Vec3Array* normalDst = static_cast(getNormalArray()); - osg::Vec4Array* tangentDst = static_cast(getTexCoordArray(7)); + osg::Vec3Array* positionDst = static_cast(geom.getVertexArray()); + osg::Vec3Array* normalDst = static_cast(geom.getNormalArray()); + osg::Vec4Array* tangentDst = static_cast(geom.getTexCoordArray(7)); for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it) { @@ -294,6 +232,10 @@ void RigGeometry::update(osg::NodeVisitor* nv) normalDst->dirty(); if (tangentDst) tangentDst->dirty(); + + nv->pushOntoNodePath(&geom); + nv->apply(geom); + nv->popFromNodePath(); } void RigGeometry::updateBounds(osg::NodeVisitor *nv) @@ -365,5 +307,32 @@ void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) mInfluenceMap = influenceMap; } +void RigGeometry::accept(osg::NodeVisitor &nv) +{ + if (!nv.validNodeMask(*this)) + return; + + nv.pushOntoNodePath(this); + + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + cull(&nv); + else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) + updateBounds(&nv); + else + nv.apply(*this); + + nv.popFromNodePath(); +} + +void RigGeometry::accept(osg::PrimitiveFunctor& func) const +{ + getGeometry(mLastFrameNumber)->accept(func); +} + +osg::Geometry* RigGeometry::getGeometry(unsigned int frame) const +{ + return mGeometry[frame%2].get(); +} + } diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 39829bcb0a..64f4bf3122 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -13,10 +13,9 @@ namespace SceneUtil /// @brief Mesh skinning implementation. /// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton. /// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important. - /// @note To avoid race conditions, the rig geometry needs to be double buffered. This can be done - /// using a FrameSwitch node that has two RigGeometry children. In the future we may want to consider implementing - /// the double buffering inside RigGeometry. - class RigGeometry : public osg::Geometry + /// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while + /// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext. + class RigGeometry : public osg::Drawable { public: RigGeometry(); @@ -24,6 +23,9 @@ namespace SceneUtil META_Object(SceneUtil, RigGeometry) + // At this point compileGLObjects() remains unimplemented, hard to avoid race conditions + // and there is limited value in compiling anyway since the data will change again for the next frame + struct BoneInfluence { osg::Matrixf mInvBindMatrix; @@ -45,15 +47,19 @@ namespace SceneUtil osg::ref_ptr getSourceGeometry(); - // Called automatically by our CullCallback - void update(osg::NodeVisitor* nv); - - // Called automatically by our UpdateCallback - void updateBounds(osg::NodeVisitor* nv); + virtual void accept(osg::NodeVisitor &nv); + virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } + virtual void accept(osg::PrimitiveFunctor&) const; private: + void cull(osg::NodeVisitor* nv); + void updateBounds(osg::NodeVisitor* nv); + + osg::ref_ptr mGeometry[2]; + osg::Geometry* getGeometry(unsigned int frame) const; + osg::ref_ptr mSourceGeometry; - osg::ref_ptr mSourceTangents; + osg::ref_ptr mSourceTangents; Skeleton* mSkeleton; osg::ref_ptr mGeomToSkelMatrix; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 64094275cd..ab03215326 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace SceneUtil { @@ -37,20 +38,20 @@ public: } }; -class FrameSwitchSerializer : public osgDB::ObjectWrapper -{ -public: - FrameSwitchSerializer() - : osgDB::ObjectWrapper(createInstanceFunc, "NifOsg::FrameSwitch", "osg::Object osg::Node osg::Group NifOsg::FrameSwitch") - { - } -}; - class RigGeometrySerializer : public osgDB::ObjectWrapper { public: RigGeometrySerializer() - : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable osg::Geometry SceneUtil::RigGeometry") + : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometry") + { + } +}; + +class MorphGeometrySerializer : public osgDB::ObjectWrapper +{ +public: + MorphGeometrySerializer() + : osgDB::ObjectWrapper(createInstanceFunc, "SceneUtil::MorphGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::MorphGeometry") { } }; @@ -95,8 +96,8 @@ void registerSerializers() osgDB::ObjectWrapperManager* mgr = osgDB::Registry::instance()->getObjectWrapperManager(); mgr->addWrapper(new PositionAttitudeTransformSerializer); mgr->addWrapper(new SkeletonSerializer); - mgr->addWrapper(new FrameSwitchSerializer); mgr->addWrapper(new RigGeometrySerializer); + mgr->addWrapper(new MorphGeometrySerializer); mgr->addWrapper(new LightManagerSerializer); mgr->addWrapper(new CameraRelativeTransformSerializer); diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index 49bc5b70f3..116edfdb4a 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -38,8 +38,6 @@ Skeleton::Skeleton() , mNeedToUpdateBoneMatrices(true) , mActive(true) , mLastFrameNumber(0) - , mTraversedEvenFrame(false) - , mTraversedOddFrame(false) { } @@ -50,8 +48,6 @@ Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op) , mNeedToUpdateBoneMatrices(true) , mActive(copy.mActive) , mLastFrameNumber(0) - , mTraversedEvenFrame(false) - , mTraversedOddFrame(false) { } @@ -115,11 +111,6 @@ void Skeleton::updateBoneMatrices(unsigned int traversalNumber) mLastFrameNumber = traversalNumber; - if (mLastFrameNumber % 2 == 0) - mTraversedEvenFrame = true; - else - mTraversedOddFrame = true; - if (mNeedToUpdateBoneMatrices) { if (mRootBone.get()) @@ -144,18 +135,14 @@ bool Skeleton::getActive() const void Skeleton::markDirty() { - mTraversedEvenFrame = false; - mTraversedOddFrame = false; + mLastFrameNumber = 0; mBoneCache.clear(); mBoneCacheInit = false; } void Skeleton::traverse(osg::NodeVisitor& nv) { - if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR - // need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized - // this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node - && mLastFrameNumber != 0 && mTraversedEvenFrame && mTraversedOddFrame) + if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR && mLastFrameNumber != 0) return; osg::Group::traverse(nv); } diff --git a/components/sceneutil/skeleton.hpp b/components/sceneutil/skeleton.hpp index 542471ff13..245e3522c7 100644 --- a/components/sceneutil/skeleton.hpp +++ b/components/sceneutil/skeleton.hpp @@ -74,8 +74,6 @@ namespace SceneUtil bool mActive; unsigned int mLastFrameNumber; - bool mTraversedEvenFrame; - bool mTraversedOddFrame; }; } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 2bfb17b5c0..7cb49c6cb5 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -158,4 +158,13 @@ namespace Shader return found->second; } + void ShaderManager::releaseGLObjects(osg::State *state) + { + OpenThreads::ScopedLock lock(mMutex); + for (auto shader : mShaders) + shader.second->releaseGLObjects(state); + for (auto program : mPrograms) + program.second->releaseGLObjects(state); + } + } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 5196dbe809..bd820a725c 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -32,6 +32,7 @@ namespace Shader osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + void releaseGLObjects(osg::State* state); private: std::string mPath; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index d85b722039..9b3876d6c8 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "shadermanager.hpp" @@ -26,6 +27,7 @@ namespace Shader , mMaterialOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) + , mNode(NULL) { } @@ -69,7 +71,7 @@ namespace Shader { if (node.getStateSet()) { - pushRequirements(); + pushRequirements(node); applyStateSet(node.getStateSet(), node); traverse(node); popRequirements(); @@ -234,9 +236,10 @@ namespace Shader } } - void ShaderVisitor::pushRequirements() + void ShaderVisitor::pushRequirements(osg::Node& node) { mRequirements.push_back(mRequirements.back()); + mRequirements.back().mNode = &node; } void ShaderVisitor::popRequirements() @@ -244,8 +247,12 @@ namespace Shader mRequirements.pop_back(); } - void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::Node& node) + void ShaderVisitor::createProgram(const ShaderRequirements &reqs) { + if (!reqs.mShaderRequired && !mForceShaders) + return; + + osg::Node& node = *reqs.mNode; osg::StateSet* writableStateSet = NULL; if (mAllowedToModifyStateSets) writableStateSet = node.getOrCreateStateSet(); @@ -302,12 +309,42 @@ namespace Shader } } + bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) + { + bool useShader = reqs.mShaderRequired || mForceShaders; + bool generateTangents = reqs.mTexStageRequiringTangents != -1; + bool changed = false; + + if (mAllowedToModifyStateSets && (useShader || generateTangents)) + { + // make sure that all UV sets are there + for (std::map::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) + { + if (sourceGeometry.getTexCoordArray(it->first) == NULL) + { + sourceGeometry.setTexCoordArray(it->first, sourceGeometry.getTexCoordArray(0)); + changed = true; + } + } + + if (generateTangents) + { + osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); + generator->generate(&sourceGeometry, reqs.mTexStageRequiringTangents); + + sourceGeometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); + changed = true; + } + } + return changed; + } + void ShaderVisitor::apply(osg::Geometry& geometry) { bool needPop = (geometry.getStateSet() != NULL); - if (geometry.getStateSet()) + if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it { - pushRequirements(); + pushRequirements(geometry); applyStateSet(geometry.getStateSet(), geometry); } @@ -315,44 +352,9 @@ namespace Shader { const ShaderRequirements& reqs = mRequirements.back(); - bool useShader = reqs.mShaderRequired || mForceShaders; - bool generateTangents = reqs.mTexStageRequiringTangents != -1; + adjustGeometry(geometry, reqs); - if (mAllowedToModifyStateSets && (useShader || generateTangents)) - { - osg::ref_ptr sourceGeometry = &geometry; - SceneUtil::RigGeometry* rig = dynamic_cast(&geometry); - if (rig) - sourceGeometry = rig->getSourceGeometry(); - - bool requiresSetGeometry = false; - - // make sure that all UV sets are there - for (std::map::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) - { - if (sourceGeometry->getTexCoordArray(it->first) == NULL) - { - sourceGeometry->setTexCoordArray(it->first, sourceGeometry->getTexCoordArray(0)); - requiresSetGeometry = true; - } - } - - if (generateTangents) - { - osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); - generator->generate(sourceGeometry, reqs.mTexStageRequiringTangents); - - sourceGeometry->setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); - requiresSetGeometry = true; - } - - if (rig && requiresSetGeometry) - rig->setSourceGeometry(sourceGeometry); - } - - // TODO: find a better place for the stateset - if (useShader) - createProgram(reqs, geometry); + createProgram(reqs); } if (needPop) @@ -366,16 +368,27 @@ namespace Shader if (drawable.getStateSet()) { - pushRequirements(); + pushRequirements(drawable); applyStateSet(drawable.getStateSet(), drawable); } if (!mRequirements.empty()) { const ShaderRequirements& reqs = mRequirements.back(); - // TODO: find a better place for the stateset - if (reqs.mShaderRequired || mForceShaders) - createProgram(reqs, drawable); + createProgram(reqs); + + if (auto rig = dynamic_cast(&drawable)) + { + osg::ref_ptr sourceGeometry = rig->getSourceGeometry(); + if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) + rig->setSourceGeometry(sourceGeometry); + } + else if (auto morph = dynamic_cast(&drawable)) + { + osg::ref_ptr sourceGeometry = morph->getSourceGeometry(); + if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) + morph->setSourceGeometry(sourceGeometry); + } } if (needPop) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 8f4597ff36..cb0538d9d3 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -52,7 +52,7 @@ namespace Shader void applyStateSet(osg::ref_ptr stateset, osg::Node& node); - void pushRequirements(); + void pushRequirements(osg::Node& node); void popRequirements(); private: @@ -89,13 +89,17 @@ namespace Shader // -1 == no tangents required int mTexStageRequiringTangents; + + // the Node that requested these requirements + osg::Node* mNode; }; std::vector mRequirements; std::string mDefaultVsTemplate; std::string mDefaultFsTemplate; - void createProgram(const ShaderRequirements& reqs, osg::Node& node); + void createProgram(const ShaderRequirements& reqs); + bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); }; } diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 361dd6c04e..1734686def 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -243,4 +243,18 @@ namespace Terrain } } + void BufferCache::releaseGLObjects(osg::State *state) + { + { + OpenThreads::ScopedLock lock(mIndexBufferMutex); + for (auto indexbuffer : mIndexBufferMap) + indexbuffer.second->releaseGLObjects(state); + } + { + OpenThreads::ScopedLock lock(mUvBufferMutex); + for (auto uvbuffer : mUvBufferMap) + uvbuffer.second->releaseGLObjects(state); + } + } + } diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index bfdf558bc0..37563d2c67 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -24,7 +24,7 @@ namespace Terrain void clearCache(); - // TODO: add releaseGLObjects() for our vertex/element buffer objects + void releaseGLObjects(osg::State* state); private: // Index buffers are shared across terrain batches where possible. There is one index buffer for each diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index ea71c726fd..7575113ef2 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -62,6 +62,12 @@ void ChunkManager::clearCache() mBufferCache.clearCache(); } +void ChunkManager::releaseGLObjects(osg::State *state) +{ + ResourceManager::releaseGLObjects(state); + mBufferCache.releaseGLObjects(state); +} + void ChunkManager::setCullingActive(bool active) { mCullingActive = active; diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 2f92fa8361..46531f23ec 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -36,6 +36,8 @@ namespace Terrain virtual void clearCache(); + void releaseGLObjects(osg::State* state) override; + void setCullingActive(bool active); private: diff --git a/components/widgets/windowcaption.hpp b/components/widgets/windowcaption.hpp index bdd4c0a2e2..b45da2d1ce 100644 --- a/components/widgets/windowcaption.hpp +++ b/components/widgets/windowcaption.hpp @@ -23,6 +23,7 @@ namespace Gui private: MyGUI::Widget* mLeft; MyGUI::Widget* mRight; + MyGUI::Widget* mClient; void align(); }; diff --git a/docs/source/manuals/openmw-cs/files-and-directories.rst b/docs/source/manuals/openmw-cs/files-and-directories.rst index 34680fa945..77593dece0 100644 --- a/docs/source/manuals/openmw-cs/files-and-directories.rst +++ b/docs/source/manuals/openmw-cs/files-and-directories.rst @@ -14,22 +14,22 @@ Basics Directories =========== -OpenMW and OpenMW CS us multiple directories on the file system. First of all +OpenMW and OpenMW CS use multiple directories on the file system. First of all there is a *user directory* that holds configuration files and a number of different sub-directories. The location of the user directory is hard-coded into the CS and depends on your operating system. ================ ========================================= -Operating System User Dircetory +Operating System User Directory ================ ========================================= -GNU/Linux ```` +GNU/Linux ``~/.config/openmw/`` OS X ``~/Library/Application Support/openmw/`` -Windows ```` +Windows ``C:\Users\ *Username* \Documents\my games\OpenMW`` ================ ========================================= In addition to to this single hard-coded directory both OpenMW and OpenMW CS -need a place to seek for a actuals data files of the game: textures, 3D models, -sounds and record files that store objects in game; dialogues an so one. These +need a place to search for actual data files of the game: textures, 3D models, +sounds and record files that store objects in game; dialogues and so on. These files are called *content files*. We support multiple such paths (we call them *data paths*) as specified in the configuration. Usually one data path points to the directory where the original Morrowind game is either installed or @@ -42,12 +42,12 @@ Content files ============= The original Morrowind engine by Bethesda Softworks uses two types of content -files: `esm` (master) and `esp` (plugin). The distinction between those two is -not clear, and often confusing. One would expect the `esm` (master) file to be -used to specify one master, which is then modified by the `esp` plugins. And +files: `ESM` (master) and `ESP` (plugin). The distinction between those two is +not clear, and often confusing. One would expect the `ESM` (master) file to be +used to specify one master, which is then modified by the `ESP` plugins. And indeed: this is the basic idea. However, the official expansions were also made as ESM files, even though they could essentially be described as really large -plugins, and therefore would rather use `esp` files. There were technical +plugins, and therefore should have been `ESP` files. There were technical reasons behind this decision – somewhat valid in the case of the original engine, but clearly it is better to create a system that can be used in a more sensible way. OpenMW achieves this with our own content file types. @@ -62,7 +62,7 @@ OpenMW content files The concepts of *Game* and *Addon* files are somewhat similar to the old concept of *ESM* and *ESP*, but more strictly enforced. It is quite -straight-formward: If you want to make new game using OpenMW as the engine (a +straight-forward: If you want to make new game using OpenMW as the engine (a so called *total conversion*) you should create a game file. If you want to create an addon for an existing game file create an addon file. Nothing else matters; the only distinction you should consider is if your project is about @@ -75,21 +75,21 @@ Another simple thing about content files are the extensions: we are using Morrowind content files ----------------------- -Using our content files is recommended for projects that are intended to used -with the OpenMW engine. However, some players might wish to still use the +Using our content files is recommended for projects that are intended to use +the OpenMW engine. However, some players might wish to still use the original Morrowind engine. In addition thousands of *ESP*/*ESM* files were created since 2002, some of them with really outstanding content. Because of this OpenMW CS simply has no other choice but to support *ESP*/*ESM* files. If -you decid to choose *ESP*/*ESM* file instead of using our own content file -types you are most likely aimng at compatibility with the original engine. This -subject is covered in it own chapter of this manual. +you decide to choose *ESP*/*ESM* file instead of using our own content file +types you are most likely aiming at compatibility with the original engine. This +subject is covered in its own chapter of this manual. .. TODO This paragraph sounds weird The actual creation of new files is described in the next chapter. Here we are going to focus only on the details you need to know in order to create your -first OpenMW CS file while fully understanding your needs. For now let’s jut +first OpenMW CS file while fully understanding your needs. For now let’s just remember that content files are created inside the user directory in the the ``data`` subdirectory (that is the one special data directory mentioned earlier). @@ -99,8 +99,8 @@ Dependencies ------------ Since an addon is supposed to change the game it follows that it also depends -on the said game to work. We can conceptualise this with an examples: your -modification is the changing prize of an iron sword, but what if there is no +on the said game to work. We can conceptualise this with an example: your +modification is changing the price of an iron sword, but what if there is no iron sword in game? That's right: we get nonsense. What you want to do is tie your addon to the files you are changing. Those can be either game files (for example when making an expansion island for a game) or other addon files @@ -112,9 +112,9 @@ files – it is only a theoretical introduction to the subject. For now just kee in mind that dependencies exist, and is up to you to decide whether your content file should depend on other content files. -Game files are not intend to have any dependencies for a very simple reasons: +Game files are not intended to have any dependencies for a very simple reasons: the player is using only one game file (excluding original and the dirty -ESP/ESM system) at a time and therefore no game file can depend on other game +ESP/ESM system) at a time and therefore no game file can depend on another game file, and since a game file makes the base for addon files it can not depend on addon files. @@ -123,7 +123,7 @@ Project files ------------- Project files act as containers for data not used by the OpenMW game engine -itself, but still useful for OpenMW CS. The shining example of this data +itself, but still useful for OpenMW CS. The shining examples of this data category are without doubt record filters (described in a later chapter of the manual). As a mod author you probably do not need or want to distribute project files at all, they are meant to be used only by you and your team. @@ -132,7 +132,7 @@ files at all, they are meant to be used only by you and your team. As you would imagine, project files make sense only in combination with actual content files. In fact, each time you start to work on new content file and a -project file was not found, one will be created. The extensio of project files +project file was not found, one will be created. The extension of project files is ``.project``. The whole name of the project file is the whole name of the content file with appended extension. For instance a ``swords.omwaddon`` file is associated with a ``swords.omwaddon.project`` file. diff --git a/docs/source/manuals/openmw-cs/starting-dialog.rst b/docs/source/manuals/openmw-cs/starting-dialog.rst index 02a65ff213..fa069d8d6d 100644 --- a/docs/source/manuals/openmw-cs/starting-dialog.rst +++ b/docs/source/manuals/openmw-cs/starting-dialog.rst @@ -3,7 +3,7 @@ OpenMW CS Starting Dialog In this chapter we will cover starting up OpenMW CS and the starting interface. Start the CS the way intended for your operating system and you will be -presented with window and three main buttons and a small button with a +presented with a window and three main buttons and a small button with a wrench-icon. The wrench will open the configuration dialog which we will cover later. The three main buttons are the following: @@ -32,7 +32,7 @@ choose exactly one game and you can choose an arbitrary amount of addon dependencies. For the sake of simplicity and maintainability choose only the addons you actually want to depend on. Also keep in mind that your dependencies might have dependencies of their own, you have to depend on those as well. If -one of your dependencies nees something it will be indicated by a warning sign +one of your dependencies needs something it will be indicated by a warning sign and automatically include its dependencies when you choose it. If you want to edit an existing content file you will be presented with a diff --git a/docs/source/manuals/openmw-cs/tour.rst b/docs/source/manuals/openmw-cs/tour.rst index 9844948ead..bb1097e0cf 100644 --- a/docs/source/manuals/openmw-cs/tour.rst +++ b/docs/source/manuals/openmw-cs/tour.rst @@ -48,7 +48,7 @@ Once the addon has been created you will be presented with a table. If you see a blank window rather than a table choose *World* → *Objects* from the menu. .. figure:: _static/images/chapter-1/objects.png - :alt: The table showing all objet records in the game. + :alt: The table showing all object records in the game. Let's talk about the interface for a second. Every window in OpenMW CS has *panels*, these are often but not always tables. You can close a panel by @@ -139,7 +139,7 @@ the first character. Type the following into the field: A filter is defined by a number of *queries* which can be logically linked. For now all that matters is that the `string(, )` query will check -whether `` matches ``. The pattern is a regular expression, +whether `` matches ``. The pattern is a regular expression, if you don't know about them you should learn their syntax. For now all that matters is that `.` stands for any character and `*` stands for any amount, even zero. In other words, we are looking for all entries which have an ID that diff --git a/docs/source/reference/modding/convert_bump_mapped_mods.rst b/docs/source/reference/modding/convert_bump_mapped_mods.rst index 791e773533..71ac294688 100644 --- a/docs/source/reference/modding/convert_bump_mapped_mods.rst +++ b/docs/source/reference/modding/convert_bump_mapped_mods.rst @@ -176,7 +176,7 @@ The sacks included in Apel's `Various Things - Sacks`_ come in two versions – #. Open up each of the models in NifSkope and look for these certain blocks_: - NiTextureEffect - NiSourceTexture with the value that appears to be a normal map file, in this mod, they have the suffix *_nm.dds*. -#. Remove all these tags by selecting them one at a time and press right click>Block>Remove. +#. Remove all these tags by selecting them one at a time and press right click>Block>Remove Branch. (Ctrl-Del) #. Repeat this on all the affected models. #. If you launch OpenMW now, you'll `no longer have shiny models`_. But one thing is missing. Can you see it? It's actually hard to spot on still pictures, but we have no normal maps here. #. Now, go back to the root of where you installed the mod. Now go to ``./Textures/`` and you'll find the texture files in question. diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index 2344222e53..a1c6c2068a 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -56,7 +56,7 @@ Enabling this feature results in better visuals, and a marginally lower frame ra This setting has no effect if the shader setting is false. -This setting can be toggled with the Refraction button in the Water tab of the Video panel of the Options menu. +This setting can be toggled with the 'Refraction' button in the Water tab of the Video panel of the Options menu. reflect actors -------------- @@ -68,6 +68,8 @@ reflect actors This setting controls whether or not NPCs and creatures are drawn in water reflections. Setting this to true will enable actors in reflections and increase realism with a likely decrease in performance. +This setting can be toggled with the 'Reflect actors' button in the Water tab of the Video panel of the Options menu. + small feature culling pixel size --------------------------------