Merge master

experimental^2
AnyOldName3 7 years ago
commit a5397e5f2d

@ -34,6 +34,7 @@ Programmers
Ben Shealy (bentsherman) Ben Shealy (bentsherman)
Bret Curtis (psi29a) Bret Curtis (psi29a)
Britt Mathis (galdor557) Britt Mathis (galdor557)
Capostrophic
cc9cii cc9cii
Chris Boyce (slothlife) Chris Boyce (slothlife)
Chris Robinson (KittyCat) Chris Robinson (KittyCat)
@ -60,6 +61,7 @@ Programmers
Gašper Sedej Gašper Sedej
gugus/gus gugus/gus
Hallfaer Tuilinn Hallfaer Tuilinn
Haoda Wang (h313)
hristoast hristoast
Internecine Internecine
Jacob Essex (Yacoby) Jacob Essex (Yacoby)

@ -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) project(OpenMW)
# If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them. # 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) 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 # what is necessary to build documentation
IF( BUILD_DOCS ) IF( BUILD_DOCS )
# Builds the documentation. # 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) 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() endif()
if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD)
set(USE_QT FALSE)
else()
set(USE_QT TRUE)
endif()
# Dependencies # Dependencies
if (USE_QT) 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}") message(STATUS "Using Qt${DESIRED_QT_VERSION}")
if (DESIRED_QT_VERSION MATCHES 4) if (DESIRED_QT_VERSION MATCHES 4)
@ -144,17 +158,6 @@ if (USE_QT)
endif() endif()
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 # Sound setup
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE) find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
# Required for building the FFmpeg headers # Required for building the FFmpeg headers
@ -206,8 +209,8 @@ if(NOT HAVE_STDINT_H)
message(FATAL_ERROR "stdint.h was not found" ) message(FATAL_ERROR "stdint.h was not found" )
endif() 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}) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
set(USED_OSG_PLUGINS set(USED_OSG_PLUGINS

@ -4,7 +4,7 @@ set(BSATOOL
source_group(apps\\bsatool FILES ${BSATOOL}) source_group(apps\\bsatool FILES ${BSATOOL})
# Main executable # Main executable
add_executable(bsatool openmw_add_executable(bsatool
${BSATOOL} ${BSATOOL}
) )

@ -8,7 +8,7 @@ set(ESMTOOL
source_group(apps\\esmtool FILES ${ESMTOOL}) source_group(apps\\esmtool FILES ${ESMTOOL})
# Main executable # Main executable
add_executable(esmtool openmw_add_executable(esmtool
${ESMTOOL} ${ESMTOOL}
) )

@ -28,7 +28,7 @@ set(ESSIMPORTER_FILES
convertplayer.cpp convertplayer.cpp
) )
add_executable(openmw-essimporter openmw_add_executable(openmw-essimporter
${ESSIMPORTER_FILES} ${ESSIMPORTER_FILES}
) )

@ -78,7 +78,7 @@ if(NOT WIN32)
endif(NOT WIN32) endif(NOT WIN32)
# Main executable # Main executable
add_executable(openmw-launcher openmw_add_executable(openmw-launcher
${GUI_TYPE} ${GUI_TYPE}
${LAUNCHER} ${LAUNCHER}
${LAUNCHER_HEADER} ${LAUNCHER_HEADER}

@ -9,7 +9,7 @@ set(MWINIIMPORT_HEADER
source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER}) source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
add_executable(openmw-iniimporter openmw_add_executable(openmw-iniimporter
${MWINIIMPORT} ${MWINIIMPORT}
) )

@ -4,7 +4,7 @@ set(NIFTEST
source_group(components\\nif\\tests FILES ${NIFTEST}) source_group(components\\nif\\tests FILES ${NIFTEST})
# Main executable # Main executable
add_executable(niftest openmw_add_executable(niftest
${NIFTEST} ${NIFTEST}
) )

@ -174,7 +174,7 @@ else()
set (OPENCS_OPENMW_CFG "") set (OPENCS_OPENMW_CFG "")
endif(APPLE) endif(APPLE)
add_executable(openmw-cs openmw_add_executable(openmw-cs
MACOSX_BUNDLE MACOSX_BUNDLE
${OPENCS_SRC} ${OPENCS_SRC}
${OPENCS_UI_HDR} ${OPENCS_UI_HDR}
@ -199,7 +199,7 @@ if(APPLE)
RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}" RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}"
OUTPUT_NAME ${OPENCS_BUNDLE_NAME} OUTPUT_NAME ${OPENCS_BUNDLE_NAME}
MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns" 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_GUI_IDENTIFIER "org.openmw.opencs"
MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION}
MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION}

@ -132,7 +132,7 @@ namespace CSMPrefs
if (mods && i == 0) if (mods && i == 0)
{ {
if (mods & Qt::ControlModifier) if (mods & Qt::ControlModifier)
result.append("Ctl+"); result.append("Ctrl+");
if (mods & Qt::ShiftModifier) if (mods & Qt::ShiftModifier)
result.append("Shift+"); result.append("Shift+");
if (mods & Qt::AltModifier) if (mods & Qt::AltModifier)
@ -196,7 +196,7 @@ namespace CSMPrefs
std::string name = value.substr(start, end - start); std::string name = value.substr(start, end - start);
if (name == "Ctl") if (name == "Ctrl")
{ {
mods |= Qt::ControlModifier; mods |= Qt::ControlModifier;
} }

@ -275,12 +275,14 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int
void CSVRender::Cell::pathgridModified() void CSVRender::Cell::pathgridModified()
{ {
mPathgrid->recreateGeometry(); if (mPathgrid)
mPathgrid->recreateGeometry();
} }
void CSVRender::Cell::pathgridRemoved() void CSVRender::Cell::pathgridRemoved()
{ {
mPathgrid->removeGeometry(); if (mPathgrid)
mPathgrid->removeGeometry();
} }
void CSVRender::Cell::reloadAssets() void CSVRender::Cell::reloadAssets()
@ -320,7 +322,7 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode)
iter->second->setSelected (selected); 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 // Only one pathgrid may be selected, so some operations will only have an effect
// if the pathgrid is already focused // if the pathgrid is already focused
@ -420,7 +422,7 @@ std::vector<osg::ref_ptr<CSVRender::TagBase> > CSVRender::Cell::getSelection (un
iter!=mObjects.end(); ++iter) iter!=mObjects.end(); ++iter)
if (iter->second->getSelected()) if (iter->second->getSelected())
result.push_back (iter->second->getTag()); result.push_back (iter->second->getTag());
if (elementMask & Mask_Pathgrid) if (mPathgrid && elementMask & Mask_Pathgrid)
if (mPathgrid->isSelected()) if (mPathgrid->isSelected())
result.push_back(mPathgrid->getTag()); result.push_back(mPathgrid->getTag());
@ -457,6 +459,6 @@ void CSVRender::Cell::reset (unsigned int elementMask)
for (std::map<std::string, Object *>::const_iterator iter (mObjects.begin()); for (std::map<std::string, Object *>::const_iterator iter (mObjects.begin());
iter!=mObjects.end(); ++iter) iter!=mObjects.end(); ++iter)
iter->second->reset(); iter->second->reset();
if (elementMask & Mask_Pathgrid) if (mPathgrid && elementMask & Mask_Pathgrid)
mPathgrid->resetIndicators(); mPathgrid->resetIndicators();
} }

@ -72,12 +72,15 @@ namespace CSVRender
} }
else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos)) else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos))
{ {
// Add node if (cell->getPathgrid())
QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); {
QString description = "Add node"; // Add node
QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack();
QString description = "Add node";
CSMWorld::CommandMacro macro(undoStack, description); CSMWorld::CommandMacro macro(undoStack, description);
cell->getPathgrid()->applyPoint(macro, hitResult.worldPos); cell->getPathgrid()->applyPoint(macro, hitResult.worldPos);
}
} }
} }
@ -205,7 +208,7 @@ namespace CSVRender
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
Cell* cell = getWorldspaceWidget().getCell(hit.worldPos); Cell* cell = getWorldspaceWidget().getCell(hit.worldPos);
if (cell) if (cell && cell->getPathgrid())
{ {
PathgridTag* tag = 0; PathgridTag* tag = 0;
if (hit.tag && (tag = dynamic_cast<PathgridTag*>(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId) if (hit.tag && (tag = dynamic_cast<PathgridTag*>(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId)

@ -221,8 +221,8 @@ SceneWidget::SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSyste
SceneWidget::~SceneWidget() 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 // 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->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
} }
void SceneWidget::setLighting(Lighting *lighting) void SceneWidget::setLighting(Lighting *lighting)

@ -32,13 +32,19 @@ std::string CSVWorld::InfoCreator::getId() const
void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const
{ {
int index = CSMWorld::IdTable& table = dynamic_cast<CSMWorld::IdTable&> (*getData().getTableModel (getCollectionId()));
dynamic_cast<CSMWorld::IdTable&> (*getData().getTableModel (getCollectionId())).
findColumnIndex (
getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ?
CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal);
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, CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack,

@ -99,7 +99,7 @@ add_openmw_dir (mwbase
# Main executable # Main executable
if (NOT ANDROID) if (NOT ANDROID)
add_executable(openmw openmw_add_executable(openmw
${OPENMW_FILES} ${OPENMW_FILES}
${GAME} ${GAME_HEADER} ${GAME} ${GAME_HEADER}
${APPLE_BUNDLE_RESOURCES} ${APPLE_BUNDLE_RESOURCES}

@ -84,7 +84,6 @@ void OMW::Engine::frame(float frametime)
try try
{ {
mStartTick = mViewer->getStartTick(); mStartTick = mViewer->getStartTick();
mEnvironment.setFrameDuration (frametime);
// update input // update input
mEnvironment.getInputManager()->update(frametime, false); mEnvironment.getInputManager()->update(frametime, false);
@ -651,6 +650,8 @@ void OMW::Engine::go()
Settings::Manager::getString("screenshot format", "General"))); Settings::Manager::getString("screenshot format", "General")));
mViewer->addEventHandler(mScreenCaptureHandler); mViewer->addEventHandler(mScreenCaptureHandler);
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
// Create encoder // Create encoder
ToUTF8::Utf8Encoder encoder (mEncoding); ToUTF8::Utf8Encoder encoder (mEncoding);
mEncoder = &encoder; mEncoder = &encoder;
@ -684,7 +685,6 @@ void OMW::Engine::go()
// Start the main rendering loop // Start the main rendering loop
osg::Timer frameTimer; osg::Timer frameTimer;
double simulationTime = 0.0; double simulationTime = 0.0;
float framerateLimit = Settings::Manager::getFloat("framerate limit", "Video");
while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest())
{ {
double dt = frameTimer.time_s(); double dt = frameTimer.time_s();
@ -697,6 +697,8 @@ void OMW::Engine::go()
mViewer->advance(simulationTime); mViewer->advance(simulationTime);
mEnvironment.setFrameDuration(dt);
frame(dt); frame(dt);
if (!mEnvironment.getInputManager()->isWindowVisible()) if (!mEnvironment.getInputManager()->isWindowVisible())
@ -714,15 +716,7 @@ void OMW::Engine::go()
mViewer->renderingTraversals(); mViewer->renderingTraversals();
} }
if (framerateLimit > 0.f) mEnvironment.limitFrameRate(frameTimer.time_s());
{
double thisFrameTime = frameTimer.time_s();
double minFrameTime = 1.0 / framerateLimit;
if (thisFrameTime < minFrameTime)
{
OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime));
}
}
} }
// Save user settings // Save user settings

@ -2,6 +2,8 @@
#include <cassert> #include <cassert>
#include <OpenThreads/Thread>
#include "world.hpp" #include "world.hpp"
#include "scriptmanager.hpp" #include "scriptmanager.hpp"
#include "dialoguemanager.hpp" #include "dialoguemanager.hpp"
@ -17,7 +19,7 @@ MWBase::Environment *MWBase::Environment::sThis = 0;
MWBase::Environment::Environment() MWBase::Environment::Environment()
: mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0), : mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0),
mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mStateManager (0), mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mStateManager (0),
mFrameDuration (0) mFrameDuration (0), mFrameRateLimit(0.f)
{ {
assert (!sThis); assert (!sThis);
sThis = this; sThis = this;
@ -79,6 +81,29 @@ void MWBase::Environment::setFrameDuration (float duration)
mFrameDuration = 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<double>(mFrameRateLimit);
if (thisFrameTime < minFrameTime)
{
OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime));
}
}
}
MWBase::World *MWBase::Environment::getWorld() const MWBase::World *MWBase::Environment::getWorld() const
{ {
assert (mWorld); assert (mWorld);

@ -33,6 +33,7 @@ namespace MWBase
InputManager *mInputManager; InputManager *mInputManager;
StateManager *mStateManager; StateManager *mStateManager;
float mFrameDuration; float mFrameDuration;
float mFrameRateLimit;
Environment (const Environment&); Environment (const Environment&);
///< not implemented ///< not implemented
@ -67,6 +68,10 @@ namespace MWBase
void setFrameDuration (float duration); void setFrameDuration (float duration);
///< Set length of current frame in seconds. ///< Set length of current frame in seconds.
void setFrameRateLimit(float frameRateLimit);
float getFrameRateLimit() const;
void limitFrameRate(double dt) const;
World *getWorld() const; World *getWorld() const;
SoundManager *getSoundManager() const; SoundManager *getSoundManager() const;

@ -223,6 +223,7 @@ namespace MWBase
virtual void keepPlayerAlive() = 0; virtual void keepPlayerAlive() = 0;
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 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; 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 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 isRunning(const MWWorld::Ptr& ptr) = 0;
virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0;
}; };

@ -212,7 +212,10 @@ namespace MWBase
virtual void setSpellVisibility(bool visible) = 0; virtual void setSpellVisibility(bool visible) = 0;
virtual void setSneakVisibility(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 std::string getSelectedSpell() = 0;
virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 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 // In WindowManager for now since there isn't a VFS singleton
virtual std::string correctIconPath(const std::string& path) = 0; 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 std::string correctTexturePath(const std::string& path) = 0;
virtual bool textureExists(const std::string& path) = 0; virtual bool textureExists(const std::string& path) = 0;

@ -3,6 +3,7 @@
#include <components/esm/loadlock.hpp> #include <components/esm/loadlock.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -155,6 +156,16 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell); return MWWorld::Ptr(cell.insert(ref), &cell);
} }
std::pair<int, std::string> 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 bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const
{ {
return (npcServices & ESM::NPC::Picks) != 0; return (npcServices & ESM::NPC::Picks) != 0;

@ -51,6 +51,8 @@ namespace MWClass
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
///< Return name of inventory icon. ///< Return name of inventory icon.
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr) virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const; const;
///< Generate action for using via inventory menu ///< Generate action for using via inventory menu

@ -3,6 +3,7 @@
#include <components/esm/loadprob.hpp> #include <components/esm/loadprob.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -155,6 +156,16 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell); return MWWorld::Ptr(cell.insert(ref), &cell);
} }
std::pair<int, std::string> 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 bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const
{ {
return (npcServices & ESM::NPC::Probes) != 0; return (npcServices & ESM::NPC::Probes) != 0;

@ -51,6 +51,8 @@ namespace MWClass
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
///< Return name of inventory icon. ///< Return name of inventory icon.
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr) virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const; const;
///< Generate action for using via inventory menu ///< Generate action for using via inventory menu

@ -4,6 +4,7 @@
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -373,6 +374,11 @@ namespace MWClass
if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
return std::make_pair(0, "#{sInventoryMessage1}"); 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<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
if (slots_.first.empty()) if (slots_.first.empty())

@ -634,7 +634,7 @@ namespace MWGui
void DialogueWindow::onFrame() 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); int disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr);
mDispositionBar->setProgressRange(100); mDispositionBar->setProgressRange(100);

@ -275,8 +275,6 @@ namespace MWGui
{ {
case BookTextParser::Event_ImgTag: case BookTextParser::Event_ImgTag:
{ {
pag.setIgnoreLeadingEmptyLines(false);
const BookTextParser::Attributes & attr = parser.getAttributes(); const BookTextParser::Attributes & attr = parser.getAttributes();
if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end()) 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 width = MyGUI::utility::parseInt(attr.at("width"));
int height = MyGUI::utility::parseInt(attr.at("height")); 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 <img> tag." << std::endl;
break;
}
pag.setIgnoreLeadingEmptyLines(false);
ImageElement elem(paper, pag, mBlockStyle, ImageElement elem(paper, pag, mBlockStyle,
src, width, height); correctedSrc, width, height);
elem.paginate(); elem.paginate();
break; break;
} }
@ -471,8 +480,7 @@ namespace MWGui
MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top,
parent->getName() + MyGUI::utility::toString(parent->getChildCount())); parent->getName() + MyGUI::utility::toString(parent->getChildCount()));
std::string image = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, mImageHeight); mImageBox->setImageTexture(src);
mImageBox->setImageTexture(image);
mImageBox->setProperty("NeedMouse", "false"); mImageBox->setProperty("NeedMouse", "false");
} }

@ -179,29 +179,33 @@ namespace MWGui
void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat<float>& value) void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat<float>& value)
{ {
int current = std::max(0, static_cast<int>(value.getCurrent())); int current = static_cast<int>(value.getCurrent());
int modified = static_cast<int>(value.getModified()); int modified = static_cast<int>(value.getModified());
// Fatigue can be negative
if (id != "FBar")
current = std::max(0, current);
MyGUI::Widget* w; MyGUI::Widget* w;
std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified);
if (id == "HBar") if (id == "HBar")
{ {
mHealth->setProgressRange(modified); mHealth->setProgressRange(std::max(0, modified));
mHealth->setProgressPosition(current); mHealth->setProgressPosition(std::max(0, current));
getWidget(w, "HealthFrame"); getWidget(w, "HealthFrame");
w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr);
} }
else if (id == "MBar") else if (id == "MBar")
{ {
mMagicka->setProgressRange (modified); mMagicka->setProgressRange(std::max(0, modified));
mMagicka->setProgressPosition (current); mMagicka->setProgressPosition(std::max(0, current));
getWidget(w, "MagickaFrame"); getWidget(w, "MagickaFrame");
w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr);
} }
else if (id == "FBar") else if (id == "FBar")
{ {
mStamina->setProgressRange (modified); mStamina->setProgressRange(std::max(0, modified));
mStamina->setProgressPosition (current); mStamina->setProgressPosition(std::max(0, current));
getWidget(w, "FatigueFrame"); getWidget(w, "FatigueFrame");
w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
} }

@ -29,6 +29,7 @@
#include "../mwrender/characterpreview.hpp" #include "../mwrender/characterpreview.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "itemview.hpp" #include "itemview.hpp"
#include "inventoryitemmodel.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) if (count > 1 && !shift)
{ {
CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();
@ -660,9 +674,18 @@ namespace MWGui
void InventoryWindow::cycle(bool next) 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; ItemModel::ModelIndex selected = -1;
// not using mSortFilterModel as we only need sorting, not filtering // 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.setSortByType(false);
model.update(); model.update();
if (model.getItemCount() == 0) if (model.getItemCount() == 0)

@ -143,10 +143,10 @@ namespace MWGui
mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level));
std::string levelupdescription; 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"); levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default");
else
levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level));
mLevelDescription->setCaption (levelupdescription); mLevelDescription->setCaption (levelupdescription);

@ -102,6 +102,15 @@ namespace MWGui
mBackgroundImage->setVisible(visible); 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 class CopyFramebufferToTextureCallback : public osg::Camera::DrawCallback
{ {
public: public:
@ -141,7 +150,7 @@ namespace MWGui
if (mViewer->getIncrementalCompileOperation()) if (mViewer->getIncrementalCompileOperation())
{ {
mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100); 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 // 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) void LoadingScreen::setProgress (size_t value)
{ {
// skip expensive update if there isn't enough visible progress // 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; return;
value = std::min(value, mProgressBar->getScrollRange()-1); value = std::min(value, mProgressBar->getScrollRange()-1);
mProgress = value; mProgress = value;
@ -231,7 +240,7 @@ namespace MWGui
bool LoadingScreen::needToDrawLoadingScreen() 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; return false;
// the minimal delay before a loading screen shows // the minimal delay before a loading screen shows

@ -43,6 +43,8 @@ namespace MWGui
virtual void setVisible(bool visible); virtual void setVisible(bool visible);
double getTargetFrameRate() const;
private: private:
void findSplashScreens(); void findSplashScreens();
bool needToDrawLoadingScreen(); bool needToDrawLoadingScreen();
@ -73,8 +75,6 @@ namespace MWGui
std::vector<std::string> mSplashScreens; std::vector<std::string> mSplashScreens;
// TODO: add releaseGLObjects() for mTexture
osg::ref_ptr<osg::Texture2D> mTexture; osg::ref_ptr<osg::Texture2D> mTexture;
std::unique_ptr<MyGUI::ITexture> mGuiTexture; std::unique_ptr<MyGUI::ITexture> mGuiTexture;

@ -14,6 +14,7 @@
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -36,6 +37,7 @@ namespace MWGui
, mItemSelectionDialog(0) , mItemSelectionDialog(0)
, mMagicSelectionDialog(0) , mMagicSelectionDialog(0)
, mSelectedIndex(-1) , mSelectedIndex(-1)
, mActivatedIndex(-1)
{ {
getWidget(mOkButton, "OKButton"); getWidget(mOkButton, "OKButton");
getWidget(mInstructionLabel, "InstructionLabel"); getWidget(mInstructionLabel, "InstructionLabel");
@ -69,6 +71,8 @@ namespace MWGui
void QuickKeysMenu::clear() void QuickKeysMenu::clear()
{ {
mActivatedIndex = -1;
for (int i=0; i<10; ++i) for (int i=0; i<10; ++i)
{ {
unassign(mQuickKeyButtons[i], i); unassign(mQuickKeyButtons[i], i);
@ -254,6 +258,15 @@ namespace MWGui
mMagicSelectionDialog->setVisible(false); 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) void QuickKeysMenu::activateQuickKey(int index)
{ {
assert (index-1 >= 0); assert (index-1 >= 0);
@ -263,6 +276,27 @@ namespace MWGui
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr player = MWMechanics::getPlayer();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); 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) if (type == Type_Item || type == Type_MagicItem)
{ {
@ -309,6 +343,22 @@ namespace MWGui
else if (type == Type_Item) else if (type == Type_Item)
{ {
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>(); MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
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); MWBase::Environment::get().getWindowManager()->useItem(item);
MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
// change draw state only if the item is in player's right hand // change draw state only if the item is in player's right hand

@ -36,6 +36,7 @@ namespace MWGui
void onAssignMagicCancel (); void onAssignMagicCancel ();
void activateQuickKey(int index); void activateQuickKey(int index);
void updateActivatedQuickKey();
/// @note This enum is serialized, so don't move the items around! /// @note This enum is serialized, so don't move the items around!
enum QuickKeyType enum QuickKeyType
@ -64,7 +65,7 @@ namespace MWGui
MagicSelectionDialog* mMagicSelectionDialog; MagicSelectionDialog* mMagicSelectionDialog;
int mSelectedIndex; int mSelectedIndex;
int mActivatedIndex;
void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onQuickKeyButtonClicked(MyGUI::Widget* sender);
void onOkButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender);

@ -180,7 +180,7 @@ namespace MWGui
void ReviewDialog::setFatigue(const MWMechanics::DynamicStat<float>& value) void ReviewDialog::setFatigue(const MWMechanics::DynamicStat<float>& value)
{ {
int current = std::max(0, static_cast<int>(value.getCurrent())); int current = static_cast<int>(value.getCurrent());
int modified = static_cast<int>(value.getModified()); int modified = static_cast<int>(value.getModified());
mFatigue->setValue(current, modified); mFatigue->setValue(current, modified);

@ -9,6 +9,7 @@
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
@ -195,6 +196,15 @@ namespace MWGui
void SpellWindow::cycle(bool next) 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())); mSpellView->setModel(new SpellModel(MWMechanics::getPlayer()));
SpellModel::ModelIndex selected = 0; SpellModel::ModelIndex selected = 0;

@ -102,12 +102,13 @@ namespace MWGui
{ {
MyGUI::ProgressBar* pt; MyGUI::ProgressBar* pt;
getWidget(pt, name); getWidget(pt, name);
pt->setProgressRange(max);
pt->setProgressPosition(val);
std::stringstream out; std::stringstream out;
out << val << "/" << max; out << val << "/" << max;
setText(tname, out.str().c_str()); 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) void StatsWindow::setPlayerName(const std::string& playerName)
@ -147,9 +148,13 @@ namespace MWGui
void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value) void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
{ {
int current = std::max(0, static_cast<int>(value.getCurrent())); int current = static_cast<int>(value.getCurrent());
int modified = static_cast<int>(value.getModified()); int modified = static_cast<int>(value.getModified());
// Fatigue can be negative
if (id != "FBar")
current = std::max(0, current);
setBar (id, id + "T", current, modified); setBar (id, id + "T", current, modified);
// health, magicka, fatigue tooltip // health, magicka, fatigue tooltip

@ -502,11 +502,10 @@ namespace MWGui
if (mBarWidget) if (mBarWidget)
{ {
mBarWidget->setProgressRange(mMax); mBarWidget->setProgressRange(std::max(0, mMax));
mBarWidget->setProgressPosition(mValue); mBarWidget->setProgressPosition(std::max(0, mValue));
} }
if (mBarTextWidget) if (mBarTextWidget)
{ {
std::stringstream out; std::stringstream out;

@ -518,6 +518,8 @@ namespace MWGui
cleanupGarbage(); cleanupGarbage();
mHud->update(); mHud->update();
updateActivatedQuickKey ();
} }
void WindowManager::updateVisible() void WindowManager::updateVisible()
@ -904,19 +906,30 @@ namespace MWGui
if (block) if (block)
{ {
osg::Timer frameTimer;
while (mMessageBoxManager->readPressedButton(false) == -1 while (mMessageBoxManager->readPressedButton(false) == -1
&& !MWBase::Environment::get().getStateManager()->hasQuitRequest()) && !MWBase::Environment::get().getStateManager()->hasQuitRequest())
{ {
mMessageBoxManager->onFrame(0.f); double dt = frameTimer.time_s();
MWBase::Environment::get().getInputManager()->update(0, true, false); 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, // 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. // 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() // refer to the advance() and frame() order in Engine::go()
mViewer->eventTraversal();
mViewer->updateTraversal();
mViewer->renderingTraversals();
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
MWBase::Environment::get().limitFrameRate(frameTimer.time_s());
} }
} }
} }
@ -1528,6 +1541,11 @@ namespace MWGui
mHud->setCrosshairVisible (show && mCrosshairEnabled); mHud->setCrosshairVisible (show && mCrosshairEnabled);
} }
void WindowManager::updateActivatedQuickKey ()
{
mQuickKeysMenu->updateActivatedQuickKey();
}
void WindowManager::activateQuickKey (int index) void WindowManager::activateQuickKey (int index)
{ {
mQuickKeysMenu->activateQuickKey(index); mQuickKeysMenu->activateQuickKey(index);
@ -1831,18 +1849,28 @@ namespace MWGui
if (mVideoWidget->hasAudioStream()) if (mVideoWidget->hasAudioStream())
MWBase::Environment::get().getSoundManager()->pauseSounds( MWBase::Environment::get().getSoundManager()->pauseSounds(
MWBase::SoundManager::Play_TypeMask&(~MWBase::SoundManager::Play_TypeMovie)); MWBase::SoundManager::Play_TypeMask&(~MWBase::SoundManager::Play_TypeMovie));
osg::Timer frameTimer;
while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) 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, // 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. // 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() // refer to the advance() and frame() order in Engine::go()
mViewer->eventTraversal();
mViewer->updateTraversal();
mViewer->renderingTraversals();
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
MWBase::Environment::get().limitFrameRate(frameTimer.time_s());
} }
mVideoWidget->stop(); mVideoWidget->stop();
@ -2062,9 +2090,12 @@ namespace MWGui
return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS()); 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) std::string WindowManager::correctTexturePath(const std::string& path)

@ -241,7 +241,10 @@ namespace MWGui
virtual void setSpellVisibility(bool visible); virtual void setSpellVisibility(bool visible);
virtual void setSneakVisibility(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 std::string getSelectedSpell() { return mSelectedSpell; }
virtual void setSelectedSpell(const std::string& spellId, int successChancePercent); 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 // In WindowManager for now since there isn't a VFS singleton
virtual std::string correctIconPath(const std::string& path); 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 std::string correctTexturePath(const std::string& path);
virtual bool textureExists(const std::string& path); virtual bool textureExists(const std::string& path);

@ -20,6 +20,7 @@
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
@ -929,6 +930,9 @@ namespace MWInput
inventory.getSelectedEnchantItem() == inventory.end()) inventory.getSelectedEnchantItem() == inventory.end())
return; return;
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer()))
return;
MWMechanics::DrawState_ state = mPlayer->getDrawState(); MWMechanics::DrawState_ state = mPlayer->getDrawState();
if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing) if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing)
mPlayer->setDrawState(MWMechanics::DrawState_Spell); mPlayer->setDrawState(MWMechanics::DrawState_Spell);
@ -944,6 +948,13 @@ namespace MWInput
if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"]) if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"])
return; 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(); MWMechanics::DrawState_ state = mPlayer->getDrawState();
if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
mPlayer->setDrawState(MWMechanics::DrawState_Weapon); mPlayer->setDrawState(MWMechanics::DrawState_Weapon);

@ -394,10 +394,15 @@ namespace MWMechanics
aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); 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 // 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()) 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<ESM::GameSetting>().find("fAlarmRadius")->getFloat();
if (sqrDist > fAlarmRadius * fAlarmRadius)
return;
bool followerOrEscorter = false; bool followerOrEscorter = false;
for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it) for (std::list<MWMechanics::AiPackage*>::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) bool Actors::isRunning(const MWWorld::Ptr& ptr)
{ {
PtrActorMap::iterator it = mActors.find(ptr); PtrActorMap::iterator it = mActors.find(ptr);
@ -1788,6 +1803,16 @@ namespace MWMechanics
return it->second->getCharacterController()->isReadyToBlock(); 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() void Actors::fastForwardAi()
{ {
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())

@ -107,6 +107,7 @@ namespace MWMechanics
int countDeaths (const std::string& id) const; int countDeaths (const std::string& id) const;
///< Return the number of deaths for actors with the given ID. ///< Return the number of deaths for actors with the given ID.
bool isAttackPrepairing(const MWWorld::Ptr& ptr);
bool isRunning(const MWWorld::Ptr& ptr); bool isRunning(const MWWorld::Ptr& ptr);
bool isSneaking(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr);
@ -150,6 +151,7 @@ namespace MWMechanics
void clear(); // Clear death counter void clear(); // Clear death counter
bool isReadyToBlock(const MWWorld::Ptr& ptr) const; bool isReadyToBlock(const MWWorld::Ptr& ptr) const;
bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const;
private: private:
PtrActorMap mActors; PtrActorMap mActors;

@ -262,22 +262,16 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co
void MWMechanics::Alchemy::removeIngredients() void MWMechanics::Alchemy::removeIngredients()
{ {
bool needsUpdate = false;
for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
if (!iter->isEmpty()) if (!iter->isEmpty())
{ {
iter->getContainerStore()->remove(*iter, 1, mAlchemist); iter->getContainerStore()->remove(*iter, 1, mAlchemist);
if (iter->getRefData().getCount()<1) if (iter->getRefData().getCount()<1)
{
needsUpdate = true;
*iter = MWWorld::Ptr(); *iter = MWWorld::Ptr();
}
} }
if (needsUpdate) updateEffects();
updateEffects();
} }
void MWMechanics::Alchemy::addPotion (const std::string& name) void MWMechanics::Alchemy::addPotion (const std::string& name)

@ -2218,6 +2218,12 @@ void CharacterController::setAttackTypeBasedOnMovement()
mAttackType = "chop"; mAttackType = "chop";
} }
bool CharacterController::isAttackPrepairing() const
{
return mUpperBodyState == UpperCharState_StartToMinAttack ||
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
}
bool CharacterController::isReadyToBlock() const bool CharacterController::isReadyToBlock() const
{ {
return updateCarriedLeftVisible(mWeaponType); return updateCarriedLeftVisible(mWeaponType);
@ -2228,6 +2234,12 @@ bool CharacterController::isKnockedOut() const
return mHitState == CharState_KnockOut; return mHitState == CharState_KnockOut;
} }
bool CharacterController::isAttackingOrSpell() const
{
return mUpperBodyState != UpperCharState_Nothing &&
mUpperBodyState != UpperCharState_WeapEquiped;
}
bool CharacterController::isSneaking() const bool CharacterController::isSneaking() const
{ {
return mIdleState == CharState_IdleSneak || return mIdleState == CharState_IdleSneak ||

@ -263,10 +263,12 @@ public:
void forceStateUpdate(); void forceStateUpdate();
bool isAttackPrepairing() const;
bool isReadyToBlock() const; bool isReadyToBlock() const;
bool isKnockedOut() const; bool isKnockedOut() const;
bool isSneaking() const; bool isSneaking() const;
bool isRunning() const; bool isRunning() const;
bool isAttackingOrSpell() const;
void setAttackingOrSpell(bool attackingOrSpell); void setAttackingOrSpell(bool attackingOrSpell);
void setAIAttackType(const std::string& attackType); void setAIAttackType(const std::string& attackType);

@ -423,6 +423,11 @@ namespace MWMechanics
mObjects.update(duration, paused); mObjects.update(duration, paused);
} }
bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr)
{
return mActors.isAttackPrepairing(ptr);
}
bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr) bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr)
{ {
return mActors.isRunning(ptr); return mActors.isRunning(ptr);
@ -1591,6 +1596,11 @@ namespace MWMechanics
return mActors.isReadyToBlock(ptr); 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) void MechanicsManager::setWerewolf(const MWWorld::Ptr& actor, bool werewolf)
{ {
MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor);

@ -188,6 +188,8 @@ namespace MWMechanics
virtual void keepPlayerAlive(); virtual void keepPlayerAlive();
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const; 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); 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 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 isRunning(const MWWorld::Ptr& ptr);
virtual bool isSneaking(const MWWorld::Ptr& ptr); virtual bool isSneaking(const MWWorld::Ptr& ptr);
private: private:
void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
OffenseType type, int arg=0); OffenseType type, int arg=0);

@ -380,7 +380,7 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int
for (int i=0; i<7; ++i) for (int i=0; i<7; ++i)
{ {
if (faction.mData.mSkills[i] != -1) if (faction.mData.mSkills[i] != -1)
skills.push_back (static_cast<int> (getSkill (faction.mData.mSkills[i]).getModified())); skills.push_back (static_cast<int> (getSkill (faction.mData.mSkills[i]).getBase()));
} }
if (skills.empty()) if (skills.empty())

@ -13,6 +13,7 @@
#include "combat.hpp" #include "combat.hpp"
#include "aicombataction.hpp" #include "aicombataction.hpp"
#include "spellpriority.hpp" #include "spellpriority.hpp"
#include "spellcasting.hpp"
namespace MWMechanics namespace MWMechanics
{ {
@ -90,10 +91,13 @@ namespace MWMechanics
if (!weapon->mEnchant.empty()) if (!weapon->mEnchant.empty())
{ {
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(weapon->mEnchant); const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(weapon->mEnchant);
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
&& (item.getCellRef().getEnchantmentCharge() == -1 {
|| item.getCellRef().getEnchantmentCharge() >= enchantment->mData.mCost)) int castCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), actor);
rating += rateEffects(enchantment->mEffects, actor, enemy);
if (item.getCellRef().getEnchantmentCharge() == -1 || item.getCellRef().getEnchantmentCharge() >= castCost)
rating += rateEffects(enchantment->mEffects, actor, enemy);
}
} }
int skill = item.getClass().getEquipmentSkill(item); int skill = item.getClass().getEquipmentSkill(item);

@ -18,7 +18,7 @@ namespace MWPhysics
Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> shape, btCollisionWorld* world) Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> shape, btCollisionWorld* world)
: mCanWaterWalk(false), mWalkingOnWater(false) : 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) , mInternalCollisionMode(true)
, mExternalCollisionMode(true) , mExternalCollisionMode(true)
, mCollisionWorld(world) , mCollisionWorld(world)

@ -182,7 +182,10 @@ namespace
void remove() void remove()
{ {
for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) 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: protected:
@ -1192,6 +1195,9 @@ namespace MWRender
mObjectRoot->addChild(created); mObjectRoot->addChild(created);
mInsert->addChild(mObjectRoot); mInsert->addChild(mObjectRoot);
} }
osg::ref_ptr<SceneUtil::Skeleton> skel = dynamic_cast<SceneUtil::Skeleton*>(mObjectRoot.get());
if (skel)
mSkeleton = skel.get();
} }
else else
{ {

@ -118,7 +118,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item)); osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item));
const NodeMap& nodeMap = getNodeMap(); 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()) if (found == nodeMap.end())
throw std::runtime_error("Can't find attachment node " + bonename); throw std::runtime_error("Can't find attachment node " + bonename);
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get()); osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get());

@ -23,6 +23,7 @@
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
@ -919,6 +920,9 @@ void NpcAnimation::showWeapons(bool showWeapon)
else else
{ {
removeIndividualPart(ESM::PRT_Weapon); 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);
} }
} }

@ -1189,6 +1189,14 @@ namespace MWScript
if (mNegativeEffect != -1) if (mNegativeEffect != -1)
currentValue -= effects.get(mNegativeEffect).getMagnitude(); 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<int>(currentValue); int ret = static_cast<int>(currentValue);
runtime.push(ret); runtime.push(ret);
} }
@ -1215,6 +1223,14 @@ namespace MWScript
if (mNegativeEffect != -1) if (mNegativeEffect != -1)
currentValue -= effects.get(mNegativeEffect).getMagnitude(); 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; int arg = runtime[0].mInteger;
runtime.pop(); runtime.pop();
effects.modifyBase(mPositiveEffect, (arg - static_cast<int>(currentValue))); effects.modifyBase(mPositiveEffect, (arg - static_cast<int>(currentValue)));

@ -3,6 +3,7 @@
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
#include <map> #include <map>
#include <numeric>
#include <osg/Matrixf> #include <osg/Matrixf>
@ -271,7 +272,6 @@ namespace MWSound
return sound; return sound;
} }
// Gets the combined volume settings for the given sound type // Gets the combined volume settings for the given sound type
float SoundManager::volumeFromType(PlayType type) const float SoundManager::volumeFromType(PlayType type) const
{ {
@ -298,7 +298,6 @@ namespace MWSound
return volume; return volume;
} }
void SoundManager::stopMusic() void SoundManager::stopMusic()
{ {
if(mMusic) if(mMusic)
@ -349,6 +348,7 @@ namespace MWSound
void SoundManager::startRandomTitle() void SoundManager::startRandomTitle()
{ {
std::vector<std::string> filelist; std::vector<std::string> filelist;
auto &tracklist = mMusicToPlay[mCurrentPlaylist];
if (mMusicFiles.find(mCurrentPlaylist) == mMusicFiles.end()) if (mMusicFiles.find(mCurrentPlaylist) == mMusicFiles.end())
{ {
const std::map<std::string, VFS::File*>& index = mVFS->getIndex(); const std::map<std::string, VFS::File*>& index = mVFS->getIndex();
@ -367,7 +367,6 @@ namespace MWSound
} }
mMusicFiles[mCurrentPlaylist] = filelist; mMusicFiles[mCurrentPlaylist] = filelist;
} }
else else
filelist = mMusicFiles[mCurrentPlaylist]; filelist = mMusicFiles[mCurrentPlaylist];
@ -375,15 +374,25 @@ namespace MWSound
if(filelist.empty()) if(filelist.empty())
return; return;
int i = Misc::Rng::rollDice(filelist.size()); // Do a Fisher-Yates shuffle
// Don't play the same music track twice in a row // Repopulate if playlist is empty
if (filelist[i] == mLastPlayedMusic) 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() bool SoundManager::isMusicPlaying()
@ -570,6 +579,9 @@ namespace MWSound
if((mode&Play_RemoveAtDistance) && (mListenerPos-objpos).length2() > 2000*2000) if((mode&Play_RemoveAtDistance) && (mListenerPos-objpos).length2() > 2000*2000)
return MWBase::SoundPtr(); 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()) if(!(mode&Play_NoPlayerLocal) && ptr == MWMechanics::getPlayer())
{ {
sound.reset(new Sound(volume * sfx->mVolume, basevol, pitch, mode|type|Play_2D)); sound.reset(new Sound(volume * sfx->mVolume, basevol, pitch, mode|type|Play_2D));

@ -6,6 +6,7 @@
#include <utility> #include <utility>
#include <deque> #include <deque>
#include <map> #include <map>
#include <unordered_map>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -49,6 +50,7 @@ namespace MWSound
// Caches available music tracks by <playlist name, (sound files) > // Caches available music tracks by <playlist name, (sound files) >
std::map<std::string, std::vector<std::string> > mMusicFiles; std::map<std::string, std::vector<std::string> > mMusicFiles;
std::unordered_map<std::string, std::vector<int>> mMusicToPlay; // A list with music files not yet played
std::string mLastPlayedMusic; // The music file that was last played std::string mLastPlayedMusic; // The music file that was last played
float mMasterVolume; float mMasterVolume;

@ -4,6 +4,7 @@
#include <iosfwd> #include <iosfwd>
#include <iostream> #include <iostream>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <MyGUI_TextIterator.h>
#include "components/loadinglistener/loadinglistener.hpp" #include "components/loadinglistener/loadinglistener.hpp"
@ -24,7 +25,7 @@ struct ContentLoader
virtual void load(const boost::filesystem::path& filepath, int& index) virtual void load(const boost::filesystem::path& filepath, int& index)
{ {
std::cout << "Loading content file " << filepath.string() << std::endl; std::cout << "Loading content file " << filepath.string() << std::endl;
mListener.setLabel(filepath.string()); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string()));
} }
protected: protected:

@ -17,7 +17,7 @@ if (GTEST_FOUND)
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) 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) target_link_libraries(openmw_test_suite ${GTEST_BOTH_LIBRARIES} components)
# Fix for not visible pthreads functions for linker with glibc 2.15 # Fix for not visible pthreads functions for linker with glibc 2.15

@ -96,7 +96,7 @@ if (OPENMW_USE_UNSHIELD)
include_directories(${LIBUNSHIELD_INCLUDE_DIRS}) include_directories(${LIBUNSHIELD_INCLUDE_DIRS})
endif() endif()
add_executable(openmw-wizard openmw_add_executable(openmw-wizard
${GUI_TYPE} ${GUI_TYPE}
${WIZARD} ${WIZARD}
${WIZARD_HEADER} ${WIZARD_HEADER}

@ -142,3 +142,32 @@ foreach (u ${ARGN})
add_hdr (OPENCS ${dir} ${u}) add_hdr (OPENCS ${dir} ${u})
endforeach (u) endforeach (u)
endmacro (opencs_hdrs_noqt) 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)

@ -49,7 +49,7 @@ add_component_dir (shader
) )
add_component_dir (sceneutil 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 lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
) )

@ -6,11 +6,10 @@
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/UserDataContainer> #include <osg/UserDataContainer>
#include <osgAnimation/MorphGeometry>
#include <osgParticle/Emitter> #include <osgParticle/Emitter>
#include <components/nif/data.hpp> #include <components/nif/data.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include "userdata.hpp" #include "userdata.hpp"
@ -188,7 +187,7 @@ GeomMorpherController::GeomMorpherController(const Nif::NiMorphData *data)
void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable) void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable)
{ {
osgAnimation::MorphGeometry* morphGeom = static_cast<osgAnimation::MorphGeometry*>(drawable); SceneUtil::MorphGeometry* morphGeom = static_cast<SceneUtil::MorphGeometry*>(drawable);
if (hasInput()) if (hasInput())
{ {
if (mKeyFrames.size() <= 1) if (mKeyFrames.size() <= 1)
@ -202,7 +201,7 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable
val = it->interpKey(input); val = it->interpKey(input);
val = std::max(0.f, std::min(1.f, val)); 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) if (target.getWeight() != val)
{ {
target.setWeight(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() UVController::UVController()

@ -31,11 +31,6 @@ namespace osgParticle
class Emitter; class Emitter;
} }
namespace osgAnimation
{
class MorphGeometry;
}
namespace NifOsg namespace NifOsg
{ {
@ -172,7 +167,7 @@ namespace NifOsg
virtual float getMaximum() const; 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 class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller
{ {
public: public:

@ -13,9 +13,6 @@
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/imagemanager.hpp> #include <components/resource/imagemanager.hpp>
// skel
#include <osgAnimation/MorphGeometry>
// particle // particle
#include <osgParticle/ParticleSystem> #include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleSystemUpdater> #include <osgParticle/ParticleSystemUpdater>
@ -39,6 +36,7 @@
#include <components/nif/effect.hpp> #include <components/nif/effect.hpp>
#include <components/sceneutil/skeleton.hpp> #include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/riggeometry.hpp> #include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include "particle.hpp" #include "particle.hpp"
#include "userdata.hpp" #include "userdata.hpp"
@ -83,35 +81,6 @@ namespace
collectDrawableProperties(nifNode->parent, out); 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; i<getNumChildren(); ++i)
{
if (i%2 == nv.getTraversalNumber()%2)
getChild(i)->accept(nv);
}
}
}
};
// NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale // 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. // 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. // 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<osgAnimation::MorphGeometry*>(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) void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys)
{ {
for(size_t i = 0;i < tk->list.size();i++) 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<int>& boundTextures, int animflags) void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
{ {
osg::ref_ptr<osg::Geometry> geometry; osg::ref_ptr<osg::Drawable> drawable;
for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next) for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next)
{ {
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
continue; continue;
if(ctrl->recType == Nif::RC_NiGeomMorpherController) if(ctrl->recType == Nif::RC_NiGeomMorpherController)
{ {
geometry = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags); drawable = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags);
osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController( osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(
static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr())->data.getPtr()); static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr())->data.getPtr());
setupController(ctrl.getPtr(), morphctrl, animflags); setupController(ctrl.getPtr(), morphctrl, animflags);
geometry->setUpdateCallback(morphctrl); drawable->setUpdateCallback(morphctrl);
break; break;
} }
} }
if (!geometry.get()) if (!drawable.get())
{ {
geometry = new osg::Geometry; osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); drawable = geom;
triShapeToGeometry(triShape, geom, parentNode, composite, boundTextures, animflags);
} }
if (geometry->getDataVariance() == osg::Object::DYNAMIC) drawable->setName(triShape->name);
{
// 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> frameswitch = new FrameSwitch;
osg::ref_ptr<osg::Geometry> geom2 = osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES);
frameswitch->addChild(geometry);
frameswitch->addChild(geom2);
parentNode->addChild(frameswitch); parentNode->addChild(drawable);
}
else
parentNode->addChild(geometry);
} }
osg::ref_ptr<osg::Geometry> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags) osg::ref_ptr<osg::Drawable> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
{ {
osg::ref_ptr<osgAnimation::MorphGeometry> morphGeom = new osgAnimation::MorphGeometry; osg::ref_ptr<SceneUtil::MorphGeometry> morphGeom = new SceneUtil::MorphGeometry;
morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE);
// No normals available in the MorphData
morphGeom->setMorphNormals(false);
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<osg::Geometry> sourceGeometry (new osg::Geometry);
triShapeToGeometry(triShape, sourceGeometry, parentNode, composite, boundTextures, animflags);
morphGeom->setSourceGeometry(sourceGeometry);
const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs; const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs;
if (morphs.empty()) if (morphs.empty())
return morphGeom; return morphGeom;
// Note we are not interested in morph 0, which just contains the original vertices // Note we are not interested in morph 0, which just contains the original vertices
for (unsigned int i = 1; i < morphs.size(); ++i) for (unsigned int i = 1; i < morphs.size(); ++i)
{ morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]), 0.f);
osg::ref_ptr<osg::Geometry> 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<osg::BoundingBox> 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; i<vertBounds.size(); ++i)
vertBounds[i].set(osg::Vec3f(0,0,0), osg::Vec3f(0,0,0));
for (unsigned int i = 1; i < morphs.size(); ++i)
{
for (unsigned int j=0; j<morphs[i].mVertices.size() && vertBounds.size(); ++j)
{
osg::BoundingBox& bounds = vertBounds[j];
bounds.expandBy(bounds._max + morphs[i].mVertices[j]);
bounds.expandBy(bounds._min + morphs[i].mVertices[j]);
}
}
osg::BoundingBox box;
for (unsigned int i=0; i<vertBounds.size(); ++i)
{
vertBounds[i]._max += morphs[0].mVertices[i];
vertBounds[i]._min += morphs[0].mVertices[i];
box.expandBy(vertBounds[i]);
}
// For the initial bounding box (used for object placement) use the default pose, fire off a bounding compute to set this initial box
morphGeom->getBound();
// Now set up the callback so that we get properly enlarged bounds if/when the mesh starts animating
morphGeom->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box));
return morphGeom; return morphGeom;
} }
@ -1219,6 +1067,7 @@ namespace NifOsg
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry); osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
rig->setSourceGeometry(geometry); rig->setSourceGeometry(geometry);
rig->setName(triShape->name);
const Nif::NiSkinInstance *skin = triShape->skin.getPtr(); const Nif::NiSkinInstance *skin = triShape->skin.getPtr();
@ -1233,7 +1082,6 @@ namespace NifOsg
SceneUtil::RigGeometry::BoneInfluence influence; SceneUtil::RigGeometry::BoneInfluence influence;
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights; const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
//influence.mWeights.reserve(weights.size());
for(size_t j = 0;j < weights.size();j++) for(size_t j = 0;j < weights.size();j++)
{ {
std::pair<unsigned short, float> indexWeight = std::make_pair(weights[j].vertex, weights[j].weight); std::pair<unsigned short, float> indexWeight = std::make_pair(weights[j].vertex, weights[j].weight);
@ -1246,17 +1094,7 @@ namespace NifOsg
} }
rig->setInfluenceMap(map); rig->setInfluenceMap(map);
// Add a copy, we will alternate between the two copies every other frame using the FrameSwitch parentNode->addChild(rig);
// This is so we can set the DataVariance as STATIC, giving a huge performance boost
rig->setDataVariance(osg::Object::STATIC);
osg::ref_ptr<FrameSwitch> 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);
} }
osg::BlendFunc::BlendFuncMode getBlendMode(int mode) osg::BlendFunc::BlendFuncMode getBlendMode(int mode)
@ -1674,6 +1512,8 @@ namespace NifOsg
bool hasMatCtrl = false; bool hasMatCtrl = false;
int lightmode = 1;
for (std::vector<const Nif::Property*>::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it) for (std::vector<const Nif::Property*>::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it)
{ {
const Nif::Property* property = *it; const Nif::Property* property = *it;
@ -1706,19 +1546,22 @@ namespace NifOsg
case Nif::RC_NiVertexColorProperty: case Nif::RC_NiVertexColorProperty:
{ {
const Nif::NiVertexColorProperty* vertprop = static_cast<const Nif::NiVertexColorProperty*>(property); const Nif::NiVertexColorProperty* vertprop = static_cast<const Nif::NiVertexColorProperty*>(property);
if (!hasVertexColors) lightmode = vertprop->data.lightmode;
break;
switch (vertprop->flags) switch (vertprop->data.vertmode)
{ {
case 0: case 0:
mat->setColorMode(osg::Material::OFF); mat->setColorMode(osg::Material::OFF);
break; break;
case 1: case 1:
mat->setColorMode(osg::Material::EMISSION); mat->setColorMode(osg::Material::EMISSION);
break; break;
case 2: case 2:
mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); if (lightmode != 0)
break; mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
else
mat->setColorMode(osg::Material::OFF);
break;
} }
break; break;
} }
@ -1772,6 +1615,35 @@ namespace NifOsg
mat->setColorMode(osg::Material::AMBIENT); 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 if (!hasMatCtrl && mat->getColorMode() == osg::Material::OFF
&& mat->getEmission(osg::Material::FRONT_AND_BACK) == osg::Vec4f(0,0,0,1) && 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) && mat->getDiffuse(osg::Material::FRONT_AND_BACK) == osg::Vec4f(1,1,1,1)

@ -38,4 +38,9 @@ namespace Resource
return mVFS; return mVFS;
} }
void ResourceManager::releaseGLObjects(osg::State *state)
{
mCache->releaseGLObjects(state);
}
} }

@ -11,6 +11,7 @@ namespace VFS
namespace osg namespace osg
{ {
class Stats; class Stats;
class State;
} }
namespace Resource namespace Resource
@ -38,6 +39,8 @@ namespace Resource
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {}
virtual void releaseGLObjects(osg::State* state);
protected: protected:
const VFS::Manager* mVFS; const VFS::Manager* mVFS;
osg::ref_ptr<Resource::ObjectCache> mCache; osg::ref_ptr<Resource::ObjectCache> mCache;

@ -97,4 +97,10 @@ namespace Resource
(*it)->reportStats(frameNumber, stats); (*it)->reportStats(frameNumber, stats);
} }
void ResourceSystem::releaseGLObjects(osg::State *state)
{
for (std::vector<ResourceManager*>::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it)
(*it)->releaseGLObjects(state);
}
} }

@ -12,6 +12,7 @@ namespace VFS
namespace osg namespace osg
{ {
class Stats; class Stats;
class State;
} }
namespace Resource namespace Resource
@ -60,6 +61,9 @@ namespace Resource
void reportStats(unsigned int frameNumber, osg::Stats* stats) const; void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
/// Call releaseGLObjects for each resource manager.
void releaseGLObjects(osg::State* state);
private: private:
std::unique_ptr<SceneManager> mSceneManager; std::unique_ptr<SceneManager> mSceneManager;
std::unique_ptr<ImageManager> mImageManager; std::unique_ptr<ImageManager> mImageManager;

@ -628,6 +628,11 @@ namespace Resource
{ {
mCache->releaseGLObjects(state); mCache->releaseGLObjects(state);
mInstanceCache->releaseGLObjects(state); mInstanceCache->releaseGLObjects(state);
mShaderManager->releaseGLObjects(state);
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSharedStateMutex);
mSharedStateManager->releaseGLObjects(state);
} }
void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico) void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico)

@ -116,7 +116,7 @@ namespace Resource
/// Manually release created OpenGL objects for the given graphics context. This may be required /// 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. /// 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. /// Set up an IncrementalCompileOperation for background compiling of loaded scenes.
void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico); void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico);

@ -32,29 +32,29 @@ namespace SceneUtil
virtual void apply(osg::MatrixTransform& node) virtual void apply(osg::MatrixTransform& node)
{ {
applyNode(node); traverse(node);
}
virtual void apply(osg::Geometry& node)
{
applyNode(node);
} }
virtual void apply(osg::Node& node) virtual void apply(osg::Node& node)
{ {
applyNode(node); traverse(node);
} }
virtual void apply(osg::Group& 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) if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0)
|| (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0)) || (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0))
mToCopy.push_back(&node); {
else osg::Node* node = &drawable;
traverse(node); while (node && node->getNumParents() && !node->getStateSet())
node = node->getParent(0);
if (node)
mToCopy.push_back(node);
}
} }
void doCopy() void doCopy()

@ -8,7 +8,7 @@
#include <osgParticle/Emitter> #include <osgParticle/Emitter>
#include <osgParticle/Program> #include <osgParticle/Program>
#include <osgAnimation/MorphGeometry> #include <components/sceneutil/morphgeometry.hpp>
#include <components/sceneutil/riggeometry.hpp> #include <components/sceneutil/riggeometry.hpp>
@ -49,46 +49,12 @@ namespace SceneUtil
{ {
if (const osgParticle::ParticleSystem* partsys = dynamic_cast<const osgParticle::ParticleSystem*>(drawable)) if (const osgParticle::ParticleSystem* partsys = dynamic_cast<const osgParticle::ParticleSystem*>(drawable))
return operator()(partsys); return operator()(partsys);
if (dynamic_cast<const osgAnimation::MorphGeometry*>(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 ). if (dynamic_cast<const SceneUtil::RigGeometry*>(drawable) || dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
*/
copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_PRIMITIVES);
#endif
osg::Drawable* cloned = osg::clone(drawable, copyop);
return cloned;
}
if (dynamic_cast<const SceneUtil::RigGeometry*>(drawable))
{ {
return osg::clone(drawable, *this); return osg::clone(drawable, *this);
} }
return osg::CopyOp::operator()(drawable); return osg::CopyOp::operator()(drawable);
} }

@ -0,0 +1,190 @@
#include "morphgeometry.hpp"
#include <cassert>
namespace SceneUtil
{
MorphGeometry::MorphGeometry()
: mLastFrameNumber(0)
, mDirty(true)
, mMorphedBoundingBox(false)
{
}
MorphGeometry::MorphGeometry(const MorphGeometry &copy, const osg::CopyOp &copyop)
: osg::Drawable(copy, copyop)
, mMorphTargets(copy.mMorphTargets)
, mLastFrameNumber(0)
, mDirty(true)
, mMorphedBoundingBox(false)
{
setSourceGeometry(copy.getSourceGeometry());
}
void MorphGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> 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<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> 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<osg::Geometry> 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<mMorphTargets.size(); ++i)
if (mMorphTargets[i].getWeight() > 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<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
std::vector<osg::BoundingBox> 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; i<vertBounds.size(); ++i)
vertBounds[i].set(osg::Vec3f(0,0,0), osg::Vec3f(0,0,0));
for (unsigned int i = 0; i < mMorphTargets.size(); ++i)
{
const osg::Vec3Array& offsets = *mMorphTargets[i].getOffsets();
for (unsigned int j=0; j<offsets.size() && j<vertBounds.size(); ++j)
{
osg::BoundingBox& bounds = vertBounds[j];
bounds.expandBy(bounds._max + offsets[j]);
bounds.expandBy(bounds._min + offsets[j]);
}
}
osg::BoundingBox box;
for (unsigned int i=0; i<vertBounds.size(); ++i)
{
vertBounds[i]._max += sourceVerts[i];
vertBounds[i]._min += sourceVerts[i];
box.expandBy(vertBounds[i]);
}
return box;
}
}
void MorphGeometry::cull(osg::NodeVisitor *nv)
{
if (mLastFrameNumber == nv->getTraversalNumber() || !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<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(geom.getVertexArray());
assert(positionSrc->size() == positionDst->size());
for (unsigned int vertex=0; vertex<positionSrc->size(); ++vertex)
(*positionDst)[vertex] = (*positionSrc)[vertex];
for (unsigned int i=0; i<mMorphTargets.size(); ++i)
{
float weight = mMorphTargets[i].getWeight();
if (weight == 0.f)
continue;
const osg::Vec3Array* offsets = mMorphTargets[i].getOffsets();
for (unsigned int vertex=0; vertex<positionSrc->size(); ++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];
}
}

@ -0,0 +1,83 @@
#ifndef OPENMW_COMPONENTS_MORPHGEOMETRY_H
#define OPENMW_COMPONENTS_MORPHGEOMETRY_H
#include <osg/Geometry>
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<osg::Geometry> sourceGeom);
class MorphTarget
{
protected:
osg::ref_ptr<osg::Vec3Array> 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<MorphTarget> 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<osg::Geometry> 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<osg::Geometry> mSourceGeometry;
osg::ref_ptr<osg::Geometry> mGeometry[2];
osg::Geometry* getGeometry(unsigned int frame) const;
unsigned int mLastFrameNumber;
bool mDirty; // Have any morph targets changed?
mutable bool mMorphedBoundingBox;
};
}
#endif

@ -10,73 +10,17 @@
namespace SceneUtil 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<RigGeometry*>(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<RigGeometry*>(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() RigGeometry::RigGeometry()
: mSkeleton(NULL) : mSkeleton(NULL)
, mLastFrameNumber(0) , mLastFrameNumber(0)
, mBoundsFirstFrame(true) , mBoundsFirstFrame(true)
{ {
setCullCallback(new UpdateRigGeometry); setUpdateCallback(new osg::Callback); // dummy to make sure getNumChildrenRequiringUpdateTraversal() is correct
setUpdateCallback(new UpdateRigBounds); // update done in accept(NodeVisitor&)
setSupportsDisplayList(false);
setUseVertexBufferObjects(true);
setComputeBoundingBoxCallback(new DummyComputeBoundCallback);
} }
RigGeometry::RigGeometry(const RigGeometry &copy, const osg::CopyOp &copyop) RigGeometry::RigGeometry(const RigGeometry &copy, const osg::CopyOp &copyop)
: osg::Geometry(copy, copyop) : Drawable(copy, copyop)
, mSkeleton(NULL) , mSkeleton(NULL)
, mInfluenceMap(copy.mInfluenceMap) , mInfluenceMap(copy.mInfluenceMap)
, mLastFrameNumber(0) , mLastFrameNumber(0)
@ -89,57 +33,47 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
{ {
mSourceGeometry = sourceGeometry; mSourceGeometry = sourceGeometry;
osg::Geometry& from = *sourceGeometry; for (unsigned int i=0; i<2; ++i)
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<texCoordList.size(); ++i)
if (texCoordList[i])
setTexCoordArray(i, texCoordList[i], osg::Array::BIND_PER_VERTEX);
setVertexAttribArrayList(from.getVertexAttribArrayList());
// 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<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL);
if (vertexArray)
{ {
vertexArray->setVertexBufferObject(vbo); const osg::Geometry& from = *sourceGeometry;
setVertexArray(vertexArray); mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY);
} osg::Geometry& to = *mGeometry[i];
to.setSupportsDisplayList(false);
if (osg::Array* normals = from.getNormalArray()) to.setUseVertexBufferObjects(true);
{ to.setCullingActive(false); // make sure to disable culling since that's handled by this class
osg::ref_ptr<osg::Array> 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<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL);
if (vertexArray)
{ {
normalArray->setVertexBufferObject(vbo); vertexArray->setVertexBufferObject(vbo);
setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX); to.setVertexArray(vertexArray);
} }
}
if (const osg::Array* normals = from.getNormalArray())
{
osg::ref_ptr<osg::Array> 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<osg::Vec4Array*>(from.getTexCoordArray(7))) if (const osg::Vec4Array* tangents = dynamic_cast<const osg::Vec4Array*>(from.getTexCoordArray(7)))
{ {
mSourceTangents = tangents; mSourceTangents = tangents;
osg::ref_ptr<osg::Array> tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); osg::ref_ptr<osg::Array> tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL);
tangentArray->setVertexBufferObject(vbo); tangentArray->setVertexBufferObject(vbo);
setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX);
}
else
mSourceTangents = NULL;
} }
else
mSourceTangents = NULL;
} }
osg::ref_ptr<osg::Geometry> RigGeometry::getSourceGeometry() osg::ref_ptr<osg::Geometry> RigGeometry::getSourceGeometry()
@ -228,7 +162,7 @@ void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& mat
ptrresult[14] += ptr[14] * weight; ptrresult[14] += ptr[14] * weight;
} }
void RigGeometry::update(osg::NodeVisitor* nv) void RigGeometry::cull(osg::NodeVisitor* nv)
{ {
if (!mSkeleton) if (!mSkeleton)
{ {
@ -238,23 +172,27 @@ void RigGeometry::update(osg::NodeVisitor* nv)
return; return;
} }
if (!mSkeleton->getActive() && mLastFrameNumber != 0) if ((!mSkeleton->getActive() && mLastFrameNumber != 0) || mLastFrameNumber == nv->getTraversalNumber())
return; {
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
if (mLastFrameNumber == nv->getTraversalNumber()) nv->pushOntoNodePath(&geom);
nv->apply(geom);
nv->popFromNodePath();
return; return;
}
mLastFrameNumber = nv->getTraversalNumber(); mLastFrameNumber = nv->getTraversalNumber();
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
mSkeleton->updateBoneMatrices(nv->getTraversalNumber()); mSkeleton->updateBoneMatrices(nv->getTraversalNumber());
// skinning // skinning
osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray()); const osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray()); const osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
osg::Vec4Array* tangentSrc = mSourceTangents; const osg::Vec4Array* tangentSrc = mSourceTangents;
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(getVertexArray()); osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(geom.getVertexArray());
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(getNormalArray()); osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray());
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(getTexCoordArray(7)); osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(geom.getTexCoordArray(7));
for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it) for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it)
{ {
@ -294,6 +232,10 @@ void RigGeometry::update(osg::NodeVisitor* nv)
normalDst->dirty(); normalDst->dirty();
if (tangentDst) if (tangentDst)
tangentDst->dirty(); tangentDst->dirty();
nv->pushOntoNodePath(&geom);
nv->apply(geom);
nv->popFromNodePath();
} }
void RigGeometry::updateBounds(osg::NodeVisitor *nv) void RigGeometry::updateBounds(osg::NodeVisitor *nv)
@ -365,5 +307,32 @@ void RigGeometry::setInfluenceMap(osg::ref_ptr<InfluenceMap> influenceMap)
mInfluenceMap = 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();
}
} }

@ -13,10 +13,9 @@ namespace SceneUtil
/// @brief Mesh skinning implementation. /// @brief Mesh skinning implementation.
/// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton. /// @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 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 /// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while
/// using a FrameSwitch node that has two RigGeometry children. In the future we may want to consider implementing /// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext.
/// the double buffering inside RigGeometry. class RigGeometry : public osg::Drawable
class RigGeometry : public osg::Geometry
{ {
public: public:
RigGeometry(); RigGeometry();
@ -24,6 +23,9 @@ namespace SceneUtil
META_Object(SceneUtil, RigGeometry) 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 struct BoneInfluence
{ {
osg::Matrixf mInvBindMatrix; osg::Matrixf mInvBindMatrix;
@ -45,15 +47,19 @@ namespace SceneUtil
osg::ref_ptr<osg::Geometry> getSourceGeometry(); osg::ref_ptr<osg::Geometry> getSourceGeometry();
// Called automatically by our CullCallback virtual void accept(osg::NodeVisitor &nv);
void update(osg::NodeVisitor* nv); virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }
virtual void accept(osg::PrimitiveFunctor&) const;
// Called automatically by our UpdateCallback private:
void cull(osg::NodeVisitor* nv);
void updateBounds(osg::NodeVisitor* nv); void updateBounds(osg::NodeVisitor* nv);
private: osg::ref_ptr<osg::Geometry> mGeometry[2];
osg::Geometry* getGeometry(unsigned int frame) const;
osg::ref_ptr<osg::Geometry> mSourceGeometry; osg::ref_ptr<osg::Geometry> mSourceGeometry;
osg::ref_ptr<osg::Vec4Array> mSourceTangents; osg::ref_ptr<const osg::Vec4Array> mSourceTangents;
Skeleton* mSkeleton; Skeleton* mSkeleton;
osg::ref_ptr<osg::RefMatrix> mGeomToSkelMatrix; osg::ref_ptr<osg::RefMatrix> mGeomToSkelMatrix;

@ -6,6 +6,7 @@
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/skeleton.hpp> #include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/riggeometry.hpp> #include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
namespace SceneUtil namespace SceneUtil
{ {
@ -37,20 +38,20 @@ public:
} }
}; };
class FrameSwitchSerializer : public osgDB::ObjectWrapper class RigGeometrySerializer : public osgDB::ObjectWrapper
{ {
public: public:
FrameSwitchSerializer() RigGeometrySerializer()
: osgDB::ObjectWrapper(createInstanceFunc<osg::Group>, "NifOsg::FrameSwitch", "osg::Object osg::Node osg::Group NifOsg::FrameSwitch") : osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::RigGeometry>, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometry")
{ {
} }
}; };
class RigGeometrySerializer : public osgDB::ObjectWrapper class MorphGeometrySerializer : public osgDB::ObjectWrapper
{ {
public: public:
RigGeometrySerializer() MorphGeometrySerializer()
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::RigGeometry>, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable osg::Geometry SceneUtil::RigGeometry") : osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::MorphGeometry>, "SceneUtil::MorphGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::MorphGeometry")
{ {
} }
}; };
@ -95,8 +96,8 @@ void registerSerializers()
osgDB::ObjectWrapperManager* mgr = osgDB::Registry::instance()->getObjectWrapperManager(); osgDB::ObjectWrapperManager* mgr = osgDB::Registry::instance()->getObjectWrapperManager();
mgr->addWrapper(new PositionAttitudeTransformSerializer); mgr->addWrapper(new PositionAttitudeTransformSerializer);
mgr->addWrapper(new SkeletonSerializer); mgr->addWrapper(new SkeletonSerializer);
mgr->addWrapper(new FrameSwitchSerializer);
mgr->addWrapper(new RigGeometrySerializer); mgr->addWrapper(new RigGeometrySerializer);
mgr->addWrapper(new MorphGeometrySerializer);
mgr->addWrapper(new LightManagerSerializer); mgr->addWrapper(new LightManagerSerializer);
mgr->addWrapper(new CameraRelativeTransformSerializer); mgr->addWrapper(new CameraRelativeTransformSerializer);

@ -38,8 +38,6 @@ Skeleton::Skeleton()
, mNeedToUpdateBoneMatrices(true) , mNeedToUpdateBoneMatrices(true)
, mActive(true) , mActive(true)
, mLastFrameNumber(0) , mLastFrameNumber(0)
, mTraversedEvenFrame(false)
, mTraversedOddFrame(false)
{ {
} }
@ -50,8 +48,6 @@ Skeleton::Skeleton(const Skeleton &copy, const osg::CopyOp &copyop)
, mNeedToUpdateBoneMatrices(true) , mNeedToUpdateBoneMatrices(true)
, mActive(copy.mActive) , mActive(copy.mActive)
, mLastFrameNumber(0) , mLastFrameNumber(0)
, mTraversedEvenFrame(false)
, mTraversedOddFrame(false)
{ {
} }
@ -115,11 +111,6 @@ void Skeleton::updateBoneMatrices(unsigned int traversalNumber)
mLastFrameNumber = traversalNumber; mLastFrameNumber = traversalNumber;
if (mLastFrameNumber % 2 == 0)
mTraversedEvenFrame = true;
else
mTraversedOddFrame = true;
if (mNeedToUpdateBoneMatrices) if (mNeedToUpdateBoneMatrices)
{ {
if (mRootBone.get()) if (mRootBone.get())
@ -144,18 +135,14 @@ bool Skeleton::getActive() const
void Skeleton::markDirty() void Skeleton::markDirty()
{ {
mTraversedEvenFrame = false; mLastFrameNumber = 0;
mTraversedOddFrame = false;
mBoneCache.clear(); mBoneCache.clear();
mBoneCacheInit = false; mBoneCacheInit = false;
} }
void Skeleton::traverse(osg::NodeVisitor& nv) void Skeleton::traverse(osg::NodeVisitor& nv)
{ {
if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR && mLastFrameNumber != 0)
// 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)
return; return;
osg::Group::traverse(nv); osg::Group::traverse(nv);
} }

@ -74,8 +74,6 @@ namespace SceneUtil
bool mActive; bool mActive;
unsigned int mLastFrameNumber; unsigned int mLastFrameNumber;
bool mTraversedEvenFrame;
bool mTraversedOddFrame;
}; };
} }

@ -158,4 +158,13 @@ namespace Shader
return found->second; return found->second;
} }
void ShaderManager::releaseGLObjects(osg::State *state)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
for (auto shader : mShaders)
shader.second->releaseGLObjects(state);
for (auto program : mPrograms)
program.second->releaseGLObjects(state);
}
} }

@ -32,6 +32,7 @@ namespace Shader
osg::ref_ptr<osg::Program> getProgram(osg::ref_ptr<osg::Shader> vertexShader, osg::ref_ptr<osg::Shader> fragmentShader); osg::ref_ptr<osg::Program> getProgram(osg::ref_ptr<osg::Shader> vertexShader, osg::ref_ptr<osg::Shader> fragmentShader);
void releaseGLObjects(osg::State* state);
private: private:
std::string mPath; std::string mPath;

@ -13,6 +13,7 @@
#include <components/resource/imagemanager.hpp> #include <components/resource/imagemanager.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/sceneutil/riggeometry.hpp> #include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include "shadermanager.hpp" #include "shadermanager.hpp"
@ -26,6 +27,7 @@ namespace Shader
, mMaterialOverridden(false) , mMaterialOverridden(false)
, mNormalHeight(false) , mNormalHeight(false)
, mTexStageRequiringTangents(-1) , mTexStageRequiringTangents(-1)
, mNode(NULL)
{ {
} }
@ -69,7 +71,7 @@ namespace Shader
{ {
if (node.getStateSet()) if (node.getStateSet())
{ {
pushRequirements(); pushRequirements(node);
applyStateSet(node.getStateSet(), node); applyStateSet(node.getStateSet(), node);
traverse(node); traverse(node);
popRequirements(); popRequirements();
@ -234,9 +236,10 @@ namespace Shader
} }
} }
void ShaderVisitor::pushRequirements() void ShaderVisitor::pushRequirements(osg::Node& node)
{ {
mRequirements.push_back(mRequirements.back()); mRequirements.push_back(mRequirements.back());
mRequirements.back().mNode = &node;
} }
void ShaderVisitor::popRequirements() void ShaderVisitor::popRequirements()
@ -244,8 +247,12 @@ namespace Shader
mRequirements.pop_back(); 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; osg::StateSet* writableStateSet = NULL;
if (mAllowedToModifyStateSets) if (mAllowedToModifyStateSets)
writableStateSet = node.getOrCreateStateSet(); 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<int, std::string>::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<osgUtil::TangentSpaceGenerator> 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) void ShaderVisitor::apply(osg::Geometry& geometry)
{ {
bool needPop = (geometry.getStateSet() != NULL); 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); applyStateSet(geometry.getStateSet(), geometry);
} }
@ -315,44 +352,9 @@ namespace Shader
{ {
const ShaderRequirements& reqs = mRequirements.back(); const ShaderRequirements& reqs = mRequirements.back();
bool useShader = reqs.mShaderRequired || mForceShaders; adjustGeometry(geometry, reqs);
bool generateTangents = reqs.mTexStageRequiringTangents != -1;
if (mAllowedToModifyStateSets && (useShader || generateTangents))
{
osg::ref_ptr<osg::Geometry> sourceGeometry = &geometry;
SceneUtil::RigGeometry* rig = dynamic_cast<SceneUtil::RigGeometry*>(&geometry);
if (rig)
sourceGeometry = rig->getSourceGeometry();
bool requiresSetGeometry = false;
// make sure that all UV sets are there createProgram(reqs);
for (std::map<int, std::string>::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<osgUtil::TangentSpaceGenerator> 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);
} }
if (needPop) if (needPop)
@ -366,16 +368,27 @@ namespace Shader
if (drawable.getStateSet()) if (drawable.getStateSet())
{ {
pushRequirements(); pushRequirements(drawable);
applyStateSet(drawable.getStateSet(), drawable); applyStateSet(drawable.getStateSet(), drawable);
} }
if (!mRequirements.empty()) if (!mRequirements.empty())
{ {
const ShaderRequirements& reqs = mRequirements.back(); const ShaderRequirements& reqs = mRequirements.back();
// TODO: find a better place for the stateset createProgram(reqs);
if (reqs.mShaderRequired || mForceShaders)
createProgram(reqs, drawable); if (auto rig = dynamic_cast<SceneUtil::RigGeometry*>(&drawable))
{
osg::ref_ptr<osg::Geometry> sourceGeometry = rig->getSourceGeometry();
if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs))
rig->setSourceGeometry(sourceGeometry);
}
else if (auto morph = dynamic_cast<SceneUtil::MorphGeometry*>(&drawable))
{
osg::ref_ptr<osg::Geometry> sourceGeometry = morph->getSourceGeometry();
if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs))
morph->setSourceGeometry(sourceGeometry);
}
} }
if (needPop) if (needPop)

@ -52,7 +52,7 @@ namespace Shader
void applyStateSet(osg::ref_ptr<osg::StateSet> stateset, osg::Node& node); void applyStateSet(osg::ref_ptr<osg::StateSet> stateset, osg::Node& node);
void pushRequirements(); void pushRequirements(osg::Node& node);
void popRequirements(); void popRequirements();
private: private:
@ -89,13 +89,17 @@ namespace Shader
// -1 == no tangents required // -1 == no tangents required
int mTexStageRequiringTangents; int mTexStageRequiringTangents;
// the Node that requested these requirements
osg::Node* mNode;
}; };
std::vector<ShaderRequirements> mRequirements; std::vector<ShaderRequirements> mRequirements;
std::string mDefaultVsTemplate; std::string mDefaultVsTemplate;
std::string mDefaultFsTemplate; std::string mDefaultFsTemplate;
void createProgram(const ShaderRequirements& reqs, osg::Node& node); void createProgram(const ShaderRequirements& reqs);
bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs);
}; };
} }

@ -243,4 +243,18 @@ namespace Terrain
} }
} }
void BufferCache::releaseGLObjects(osg::State *state)
{
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mIndexBufferMutex);
for (auto indexbuffer : mIndexBufferMap)
indexbuffer.second->releaseGLObjects(state);
}
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mUvBufferMutex);
for (auto uvbuffer : mUvBufferMap)
uvbuffer.second->releaseGLObjects(state);
}
}
} }

@ -24,7 +24,7 @@ namespace Terrain
void clearCache(); void clearCache();
// TODO: add releaseGLObjects() for our vertex/element buffer objects void releaseGLObjects(osg::State* state);
private: private:
// Index buffers are shared across terrain batches where possible. There is one index buffer for each // Index buffers are shared across terrain batches where possible. There is one index buffer for each

@ -62,6 +62,12 @@ void ChunkManager::clearCache()
mBufferCache.clearCache(); mBufferCache.clearCache();
} }
void ChunkManager::releaseGLObjects(osg::State *state)
{
ResourceManager::releaseGLObjects(state);
mBufferCache.releaseGLObjects(state);
}
void ChunkManager::setCullingActive(bool active) void ChunkManager::setCullingActive(bool active)
{ {
mCullingActive = active; mCullingActive = active;

@ -36,6 +36,8 @@ namespace Terrain
virtual void clearCache(); virtual void clearCache();
void releaseGLObjects(osg::State* state) override;
void setCullingActive(bool active); void setCullingActive(bool active);
private: private:

@ -23,6 +23,7 @@ namespace Gui
private: private:
MyGUI::Widget* mLeft; MyGUI::Widget* mLeft;
MyGUI::Widget* mRight; MyGUI::Widget* mRight;
MyGUI::Widget* mClient;
void align(); void align();
}; };

@ -14,22 +14,22 @@ Basics
Directories 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 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 different sub-directories. The location of the user directory is hard-coded
into the CS and depends on your operating system. into the CS and depends on your operating system.
================ ========================================= ================ =========================================
Operating System User Dircetory Operating System User Directory
================ ========================================= ================ =========================================
GNU/Linux ``<whatever>`` GNU/Linux ``~/.config/openmw/``
OS X ``~/Library/Application Support/openmw/`` OS X ``~/Library/Application Support/openmw/``
Windows ``<whatever>`` Windows ``C:\Users\ *Username* \Documents\my games\OpenMW``
================ ========================================= ================ =========================================
In addition to to this single hard-coded directory both OpenMW and OpenMW CS 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, 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 an so one. These 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 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 *data paths*) as specified in the configuration. Usually one data path points
to the directory where the original Morrowind game is either installed or 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 The original Morrowind engine by Bethesda Softworks uses two types of content
files: `esm` (master) and `esp` (plugin). The distinction between those two is 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 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 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 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 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 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 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. 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 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 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 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 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 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 Morrowind content files
----------------------- -----------------------
Using our content files is recommended for projects that are intended to used Using our content files is recommended for projects that are intended to use
with the OpenMW engine. However, some players might wish to still use the the OpenMW engine. However, some players might wish to still use the
original Morrowind engine. In addition thousands of *ESP*/*ESM* files were original Morrowind engine. In addition thousands of *ESP*/*ESM* files were
created since 2002, some of them with really outstanding content. Because of 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 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 you decide 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 types you are most likely aiming at compatibility with the original engine. This
subject is covered in it own chapter of this manual. subject is covered in its own chapter of this manual.
.. TODO This paragraph sounds weird .. TODO This paragraph sounds weird
The actual creation of new files is described in the next chapter. Here we are 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 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 lets jut first OpenMW CS file while fully understanding your needs. For now lets just
remember that content files are created inside the user directory in the the remember that content files are created inside the user directory in the the
``data`` subdirectory (that is the one special data directory mentioned ``data`` subdirectory (that is the one special data directory mentioned
earlier). earlier).
@ -99,8 +99,8 @@ Dependencies
------------ ------------
Since an addon is supposed to change the game it follows that it also depends 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 on the said game to work. We can conceptualise this with an example: your
modification is the changing prize of an iron sword, but what if there is no 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 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 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 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 in mind that dependencies exist, and is up to you to decide whether your
content file should depend on other content files. 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 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 file, and since a game file makes the base for addon files it can not depend on
addon files. addon files.
@ -123,7 +123,7 @@ Project files
------------- -------------
Project files act as containers for data not used by the OpenMW game engine 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 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 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. 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 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 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 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 content file with appended extension. For instance a ``swords.omwaddon`` file
is associated with a ``swords.omwaddon.project`` file. is associated with a ``swords.omwaddon.project`` file.

@ -3,7 +3,7 @@ OpenMW CS Starting Dialog
In this chapter we will cover starting up OpenMW CS and the starting interface. 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 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 wrench-icon. The wrench will open the configuration dialog which we will cover
later. The three main buttons are the following: 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 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 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 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. and automatically include its dependencies when you choose it.
If you want to edit an existing content file you will be presented with a If you want to edit an existing content file you will be presented with a

@ -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. a blank window rather than a table choose *World**Objects* from the menu.
.. figure:: _static/images/chapter-1/objects.png .. 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 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 *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 A filter is defined by a number of *queries* which can be logically linked. For
now all that matters is that the `string(<property>, <pattern>)` query will check now all that matters is that the `string(<property>, <pattern>)` query will check
whether `<propery>` matches `<pattern>`. The pattern is a regular expression, whether `<property>` matches `<pattern>`. The pattern is a regular expression,
if you don't know about them you should learn their syntax. For now all that 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, 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 even zero. In other words, we are looking for all entries which have an ID that

@ -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_: #. Open up each of the models in NifSkope and look for these certain blocks_:
- NiTextureEffect - NiTextureEffect
- NiSourceTexture with the value that appears to be a normal map file, in this mod, they have the suffix *_nm.dds*. - 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. #. 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. #. 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. #. 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.

@ -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 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 reflect actors
-------------- --------------
@ -68,6 +68,8 @@ reflect actors
This setting controls whether or not NPCs and creatures are drawn in water reflections. 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. 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 small feature culling pixel size
-------------------------------- --------------------------------

Loading…
Cancel
Save