Merge branch 'master' into new-script-api

new-script-api
Koncord 7 years ago
commit 045dc566ea

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

@ -1,3 +1,43 @@
# Apps and tools
option(BUILD_OPENMW "build OpenMW" ON)
option(BUILD_OPENMW_MP "build OpenMW-MP" ON)
option(BUILD_MASTER "build tes3mp master server" OFF)
option(BUILD_BSATOOL "build BSA extractor" ON)
option(BUILD_ESMTOOL "build ESM inspector" ON)
option(BUILD_LAUNCHER "build Launcher" ON)
option(BUILD_BROWSER "build tes3mp Server Browser" 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_BROWSER 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,24 +99,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_OPENMW_MP "build OpenMW-MP" ON)
option(BUILD_MASTER "build tes3mp master server" OFF)
option(BUILD_BSATOOL "build BSA extractor" ON)
option(BUILD_ESMTOOL "build ESM inspector" ON)
option(BUILD_LAUNCHER "build Launcher" ON)
option(BUILD_BROWSER "build tes3mp Server Browser" 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.
@ -126,16 +148,8 @@ endif()
find_package(RakNet REQUIRED) find_package(RakNet REQUIRED)
include_directories(${RakNet_INCLUDES}) include_directories(${RakNet_INCLUDES})
if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER 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 5 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)
@ -150,17 +164,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()
IF(BUILD_OPENMW OR BUILD_OPENCS) IF(BUILD_OPENMW OR BUILD_OPENCS)
# Sound setup # Sound setup
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE) find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
@ -217,7 +220,8 @@ endif()
IF(BUILD_OPENMW OR BUILD_OPENCS) IF(BUILD_OPENMW OR BUILD_OPENCS)
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX) find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
set(USED_OSG_PLUGINS set(USED_OSG_PLUGINS
osgdb_bmp osgdb_bmp
@ -261,8 +265,6 @@ IF(BUILD_OPENMW OR BUILD_OPENCS)
ENDIF(BUILD_OPENMW OR BUILD_OPENCS) ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
set(BOOST_COMPONENTS system filesystem program_options) set(BOOST_COMPONENTS system filesystem program_options)
if(WIN32) if(WIN32)
@ -357,6 +359,8 @@ if (NOT WIN32 AND NOT APPLE)
"${OpenMW_BINARY_DIR}/openmw.desktop") "${OpenMW_BINARY_DIR}/openmw.desktop")
configure_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml configure_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml
"${OpenMW_BINARY_DIR}/openmw.appdata.xml") "${OpenMW_BINARY_DIR}/openmw.appdata.xml")
configure_file(${OpenMW_SOURCE_DIR}/files/tes3mp-browser.desktop
"${OpenMW_BINARY_DIR}/tes3mp-browser.desktop")
configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop
"${OpenMW_BINARY_DIR}/openmw-cs.desktop") "${OpenMW_BINARY_DIR}/openmw-cs.desktop")
endif() endif()
@ -437,6 +441,9 @@ IF(NOT WIN32 AND NOT APPLE)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/metainfo" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/metainfo" COMPONENT "openmw")
IF(BUILD_BROWSER)
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-browser.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "browser")
ENDIF(BUILD_BROWSER)
IF(BUILD_OPENCS) IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs")

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

@ -131,7 +131,7 @@ add_openmw_dir (mwmp/processors/world BaseObjectProcessor ProcessorConsoleComman
# Main executable # Main executable
if (NOT ANDROID) if (NOT ANDROID)
add_executable(tes3mp openmw_add_executable(tes3mp
${OPENMW_FILES} ${OPENMW_FILES}
${GAME} ${GAME_HEADER} ${GAME} ${GAME_HEADER}
${APPLE_BUNDLE_RESOURCES} ${APPLE_BUNDLE_RESOURCES}

@ -257,6 +257,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;
}; };

@ -380,7 +380,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

@ -374,7 +374,9 @@ 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}");
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)) // 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}"); 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);

@ -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");
} }

@ -269,6 +269,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();

@ -129,7 +129,7 @@ namespace MWGui
Disable increases for Security and Sneak when using ignoreJailSkillIncreases Disable increases for Security and Sneak when using ignoreJailSkillIncreases
*/ */
if (mwmp::Main::get().getLocalPlayer()->ignoreJailSkillIncreases) if (mwmp::Main::get().getLocalPlayer()->ignoreJailSkillIncreases)
value.setBase(value.getBase() - 1); value.setBase(std::max(0, value.getBase()-1));
else if (skill == ESM::Skill::Security || skill == ESM::Skill::Sneak) else if (skill == ESM::Skill::Security || skill == ESM::Skill::Sneak)
/* /*
End of tes3mp change (minor) End of tes3mp change (minor)

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

@ -344,16 +344,17 @@ namespace MWGui
{ {
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>(); MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); 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 // delay weapon switching if player is busy
if (isDelayNeeded && isWeapon) if (isDelayNeeded && (isWeapon || isTool))
{ {
mActivatedIndex = index; mActivatedIndex = index;
return; return;
} }
// disable weapon switching if player is dead or paralyzed // disable weapon switching if player is dead or paralyzed
if (isReturnNeeded && isWeapon) if (isReturnNeeded && (isWeapon || isTool))
{ {
return; return;
} }

@ -2128,9 +2128,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)

@ -408,7 +408,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);

@ -985,7 +985,11 @@ namespace MWInput
if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"]) if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"])
return; return;
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer())) // 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; return;
MWMechanics::DrawState_ state = mPlayer->getDrawState(); MWMechanics::DrawState_ state = mPlayer->getDrawState();

@ -447,6 +447,11 @@ namespace MWMechanics
// 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)
{ {
@ -851,6 +856,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);
@ -1065,7 +1080,18 @@ namespace MWMechanics
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr))
{ {
static const int iCrimeThresholdMultiplier = esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->getInt(); static const int iCrimeThresholdMultiplier = esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->getInt();
if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier)
/*
Start of tes3mp change (major)
Only attack players based on their high bounty if they haven't died since the last
time an attempt was made to arrest them
*/
if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier
&& !mwmp::Main::get().getLocalPlayer()->diedSinceArrestAttempt)
/*
End of tes3mp change (major)
*/
{ {
MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player);
creatureStats.setHitAttemptActorId(player.getClass().getCreatureStats(player).getActorId()); // Stops the guard from quitting combat if player is unreachable creatureStats.setHitAttemptActorId(player.getClass().getCreatureStats(player).getActorId()); // Stops the guard from quitting combat if player is unreachable
@ -1096,6 +1122,24 @@ namespace MWMechanics
// Update witness crime id // Update witness crime id
npcStats.setCrimeId(-1); npcStats.setCrimeId(-1);
} }
/*
Start of tes3mp addition
If the player has died, stop combat with them as though they had
paid their bounty
*/
else if (mwmp::Main::get().getLocalPlayer()->diedSinceArrestAttempt)
{
if (creatureStats.getAiSequence().isInCombat(player))
{
creatureStats.getAiSequence().stopCombat();
creatureStats.setAttacked(false);
creatureStats.setAlarmed(false);
}
}
/*
End of tes3mp addition
*/
} }
} }
} }

@ -117,6 +117,7 @@ namespace MWMechanics
End of tes3mp addition End of tes3mp addition
*/ */
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);

@ -13,7 +13,10 @@
Include additional headers for multiplayer purposes Include additional headers for multiplayer purposes
*/ */
#include <components/openmw-mp/Log.hpp>
#include "../mwgui/windowmanagerimp.hpp" #include "../mwgui/windowmanagerimp.hpp"
#include "../mwmp/Main.hpp"
#include "../mwmp/LocalPlayer.hpp"
/* /*
End of tes3mp addition End of tes3mp addition
*/ */
@ -81,6 +84,18 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte
if (pathTo(actor, dest, duration, 100)) { if (pathTo(actor, dest, duration, 100)) {
target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached
/*
Start of tes3mp addition
Record that the player has not died since the last attempt to arrest them
*/
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "After being pursued by %s, diedSinceArrestAttempt is now false", actor.getCellRef().getRefId().c_str());
mwmp::Main::get().getLocalPlayer()->diedSinceArrestAttempt = false;
/*
End of tes3mp addition
*/
return true; return true;
} }

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

@ -2357,6 +2357,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);

@ -263,6 +263,7 @@ 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;

@ -436,6 +436,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);

@ -220,8 +220,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);

@ -422,7 +422,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())

@ -61,6 +61,10 @@ LocalPlayer::LocalPlayer()
jailProgressText = ""; jailProgressText = "";
jailEndText = ""; jailEndText = "";
isWerewolf = false;
diedSinceArrestAttempt = false;
} }
LocalPlayer::~LocalPlayer() LocalPlayer::~LocalPlayer()
@ -93,15 +97,8 @@ void LocalPlayer::update()
updateDeadState(); updateDeadState();
updateEquipment(); updateEquipment();
updateStatsDynamic(); updateStatsDynamic();
updateAttributes();
// Only send attributes and skills if we are not a werewolf, or they will be updateSkills();
// overwritten by the werewolf ones
if (!isWerewolf)
{
updateAttributes();
updateSkills();
}
updateLevel(); updateLevel();
updateBounty(); updateBounty();
} }
@ -230,6 +227,10 @@ void LocalPlayer::updateStatsDynamic(bool forceUpdate)
void LocalPlayer::updateAttributes(bool forceUpdate) void LocalPlayer::updateAttributes(bool forceUpdate)
{ {
// Only send attributes if we are not a werewolf, or they will be
// overwritten by the werewolf ones
if (isWerewolf) return;
MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWWorld::Ptr ptrPlayer = getPlayerPtr();
const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer);
bool attributesChanged = false; bool attributesChanged = false;
@ -252,6 +253,10 @@ void LocalPlayer::updateAttributes(bool forceUpdate)
void LocalPlayer::updateSkills(bool forceUpdate) void LocalPlayer::updateSkills(bool forceUpdate)
{ {
// Only send skills if we are not a werewolf, or they will be
// overwritten by the werewolf ones
if (isWerewolf) return;
MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWWorld::Ptr ptrPlayer = getPlayerPtr();
const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer);

@ -52,6 +52,12 @@ namespace mwmp
// readied but be unable to use it unless we clear it here // readied but be unable to use it unless we clear it here
playerPtr.getClass().getNpcStats(playerPtr).setDrawState(MWMechanics::DrawState_Nothing); playerPtr.getClass().getNpcStats(playerPtr).setDrawState(MWMechanics::DrawState_Nothing);
// Record that the player has died since the last attempt was made to arrest them,
// used to make guards lenient enough to attempt an arrest again
player->diedSinceArrestAttempt = true;
LOG_APPEND(Log::LOG_INFO, "- diedSinceArrestAttempt is now true");
packet.setPlayer(player); packet.setPlayer(player);
packet.Send(serverAddr); packet.Send(serverAddr);

@ -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);
} }
} }

@ -1310,6 +1310,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);
} }
@ -1336,6 +1344,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)));

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

@ -145,3 +145,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)

@ -52,7 +52,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); parentNode->addChild(drawable);
frameswitch->addChild(geometry);
frameswitch->addChild(geom2);
parentNode->addChild(frameswitch);
}
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)

@ -243,6 +243,8 @@ namespace mwmp
std::string jailEndText; std::string jailEndText;
unsigned int resurrectType; unsigned int resurrectType;
bool diedSinceArrestAttempt;
}; };
} }

@ -80,11 +80,14 @@ namespace mwmp
{ {
if (write) if (write)
{ {
RakNet::RakString rstr("%s", str.c_str());
if (compress) if (compress)
rstr.SerializeCompressed(bs); RakNet::RakString::SerializeCompressed(str.c_str(), bs);
else else
{
RakNet::RakString rstr;
rstr = str.c_str();
bs->Write(rstr); bs->Write(rstr);
}
} }
else else
{ {

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

@ -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);
}; };
} }

@ -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();
}; };

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

@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=TES3MP Server Browser
GenericName=Server Browser
Comment=Multiplayer extension for TES3.
Keywords=Morrowind;Multiplayer;Server Browser;TES;openmw;
TryExec=tes3mp-browser
Exec=tes3mp-browser
Icon=openmw
Categories=Game;RolePlaying;
Loading…
Cancel
Save