diff --git a/AUTHORS.md b/AUTHORS.md index 09ab78412..e6ff67293 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -49,6 +49,7 @@ Programmers Cédric Mocquillon Chris Boyce (slothlife) Chris Robinson (KittyCat) + Coleman Smith (olcoal) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) crussell187 @@ -122,7 +123,6 @@ Programmers Lordrea Łukasz Gołębiewski (lukago) Lukasz Gromanowski (lgro) - Manuel Edelmann (vorenon) Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) diff --git a/CHANGELOG.md b/CHANGELOG.md index e54515676..d896ca55c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.47.0 ------ + Bug #832: OpenMW-CS: Handle deleted references Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path Bug #1952: Incorrect particle lighting Bug #2069: Fireflies in Fireflies invade Morrowind look wrong @@ -67,14 +68,19 @@ Bug #5644: Summon effects running on the player during game initialization cause crashes Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval + Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE + Bug #5731: Editor: skirts are invisible on characters Feature #390: 3rd person look "over the shoulder" + Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container + Feature #2686: Timestamps in openmw.log Feature #4894: Consider actors as obstacles for pathfinding Feature #5043: Head Bobbing + Feature #5199: Improve Scene Colors Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher Feature #5362: Show the soul gems' trapped soul in count dialog Feature #5445: Handle NiLines @@ -93,6 +99,7 @@ Feature #5649: Skyrim SE compressed BSA format support Feature #5672: Make stretch menu background configuration more accessible Feature #5692: Improve spell/magic item search to factor in magic effect names + Feature #5730: Add graphic herbalism option to the launcher and documents Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 6a16b7658..8a7948bb7 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -34,7 +34,9 @@ Bug Fixes: - Morrowind legacy madness: Using a key on a trapped door/container now only disarms the trap if the door/container is locked (#5370) Editor Bug Fixes: +- Deleted and moved objects within a cell are now saved properly (#832) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) +- Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) Miscellaneous: diff --git a/CMakeLists.txt b/CMakeLists.txt index 76764fbd9..be7bc79e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -584,6 +584,12 @@ if (WIN32) 5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file (config_begin.hpp, config_end.hpp) ) endif() + + if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.0" ) + set(WARNINGS_DISABLE ${WARNINGS_DISABLE} + 4866 # compiler may not enforce left-to-right evaluation order for call + ) + endif() foreach(d ${WARNINGS_DISABLE}) set(WARNINGS "${WARNINGS} /wd${d}") diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index b35e78639..ed0407235 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -153,6 +153,7 @@ bool Launcher::AdvancedPage::loadSettings() if (showOwnedIndex >= 0 && showOwnedIndex <= 3) showOwnedComboBox->setCurrentIndex(showOwnedIndex); loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); + loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); } // Bug fixes @@ -279,6 +280,7 @@ void Launcher::AdvancedPage::saveSettings() if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); + saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); } // Bug fixes diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 39aae48bd..588be9ccb 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -210,6 +210,19 @@ void CSMPrefs::State::declare() setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world."). setRange(10, 10000); declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1); + declareBool("scene-use-gradient", "Use Gradient Background", true); + declareColour ("scene-day-background-colour", "Day Background Colour", QColor (110, 120, 128, 255)); + declareColour ("scene-day-gradient-colour", "Day Gradient Colour", QColor (47, 51, 51, 255)). + setTooltip("Sets the gradient color to use in conjunction with the day background color. Ignored if " + "the gradient option is disabled."); + declareColour ("scene-bright-background-colour", "Scene Bright Background Colour", QColor (79, 87, 92, 255)); + declareColour ("scene-bright-gradient-colour", "Scene Bright Gradient Colour", QColor (47, 51, 51, 255)). + setTooltip("Sets the gradient color to use in conjunction with the bright background color. Ignored if " + "the gradient option is disabled."); + declareColour ("scene-night-background-colour", "Scene Night Background Colour", QColor (64, 77, 79, 255)); + declareColour ("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor (47, 51, 51, 255)). + setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if " + "the gradient option is disabled."); declareCategory ("Tooltips"); declareBool ("scene", "Show Tooltips in 3D scenes", true); diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 5ed80a1e4..8558aa9bc 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -593,56 +593,33 @@ namespace CSMWorld } else if (type == UniversalId::Type_Clothing) { - int priority = 0; - // TODO: reserve bodyparts for robes and skirts auto& clothing = dynamic_cast&>(record).get(); + std::vector parts; if (clothing.mData.mType == ESM::Clothing::Robe) { - auto reservedList = std::vector(); - - ESM::PartReference pr; - pr.mMale = ""; - pr.mFemale = ""; - - ESM::PartReferenceType parts[] = { + parts = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, - ESM::PRT_RForearm, ESM::PRT_LForearm + ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass }; - size_t parts_size = sizeof(parts)/sizeof(parts[0]); - for(size_t p = 0;p < parts_size;++p) - { - pr.mPart = parts[p]; - reservedList.push_back(pr); - } - - priority = parts_size; - addParts(reservedList, priority); } else if (clothing.mData.mType == ESM::Clothing::Skirt) { - auto reservedList = std::vector(); - - ESM::PartReference pr; - pr.mMale = ""; - pr.mFemale = ""; - - ESM::PartReferenceType parts[] = { - ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg - }; - size_t parts_size = sizeof(parts)/sizeof(parts[0]); - for(size_t p = 0;p < parts_size;++p) - { - pr.mPart = parts[p]; - reservedList.push_back(pr); - } - - priority = parts_size; - addParts(reservedList, priority); + parts = {ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg}; } + std::vector reservedList; + for (const auto& p : parts) + { + ESM::PartReference pr; + pr.mPart = p; + reservedList.emplace_back(pr); + } + + int priority = parts.size(); addParts(clothing.mParts.mParts, priority); + addParts(reservedList, priority); // Changing parts could affect what is picked for rendering data->addOtherDependency(itemId); diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index d8f6b391b..126a0ea78 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -64,10 +64,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // ignore content file number std::map::iterator iter = cache.begin(); - ref.mRefNum.mIndex = ref.mRefNum.mIndex & 0x00ffffff; + unsigned int thisIndex = ref.mRefNum.mIndex & 0x00ffffff; + if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; + for (; iter != cache.end(); ++iter) { - if (ref.mRefNum.mIndex == iter->first.mIndex) + if (thisIndex == iter->first.mIndex) break; } diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index b6fee1545..ac260fe83 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -151,9 +151,9 @@ void CSVRender::CellArrow::buildShape() osg::Vec4Array *colours = new osg::Vec4Array; for (int i=0; i<6; ++i) - colours->push_back (osg::Vec4f (1.0f, 0.0f, 0.0f, 1.0f)); + colours->push_back (osg::Vec4f (0.11, 0.6f, 0.95f, 1.0f)); for (int i=0; i<6; ++i) - colours->push_back (osg::Vec4f (0.8f, (i==2 || i==5) ? 0.6f : 0.4f, 0.0f, 1.0f)); + colours->push_back (osg::Vec4f (0.08f, 0.44f, 0.7f, 1.0f)); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index d45b9daaa..4babb5881 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -70,7 +70,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) setLayout(layout); mView->getCamera()->setGraphicsContext(window); - mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) ); mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; @@ -213,6 +212,25 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() ); + // set up gradient view or configured clear color + QColor bgColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); + + if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) { + QColor gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); + mGradientCamera = createGradientCamera(bgColour, gradientColour); + + mView->getCamera()->setClearMask(0); + mView->getCamera()->addChild(mGradientCamera.get()); + } + else { + mView->getCamera()->setClearColor(osg::Vec4( + bgColour.redF(), + bgColour.greenF(), + bgColour.blueF(), + 1.0f + )); + } + // we handle lighting manually mView->setLightingMode(osgViewer::View::NO_LIGHT); @@ -250,6 +268,79 @@ SceneWidget::~SceneWidget() mResourceSystem->releaseGLObjects(SDLUtil::GraphicsWindowSDL2::findContext(*mView)->getState()); } + +osg::ref_ptr SceneWidget::createGradientRectangle(QColor bgColour, QColor gradientColour) +{ + osg::ref_ptr geometry = new osg::Geometry; + + osg::ref_ptr vertices = new osg::Vec3Array; + + vertices->push_back(osg::Vec3(0.0f, 0.0f, -1.0f)); + vertices->push_back(osg::Vec3(1.0f, 0.0f, -1.0f)); + vertices->push_back(osg::Vec3(0.0f, 1.0f, -1.0f)); + vertices->push_back(osg::Vec3(1.0f, 1.0f, -1.0f)); + + geometry->setVertexArray(vertices); + + osg::ref_ptr primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // triangle 1 + primitives->push_back (0); + primitives->push_back (1); + primitives->push_back (2); + + // triangle 2 + primitives->push_back (2); + primitives->push_back (1); + primitives->push_back (3); + + geometry->addPrimitiveSet(primitives); + + osg::ref_ptr colours = new osg::Vec4ubArray; + colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); + colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f)); + colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); + colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f)); + + geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + geometry->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + return geometry; +} + + +osg::ref_ptr SceneWidget::createGradientCamera(QColor bgColour, QColor gradientColour) +{ + osg::ref_ptr camera = new osg::Camera(); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1.0f, 0, 1.0f)); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setViewMatrix(osg::Matrix::identity()); + + camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + camera->setAllowEventFocus(false); + + // draw subgraph before main camera view. + camera->setRenderOrder(osg::Camera::PRE_RENDER); + + camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + osg::ref_ptr gradientQuad = createGradientRectangle(bgColour, gradientColour); + + camera->addChild(gradientQuad); + return camera; +} + + +void SceneWidget::updateGradientCamera(QColor bgColour, QColor gradientColour) +{ + osg::ref_ptr gradientRect = createGradientRectangle(bgColour, gradientColour); + // Replaces previous rectangle + mGradientCamera->setChild(0, gradientRect.get()); +} + void SceneWidget::setLighting(Lighting *lighting) { if (mLighting) @@ -277,12 +368,59 @@ void SceneWidget::setAmbient(const osg::Vec4f& ambient) void SceneWidget::selectLightingMode (const std::string& mode) { - if (mode=="day") - setLighting (&mLightingDay); - else if (mode=="night") - setLighting (&mLightingNight); - else if (mode=="bright") - setLighting (&mLightingBright); + QColor backgroundColour; + QColor gradientColour; + if (mode == "day") + { + backgroundColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor(); + gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor(); + setLighting(&mLightingDay); + } + else if (mode == "night") + { + backgroundColour = CSMPrefs::get()["Rendering"]["scene-night-background-colour"].toColor(); + gradientColour = CSMPrefs::get()["Rendering"]["scene-night-gradient-colour"].toColor(); + setLighting(&mLightingNight); + } + else if (mode == "bright") + { + backgroundColour = CSMPrefs::get()["Rendering"]["scene-bright-background-colour"].toColor(); + gradientColour = CSMPrefs::get()["Rendering"]["scene-bright-gradient-colour"].toColor(); + setLighting(&mLightingBright); + } + if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) { + if (mGradientCamera.get() != nullptr) { + // we can go ahead and update since this camera still exists + updateGradientCamera(backgroundColour, gradientColour); + + if (!mView->getCamera()->containsNode(mGradientCamera.get())) + { + // need to re-attach the gradient camera + mView->getCamera()->setClearMask(0); + mView->getCamera()->addChild(mGradientCamera.get()); + } + } + else { + // need to create the gradient camera + mGradientCamera = createGradientCamera(backgroundColour, gradientColour); + mView->getCamera()->setClearMask(0); + mView->getCamera()->addChild(mGradientCamera.get()); + } + } + else { + // Fall back to using the clear color for the camera + mView->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + mView->getCamera()->setClearColor(osg::Vec4( + backgroundColour.redF(), + backgroundColour.greenF(), + backgroundColour.blueF(), + 1.0f + )); + if (mGradientCamera.get() != nullptr && mView->getCamera()->containsNode(mGradientCamera.get())) { + // Remove the child to prevent the gradient from rendering + mView->getCamera()->removeChild(mGradientCamera.get()); + } + } } CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 6a94254b9..d7d9dba0c 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -100,10 +100,15 @@ namespace CSVRender void mouseMoveEvent (QMouseEvent *event) override; void wheelEvent (QWheelEvent *event) override; + osg::ref_ptr createGradientRectangle(QColor bgColour, QColor gradientColour); + osg::ref_ptr createGradientCamera(QColor bgColour, QColor gradientColour); + void updateGradientCamera(QColor bgColour, QColor gradientColour); + std::shared_ptr mResourceSystem; Lighting* mLighting; - + + osg::ref_ptr mGradientCamera; osg::Vec4f mDefaultAmbient; bool mHasDefaultAmbient; bool mIsExterior; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index aca050592..89aa2b9fd 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -135,7 +135,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat if (variables.count ("help")) { - std::cout << desc << std::endl; + getRawStdout() << desc << std::endl; return false; } @@ -144,7 +144,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.readConfiguration(variables, desc, true); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); - std::cout << v.describe() << std::endl; + getRawStdout() << v.describe() << std::endl; return false; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index d4f1d2f8a..619bbbdf5 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -286,6 +286,9 @@ namespace MWBase virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr + virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0; + ///< @return an updated Ptr + virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z, diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index cd0384bb0..7ddc8c550 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -1,10 +1,12 @@ #include "loadingscreen.hpp" #include +#include #include #include +#include #include #include @@ -43,6 +45,8 @@ namespace MWGui , mNestedLoadingCount(0) , mProgress(0) , mShowWallpaper(true) + , mOldCallback(nullptr) + , mHasCallback(false) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); @@ -136,24 +140,54 @@ namespace MWGui { public: CopyFramebufferToTextureCallback(osg::Texture2D* texture) - : mTexture(texture) - , oneshot(true) + : mOneshot(true) + , mTexture(texture) { } void operator () (osg::RenderInfo& renderInfo) const override { - if (!oneshot) - return; - oneshot = false; + { + std::unique_lock lock(mMutex); + mOneshot = false; + } + mSignal.notify_all(); + int w = renderInfo.getCurrentCamera()->getViewport()->width(); int h = renderInfo.getCurrentCamera()->getViewport()->height(); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); + + { + std::unique_lock lock(mMutex); + mOneshot = false; + } + mSignal.notify_all(); + } + + void wait() + { + std::unique_lock lock(mMutex); + while (mOneshot) + mSignal.wait(lock); + } + + void waitUntilInvoked() + { + std::unique_lock lock(mMutex); + while (mOneshot) + mSignal.wait(lock); + } + + void reset() + { + mOneshot = true; } private: + mutable bool mOneshot; + mutable std::mutex mMutex; + mutable std::condition_variable mSignal; osg::ref_ptr mTexture; - mutable bool oneshot; }; class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback @@ -322,9 +356,20 @@ namespace MWGui mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture)); } - // Notice that the next time this is called, the current CopyFramebufferToTextureCallback will be deleted - // so there's no memory leak as at most one object of type CopyFramebufferToTextureCallback is allocated at a time. - mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture)); + if (!mCopyFramebufferToTextureCallback) + { + mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture); + } + +#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) + mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback); +#else + // TODO: Remove once we officially end support for OSG versions pre 3.5.10 + mOldCallback = mViewer->getCamera()->getInitialDrawCallback(); + mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback); +#endif + mCopyFramebufferToTextureCallback->reset(); + mHasCallback = true; mBackgroundImage->setBackgroundImage(""); mBackgroundImage->setVisible(false); @@ -367,6 +412,21 @@ namespace MWGui mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + if (mHasCallback) + { + mCopyFramebufferToTextureCallback->waitUntilInvoked(); + + // Note that we are removing the callback before the draw thread has returned from it. + // This is OK as we are retaining the ref_ptr. +#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10) + mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback); +#else + // TODO: Remove once we officially end support for OSG versions pre 3.5.10 + mViewer->getCamera()->setInitialDrawCallback(mOldCallback); +#endif + mHasCallback = false; + } + mLastRenderTime = mTimer.time_m(); } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 2577827aa..5d86ed389 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -3,6 +3,7 @@ #include +#include #include #include @@ -28,6 +29,7 @@ namespace Resource namespace MWGui { class BackgroundImage; + class CopyFramebufferToTextureCallback; class LoadingScreen : public WindowBase, public Loading::Listener { @@ -84,6 +86,9 @@ namespace MWGui std::vector mSplashScreens; osg::ref_ptr mTexture; + osg::ref_ptr mCopyFramebufferToTextureCallback; + osg::ref_ptr mOldCallback; + bool mHasCallback; std::unique_ptr mGuiTexture; void changeWallpaper(); diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 2a3e2cd85..e02b2f45f 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -335,6 +335,15 @@ namespace MWGui { int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); + + std::string detail; + for (int i = 0; i < ESM::Attribute::Length; ++i) + { + if (auto increase = PCstats.getLevelUpAttributeIncrease(i)) + detail += (detail.empty() ? "" : "\n") + ESM::Attribute::sAttributeNames[i] + " x" + MyGUI::utility::toString(increase); + } + if (!detail.empty()) + levelWidget->setUserString("Caption_LevelDetailText", detail); levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 5d19368bf..71453cd07 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -322,6 +322,11 @@ void MWMechanics::NpcStats::updateHealth() setHealth(floor(0.5f * (strength + endurance))); } +int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const +{ + return mSkillIncreases[attribute]; +} + int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const { int num = mSkillIncreases[attribute]; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 9bd8e20ad..cab52cb28 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -87,6 +87,8 @@ namespace MWMechanics int getLevelProgress() const; + int getLevelUpAttributeIncrease(int attribute) const; + int getLevelupAttributeMultiplier(int attribute) const; int getSkillIncreasesForSpecialization(int spec) const; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 77b07cde5..10fd463fb 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -20,7 +20,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler) : mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false) - , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBoxTranslate), mHalfExtents(shape->mCollisionBoxHalfExtents) + , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) @@ -76,6 +76,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateScale(); resetPosition(); addCollisionMask(getCollisionMask()); + updateCollisionObjectPosition(); } Actor::~Actor() @@ -120,6 +121,8 @@ int Actor::getCollisionMask() const void Actor::updatePositionUnsafe() { + if (!mWorldPositionChanged && mWorldPosition != mPtr.getRefData().getPosition().asVec3()) + mWorldPositionChanged = true; mWorldPosition = mPtr.getRefData().getPosition().asVec3(); } @@ -137,7 +140,9 @@ osg::Vec3f Actor::getWorldPosition() const void Actor::setSimulationPosition(const osg::Vec3f& position) { - mSimulationPosition = position; + if (!mResetSimulation) + mSimulationPosition = position; + mResetSimulation = false; } osg::Vec3f Actor::getSimulationPosition() const @@ -153,6 +158,7 @@ void Actor::updateCollisionObjectPositionUnsafe() mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition)); mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation)); mCollisionObject->setWorldTransform(mLocalTransform); + mWorldPositionChanged = false; } void Actor::updateCollisionObjectPosition() @@ -167,18 +173,20 @@ osg::Vec3f Actor::getCollisionObjectPosition() const return Misc::Convert::toOsg(mLocalTransform.getOrigin()); } -void Actor::setPosition(const osg::Vec3f& position) +bool Actor::setPosition(const osg::Vec3f& position) { std::scoped_lock lock(mPositionMutex); - mPreviousPosition = mPosition; - mPosition = position; + bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; + mPreviousPosition = mPosition + mPositionOffset; + mPosition = position + mPositionOffset; + mPositionOffset = osg::Vec3f(); + return hasChanged; } void Actor::adjustPosition(const osg::Vec3f& offset) { std::scoped_lock lock(mPositionMutex); - mPosition += offset; - mPreviousPosition += offset; + mPositionOffset += offset; } void Actor::resetPosition() @@ -188,7 +196,9 @@ void Actor::resetPosition() mPreviousPosition = mWorldPosition; mPosition = mWorldPosition; mSimulationPosition = mWorldPosition; - updateCollisionObjectPositionUnsafe(); + mStandingOnPtr = nullptr; + mWorldPositionChanged = false; + mResetSimulation = true; } osg::Vec3f Actor::getPosition() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 07a9bebd2..b26b52d14 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -90,8 +90,9 @@ namespace MWPhysics /** * Store the current position into mPreviousPosition, then move to this position. + * Returns true if the new position is different. */ - void setPosition(const osg::Vec3f& position); + bool setPosition(const osg::Vec3f& position); void resetPosition(); void adjustPosition(const osg::Vec3f& offset); @@ -177,6 +178,9 @@ namespace MWPhysics osg::Vec3f mSimulationPosition; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; + osg::Vec3f mPositionOffset; + bool mWorldPositionChanged; + bool mResetSimulation; btTransform mLocalTransform; mutable std::mutex mPositionMutex; diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 8f7242276..3ec09d00d 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -30,12 +30,9 @@ namespace MWPhysics return btScalar(1); auto* targetHolder = static_cast(mMe->getUserPointer()); const MWWorld::Ptr target = targetHolder->getPtr(); - // do nothing if we hit the caster. Sometimes the launching origin is inside of caster collision shape - if (projectileHolder->getCaster() != target) - { + if (projectileHolder->isValidTarget(target)) projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); - return btScalar(1); - } + return btScalar(1); } btVector3 hitNormalWorld; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 422ca78bd..f9dc3bdd5 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -24,6 +24,10 @@ namespace MWPhysics { if (rayResult.m_collisionObject == mMe) return 1.f; + + if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject()) + return 1.f; + if (!mTargets.empty()) { if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index a78a30788..60a7259f8 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -100,15 +100,6 @@ namespace osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { const float interpolationFactor = timeAccum / physicsDt; - - // account for force change of actor's position in the main thread - const auto correction = actorData.mActorRaw->getWorldPosition() - actorData.mOrigin; - if (correction.length() != 0) - { - actorData.mActorRaw->adjustPosition(correction); - actorData.mPosition = actorData.mActorRaw->getPosition(); - } - return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } @@ -213,31 +204,31 @@ namespace MWPhysics thread.join(); } - const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + const std::vector& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. std::unique_lock lock(mSimulationMutex); - for (auto& data : actorsData) - data.updatePosition(); + mMovedActors.clear(); // start by finishing previous background computation if (mNumThreads != 0) { for (auto& data : mActorsFrameData) { - // Ignore actors that were deleted while the background thread was running - if (!data.mActor.lock()) - continue; + // Only return actors that are still part of the scene + if (std::any_of(actorsData.begin(), actorsData.end(), [&data](const auto& newFrameData) { return data.mActorRaw->getPtr() == newFrameData.mActorRaw->getPtr(); })) + { + updateMechanics(data); - updateMechanics(data); - if (mAdvanceSimulation) - data.mActorRaw->setStandingOnPtr(data.mStandingOn); - - if (mMovementResults.find(data.mPtr) != mMovementResults.end()) - data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]); + // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values + if (mAdvanceSimulation) + data.mActorRaw->setStandingOnPtr(data.mStandingOn); + data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt)); + mMovedActors.emplace_back(data.mActorRaw->getPtr()); + } } if (mFrameNumber == frameNumber - 1) @@ -252,6 +243,8 @@ namespace MWPhysics } // init + for (auto& data : actorsData) + data.updatePosition(); mRemainingSteps = numSteps; mTimeAccum = timeAccum; mActorsFrameData = std::move(actorsData); @@ -266,52 +259,28 @@ namespace MWPhysics if (mNumThreads == 0) { - mMovementResults.clear(); syncComputation(); - - for (auto& data : mActorsFrameData) - { - if (mAdvanceSimulation) - data.mActorRaw->setStandingOnPtr(data.mStandingOn); - if (mMovementResults.find(data.mPtr) != mMovementResults.end()) - data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]); - } - return mMovementResults; + return mMovedActors; } - // Remove actors that were deleted while the background thread was running - for (auto& data : mActorsFrameData) - { - if (!data.mActor.lock()) - mMovementResults.erase(data.mPtr); - } - std::swap(mMovementResults, mPreviousMovementResults); - - // mMovementResults is shared between all workers instance - // pre-allocate all nodes so that we don't need synchronization - mMovementResults.clear(); - for (const auto& m : mActorsFrameData) - mMovementResults[m.mPtr] = m.mPosition; - lock.unlock(); mHasJob.notify_all(); - return mPreviousMovementResults; + return mMovedActors; } - const PtrPositionList& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) + const std::vector& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { std::unique_lock lock(mSimulationMutex); - mMovementResults.clear(); - mPreviousMovementResults.clear(); + mMovedActors.clear(); mActorsFrameData.clear(); - for (const auto& [_, actor] : actors) { actor->resetPosition(); - actor->setStandingOnPtr(nullptr); - mMovementResults[actor->getPtr()] = actor->getWorldPosition(); + actor->setSimulationPosition(actor->getWorldPosition()); // resetPosition skip next simulation, now we need to "consume" it + actor->updateCollisionObjectPosition(); + mMovedActors.emplace_back(actor->getPtr()); } - return mMovementResults; + return mMovedActors; } void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const @@ -379,18 +348,18 @@ namespace MWPhysics mCollisionWorld->removeCollisionObject(collisionObject); } - void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr) + void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr, bool immediate) { - if (mDeferAabbUpdate) - { - std::unique_lock lock(mUpdateAabbMutex); - mUpdateAabb.insert(std::move(ptr)); - } - else + if (!mDeferAabbUpdate || immediate) { std::unique_lock lock(mCollisionWorldMutex); updatePtrAabb(ptr); } + else + { + std::unique_lock lock(mUpdateAabbMutex); + mUpdateAabb.insert(std::move(ptr)); + } } bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2) @@ -493,7 +462,6 @@ namespace MWPhysics { auto& actorData = mActorsFrameData[job]; handleFall(actorData, mAdvanceSimulation); - mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt); } } @@ -511,9 +479,7 @@ namespace MWPhysics { if(const auto actor = actorData.mActor.lock()) { - bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition(); - actorData.mActorRaw->setPosition(actorData.mPosition); - if (positionChanged) + if (actor->setPosition(actorData.mPosition)) { actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); @@ -550,8 +516,11 @@ namespace MWPhysics for (auto& actorData : mActorsFrameData) { handleFall(actorData, mAdvanceSimulation); - mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt); + actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt)); updateMechanics(actorData); + mMovedActors.emplace_back(actorData.mActorRaw->getPtr()); + if (mAdvanceSimulation) + actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); } } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 3a761829b..90e1007b8 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -32,9 +32,9 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + const std::vector& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); - const PtrPositionList& resetSimulation(const ActorMap& actors); + const std::vector& resetSimulation(const ActorMap& actors); // Thread safe wrappers void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; @@ -46,7 +46,7 @@ namespace MWPhysics void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask); void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask); void removeCollisionObject(btCollisionObject* collisionObject); - void updateSingleAabb(std::weak_ptr ptr); + void updateSingleAabb(std::weak_ptr ptr, bool immediate=false); bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2); private: @@ -60,8 +60,7 @@ namespace MWPhysics std::unique_ptr mWorldFrameData; std::vector mActorsFrameData; - PtrPositionList mMovementResults; - PtrPositionList mPreviousMovementResults; + std::vector mMovedActors; const float mPhysicsDt; float mTimeAccum; std::shared_ptr mCollisionWorld; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 324b1db2d..8b9cb0a79 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -437,7 +437,7 @@ namespace MWPhysics ActorMap::iterator found = mActors.find(ptr); if (found == mActors.end()) return ptr.getRefData().getPosition().asVec3(); - found->second->resetPosition(); + resetPosition(ptr); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); } @@ -537,6 +537,13 @@ namespace MWPhysics if (actor->getStandingOnPtr() == old) actor->setStandingOnPtr(updated); } + + for (auto& [_, projectile] : mProjectiles) + { + if (projectile->getCaster() == old) + projectile->setCaster(updated); + } + } Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr) @@ -640,12 +647,23 @@ namespace MWPhysics } } + void PhysicsSystem::resetPosition(const MWWorld::ConstPtr &ptr) + { + ActorMap::iterator foundActor = mActors.find(ptr); + if (foundActor != mActors.end()) + { + foundActor->second->resetPosition(); + mTaskScheduler->updateSingleAabb(foundActor->second, true); + return; + } + } + void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { osg::ref_ptr shape = mShapeManager->getShape(mesh); // Try to get shape from basic model as fallback for creatures - if (!ptr.getClass().isNpc() && shape && shape->mCollisionBoxHalfExtents.length2() == 0) + if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.extents.length2() == 0) { const std::string fallbackModel = ptr.getClass().getModel(ptr); if (fallbackModel != mesh) @@ -676,6 +694,8 @@ namespace MWPhysics if (found != mActors.end()) { bool cmode = found->second->getCollisionMode(); + if (cmode) + resetPosition(found->first); cmode = !cmode; found->second->enableCollisionMode(cmode); // NB: Collision body isn't disabled for vanilla TCL compatibility @@ -704,7 +724,7 @@ namespace MWPhysics mMovementQueue.clear(); } - const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + const std::vector& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { mTimeAccum += dt; @@ -923,7 +943,6 @@ namespace MWPhysics void ActorFrameData::updatePosition() { mActorRaw->updatePosition(); - mOrigin = mActorRaw->getSimulationPosition(); mPosition = mActorRaw->getPosition(); if (mMoveToWaterSurface) { diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b33d829a4..8c7674991 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -50,8 +50,6 @@ class btVector3; namespace MWPhysics { - using PtrPositionList = std::map; - class HeightField; class Object; class Actor; @@ -99,7 +97,6 @@ namespace MWPhysics float mOldHeight; float mFallHeight; osg::Vec3f mMovement; - osg::Vec3f mOrigin; osg::Vec3f mPosition; ESM::Position mRefpos; }; @@ -147,6 +144,7 @@ namespace MWPhysics void updateScale (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr); void updatePosition (const MWWorld::Ptr& ptr); + void resetPosition(const MWWorld::ConstPtr &ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); @@ -210,7 +208,7 @@ namespace MWPhysics void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); /// Apply all queued movements, then clear the list. - const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + const std::vector& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 5f5bc778b..a8aaeb72a 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -55,7 +55,7 @@ Projectile::~Projectile() void Projectile::commitPositionChange() { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mMutex); if (mTransformUpdatePending) { mCollisionObject->setWorldTransform(mLocalTransform); @@ -65,7 +65,7 @@ void Projectile::commitPositionChange() void Projectile::setPosition(const osg::Vec3f &position) { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mMutex); mLocalTransform.setOrigin(Misc::Convert::toBullet(position)); mTransformUpdatePending = true; } @@ -74,7 +74,7 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) { if (!mActive.load(std::memory_order_acquire)) return; - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mMutex); mHitTarget = target; mHitPosition = pos; mHitNormal = normal; @@ -86,4 +86,46 @@ void Projectile::activate() assert(!mActive); mActive.store(true, std::memory_order_release); } + +MWWorld::Ptr Projectile::getCaster() const +{ + std::scoped_lock lock(mMutex); + return mCaster; +} + +void Projectile::setCaster(MWWorld::Ptr caster) +{ + std::scoped_lock lock(mMutex); + mCaster = caster; +} + +void Projectile::setValidTargets(const std::vector& targets) +{ + std::scoped_lock lock(mMutex); + mValidTargets = targets; +} + +bool Projectile::isValidTarget(const MWWorld::Ptr& target) const +{ + std::scoped_lock lock(mMutex); + if (mCaster == target) + return false; + + if (!mValidTargets.empty()) + { + bool validTarget = false; + for (const auto& targetActor : mValidTargets) + { + if (targetActor == target) + { + validTarget = true; + break; + } + } + + return validTarget; + } + return true; +} + } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index ed0fdce15..5ae963b9a 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -62,7 +62,8 @@ namespace MWPhysics return mHitTarget; } - MWWorld::Ptr getCaster() const { return mCaster; } + MWWorld::Ptr getCaster() const; + void setCaster(MWWorld::Ptr caster); osg::Vec3f getHitPos() const { @@ -73,6 +74,9 @@ namespace MWPhysics void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); void activate(); + void setValidTargets(const std::vector& targets); + bool isValidTarget(const MWWorld::Ptr& target) const; + private: std::unique_ptr mShape; @@ -87,7 +91,9 @@ namespace MWPhysics btVector3 mHitPosition; btVector3 mHitNormal; - mutable std::mutex mPositionMutex; + std::vector mValidTargets; + + mutable std::mutex mMutex; osg::Vec3f mPosition; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 8c9f5f493..f8ff3780d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -782,8 +782,6 @@ namespace MWRender NodeMap::const_iterator found = nodeMap.find("bip01"); if (found == nodeMap.end()) found = nodeMap.find("root bone"); - if (found == nodeMap.end()) - found = nodeMap.find("root"); if (found != nodeMap.end()) mAccumRoot = found->second; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index a908940ed..103c6629d 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -32,11 +32,7 @@ namespace MWScript std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) - { - osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); - actorPos += diff; - MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z()); - } + MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff); } template @@ -727,14 +723,12 @@ namespace MWScript return; osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; - osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3()); - worldPos += diff; // We should move actors, standing on moving object, too. // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z())); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); } }; @@ -755,15 +749,14 @@ namespace MWScript Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); - const float *objPos = ptr.getRefData().getPosition().pos; osg::Vec3f diff; if (axis == "x") - diff.x() += movement; + diff.x() = movement; else if (axis == "y") - diff.y() += movement; + diff.y() = movement; else if (axis == "z") - diff.z() += movement; + diff.z() = movement; else return; @@ -771,7 +764,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+diff.x(), objPos[1]+diff.y(), objPos[2]+diff.z())); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff)); } }; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index dba4289a2..08c9e4662 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -387,7 +387,7 @@ namespace MWWorld if (magicBoltState.mToDelete) continue; - const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); + auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); if (!projectile->isActive()) continue; // If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. @@ -423,6 +423,7 @@ namespace MWWorld std::vector targetActors; if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); + projectile->setValidTargets(targetActors); // Check for impact // TODO: use a proper btRigidBody / btGhostObject? @@ -469,7 +470,7 @@ namespace MWWorld if (projectileState.mToDelete) continue; - const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); + auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); if (!projectile->isActive()) continue; // gravity constant - must be way lower than the gravity affecting actors, since we're not @@ -499,6 +500,7 @@ namespace MWWorld std::vector targetActors; if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); + projectile->setValidTargets(targetActors); // Check for impact // TODO: use a proper btRigidBody / btGhostObject? @@ -547,7 +549,7 @@ namespace MWWorld const auto pos = projectile->getHitPos(); MWWorld::Ptr caster = projectileState.getCaster(); assert(target != caster); - if (!isValidTarget(caster, target)) + if (!projectile->isValidTarget(target)) { projectile->activate(); continue; @@ -583,7 +585,7 @@ namespace MWWorld const auto pos = projectile->getHitPos(); MWWorld::Ptr caster = magicBoltState.getCaster(); assert(target != caster); - if (!isValidTarget(caster, target)) + if (!projectile->isValidTarget(target)) { projectile->activate(); continue; @@ -607,32 +609,6 @@ namespace MWWorld mMagicBolts.end()); } - bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) - { - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. - std::vector targetActors; - if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) - { - caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); - if (!targetActors.empty()) - { - bool validTarget = false; - for (MWWorld::Ptr& targetActor : targetActors) - { - if (targetActor == target) - { - validTarget = true; - break; - } - } - - return validTarget; - } - } - - return true; - } - void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state) { mParent->removeChild(state.mNode); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index d589e985e..2cd570847 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -132,8 +132,6 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - bool isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); - void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); void update (State& state, float duration); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d00fabb66..c2ad6e22f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1251,6 +1251,18 @@ namespace MWWorld return moveObjectImp(ptr, x, y, z, true, moveToActive); } + MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec) + { + auto* actor = mPhysics->getActor(ptr); + if (actor) + { + actor->adjustPosition(vec); + return ptr; + } + osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; + return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); + } + void World::scaleObject (const Ptr& ptr, float scale) { if (mPhysics->getActor(ptr)) @@ -1344,12 +1356,7 @@ namespace MWWorld } moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); - if (force) // force physics to use the new position - { - auto actor = mPhysics->getActor(ptr); - if(actor) - actor->resetPosition(); - } + mPhysics->resetPosition(ptr); } void World::fixPosition() @@ -1496,20 +1503,30 @@ namespace MWWorld mProjectileManager->update(duration); - const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); + const auto& results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); mProjectileManager->processHits(); mDiscardMovements = false; - for(const auto& [actor, position]: results) + for(const auto& actor : results) { // Handle player last, in case a cell transition occurs if(actor != getPlayerPtr()) + { + auto* physactor = mPhysics->getActor(actor); + assert(physactor); + const auto position = physactor->getSimulationPosition(); moveObjectImp(actor, position.x(), position.y(), position.z(), false); + } } - const auto player = results.find(getPlayerPtr()); + const auto player = std::find(results.begin(), results.end(), getPlayerPtr()); if (player != results.end()) - moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); + { + auto* physactor = mPhysics->getActor(*player); + assert(physactor); + const auto position = physactor->getSimulationPosition(); + moveObjectImp(*player, position.x(), position.y(), position.z(), false); + } } void World::updateNavigator() diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1fc5ebc20..75717b16b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -380,6 +380,9 @@ namespace MWWorld MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; ///< @return an updated Ptr + MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec) override; + ///< @return an updated Ptr + void scaleObject (const Ptr& ptr, float scale) override; /// World rotates object, uses radians diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index cd2d2e80a..5658a5eef 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -1,5 +1,5 @@ -find_package(GTest REQUIRED) -find_package(GMock REQUIRED) +find_package(GTest 1.10 REQUIRED) +find_package(GMock 1.10 REQUIRED) if (GTEST_FOUND AND GMOCK_FOUND) include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp index a3606f827..621db51a8 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -15,7 +15,7 @@ namespace struct DetourNavigatorRecastMeshObjectTest : Test { btBoxShape mBoxShape {btVector3(1, 2, 3)}; - btCompoundShape mCompoundShape {btVector3(1, 2, 3)}; + btCompoundShape mCompoundShape {true}; btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)}; DetourNavigatorRecastMeshObjectTest() diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 77aaccfdd..f3b2bb3dc 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -58,10 +59,10 @@ struct ContentFileTest : public ::testing::Test boost::program_options::options_description desc("Allowed options"); desc.add_options() - ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) - ("content", boost::program_options::value >()->default_value(std::vector(), "") - ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") - ("data-local", boost::program_options::value()->default_value("")); + ("data", boost::program_options::value()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing()) + ("content", boost::program_options::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") + ("data-local", boost::program_options::value()->default_value(Files::EscapePath(), "")); boost::program_options::notify(variables); @@ -69,12 +70,12 @@ struct ContentFileTest : public ::testing::Test Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { - dataDirs = Files::PathContainer(variables["data"].as()); + dataDirs = Files::EscapePath::toPathContainer(variables["data"].as()); } - std::string local = variables["data-local"].as(); + Files::PathContainer::value_type local(variables["data-local"].as().mPath); if (!local.empty()) { - dataLocal.push_back(Files::PathContainer::value_type(local)); + dataLocal.push_back(local); } mConfigurationManager.processPaths (dataDirs); @@ -85,7 +86,7 @@ struct ContentFileTest : public ::testing::Test Files::Collections collections (dataDirs, true); - std::vector contentFiles = variables["content"].as >(); + std::vector contentFiles = variables["content"].as().toStdStringVector(); for (auto & contentFile : contentFiles) mContentFiles.push_back(collections.getPath(contentFile)); } diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 7da8f0fd5..a1f5d6091 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -162,8 +162,8 @@ namespace Resource { return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape) && compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape) - && lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents - && lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate + && lhs.mCollisionBox.extents == rhs.mCollisionBox.extents + && lhs.mCollisionBox.center == rhs.mCollisionBox.center && lhs.mAnimatedShapes == rhs.mAnimatedShapes; } @@ -172,7 +172,8 @@ namespace Resource return stream << "Resource::BulletShape {" << value.mCollisionShape << ", " << value.mAvoidCollisionShape << ", " - << "osg::Vec3f {" << value.mCollisionBoxHalfExtents << "}" << ", " + << "osg::Vec3f {" << value.mCollisionBox.extents << "}" << ", " + << "osg::Vec3f {" << value.mCollisionBox.center << "}" << ", " << value.mAnimatedShapes << "}"; } @@ -258,14 +259,19 @@ namespace value.isBone = false; } - void init(Nif::NiTriShape& value) + void init(Nif::NiGeometry& value) { init(static_cast(value)); - value.recType = Nif::RC_NiTriShape; - value.data = Nif::NiTriShapeDataPtr(nullptr); + value.data = Nif::NiGeometryDataPtr(nullptr); value.skin = Nif::NiSkinInstancePtr(nullptr); } + void init(Nif::NiTriShape& value) + { + init(static_cast(value)); + value.recType = Nif::RC_NiTriShape; + } + void init(Nif::NiSkinInstance& value) { value.data = Nif::NiSkinDataPtr(nullptr); @@ -293,22 +299,22 @@ namespace struct NifFileMock : Nif::File { - MOCK_CONST_METHOD1(getRecord, Nif::Record* (std::size_t)); - MOCK_CONST_METHOD0(numRecords, std::size_t ()); - MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t)); - MOCK_CONST_METHOD0(numRoots, std::size_t ()); - MOCK_CONST_METHOD1(getString, std::string (uint32_t)); - MOCK_METHOD1(setUseSkinning, void (bool)); - MOCK_CONST_METHOD0(getUseSkinning, bool ()); - MOCK_CONST_METHOD0(getFilename, std::string ()); - MOCK_CONST_METHOD0(getVersion, unsigned int ()); - MOCK_CONST_METHOD0(getUserVersion, unsigned int ()); - MOCK_CONST_METHOD0(getBethVersion, unsigned int ()); + MOCK_METHOD(Nif::Record*, getRecord, (std::size_t), (const, override)); + MOCK_METHOD(std::size_t, numRecords, (), (const, override)); + MOCK_METHOD(Nif::Record*, getRoot, (std::size_t), (const, override)); + MOCK_METHOD(std::size_t, numRoots, (), (const, override)); + MOCK_METHOD(std::string, getString, (uint32_t), (const, override)); + MOCK_METHOD(void, setUseSkinning, (bool), (override)); + MOCK_METHOD(bool, getUseSkinning, (), (const, override)); + MOCK_METHOD(std::string, getFilename, (), (const, override)); + MOCK_METHOD(unsigned int, getVersion, (), (const, override)); + MOCK_METHOD(unsigned int, getUserVersion, (), (const, override)); + MOCK_METHOD(unsigned int, getBethVersion, (), (const, override)); }; struct RecordMock : Nif::Record { - MOCK_METHOD1(read, void (Nif::NIFStream *nif)); + MOCK_METHOD(void, read, (Nif::NIFStream *nif), (override)); }; struct TestBulletNifLoader : Test @@ -360,13 +366,15 @@ namespace init(mNiStringExtraData2); init(mController); + mNiTriShapeData.recType = Nif::RC_NiTriShapeData; mNiTriShapeData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0)}; mNiTriShapeData.triangles = {0, 1, 2}; - mNiTriShape.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData); + mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + mNiTriShapeData2.recType = Nif::RC_NiTriShapeData; mNiTriShapeData2.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)}; mNiTriShapeData2.triangles = {0, 1, 2}; - mNiTriShape2.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData2); + mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2); } }; @@ -433,8 +441,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -458,8 +466,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -488,8 +496,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -523,8 +531,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); @@ -558,8 +566,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(4, 5, 6); - expected.mCollisionBoxTranslate = osg::Vec3f(-4, -5, -6); + expected.mCollisionBox.extents = osg::Vec3f(4, 5, 6); + expected.mCollisionBox.center = osg::Vec3f(-4, -5, -6); std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); std::unique_ptr shape(new btCompoundShape); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); @@ -581,8 +589,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } @@ -615,8 +623,8 @@ namespace const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; - expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3); + expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } @@ -872,7 +880,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) { - mNiTriShape.data = Nif::NiTriShapeDataPtr(nullptr); + mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -887,7 +895,8 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape) { - mNiTriShape.data->triangles.clear(); + auto data = static_cast(mNiTriShape.data.getPtr()); + data->triangles.clear(); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 160c6f721..d92f5b029 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -157,6 +157,8 @@ void Wizard::MainWizard::setupGameSettings() mGameSettings.readUserFile(stream); } + file.close(); + // Now the rest QStringList paths; paths.append(userPath + QLatin1String("openmw.cfg")); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1833210e4..246f51719 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -152,7 +152,13 @@ add_component_dir (fallback fallback validate ) -if(NOT WIN32 AND NOT ANDROID) +if(WIN32) + add_component_dir (crashcatcher + windows_crashcatcher + windows_crashmonitor + windows_crashshm + ) +elseif(NOT ANDROID) add_component_dir (crashcatcher crashcatcher ) diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp new file mode 100644 index 000000000..4c3dfa8f6 --- /dev/null +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include + +#include "windows_crashcatcher.hpp" +#include "windows_crashmonitor.hpp" +#include "windows_crashshm.hpp" +#include + +namespace Crash +{ + + HANDLE duplicateHandle(HANDLE handle) + { + HANDLE duplicate; + if (!DuplicateHandle(GetCurrentProcess(), handle, + GetCurrentProcess(), &duplicate, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + throw std::runtime_error("Crash monitor could not duplicate handle"); + } + return duplicate; + } + + CrashCatcher* CrashCatcher::sInstance = nullptr; + + CrashCatcher::CrashCatcher(int argc, char **argv, const std::string& crashLogPath) + { + assert(sInstance == nullptr); // don't allow two instances + + sInstance = this; + + HANDLE shmHandle = nullptr; + for (int i=0; i= argc - 1) + throw std::runtime_error("Crash monitor is missing the SHM handle argument"); + + sscanf(argv[i + 1], "%p", &shmHandle); + break; + } + + if (!shmHandle) + { + setupIpc(); + startMonitorProcess(crashLogPath); + installHandler(); + } + else + { + CrashMonitor(shmHandle).run(); + exit(0); + } + } + + CrashCatcher::~CrashCatcher() + { + sInstance = nullptr; + + if (mShm && mSignalMonitorEvent) + { + shmLock(); + mShm->mEvent = CrashSHM::Event::Shutdown; + shmUnlock(); + + SetEvent(mSignalMonitorEvent); + } + + if (mShmHandle) + CloseHandle(mShmHandle); + } + + void CrashCatcher::setupIpc() + { + SECURITY_ATTRIBUTES attributes; + ZeroMemory(&attributes, sizeof(attributes)); + attributes.bInheritHandle = TRUE; + + mSignalAppEvent = CreateEventW(&attributes, FALSE, FALSE, NULL); + mSignalMonitorEvent = CreateEventW(&attributes, FALSE, FALSE, NULL); + + mShmHandle = CreateFileMappingW(INVALID_HANDLE_VALUE, &attributes, PAGE_READWRITE, HIWORD(sizeof(CrashSHM)), LOWORD(sizeof(CrashSHM)), NULL); + if (mShmHandle == nullptr) + throw std::runtime_error("Failed to allocate crash catcher shared memory"); + + mShm = reinterpret_cast(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM))); + if (mShm == nullptr) + throw std::runtime_error("Failed to map crash catcher shared memory"); + + mShmMutex = CreateMutexW(&attributes, FALSE, NULL); + if (mShmMutex == nullptr) + throw std::runtime_error("Failed to create crash catcher shared memory mutex"); + } + + void CrashCatcher::shmLock() + { + if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0) + throw std::runtime_error("SHM lock timed out"); + } + + void CrashCatcher::shmUnlock() + { + ReleaseMutex(mShmMutex); + } + + void CrashCatcher::waitMonitor() + { + if (WaitForSingleObject(mSignalAppEvent, CrashCatcherTimeout) != WAIT_OBJECT_0) + throw std::runtime_error("Waiting for monitor failed"); + } + + void CrashCatcher::signalMonitor() + { + SetEvent(mSignalMonitorEvent); + } + + void CrashCatcher::installHandler() + { + SetUnhandledExceptionFilter(vectoredExceptionHandler); + } + + void CrashCatcher::startMonitorProcess(const std::string& crashLogPath) + { + std::wstring executablePath; + DWORD copied = 0; + do { + executablePath.resize(executablePath.size() + MAX_PATH); + copied = GetModuleFileNameW(nullptr, executablePath.data(), executablePath.size()); + } while (copied >= executablePath.size()); + executablePath.resize(copied); + + memset(mShm->mStartup.mLogFilePath, 0, sizeof(mShm->mStartup.mLogFilePath)); + int length = crashLogPath.length(); + if (length > MAX_LONG_PATH) length = MAX_LONG_PATH; + strncpy(mShm->mStartup.mLogFilePath, crashLogPath.c_str(), length); + mShm->mStartup.mLogFilePath[length] = '\0'; + + // note that we don't need to lock the SHM here, the other process has not started yet + mShm->mEvent = CrashSHM::Event::Startup; + mShm->mStartup.mShmMutex = duplicateHandle(mShmMutex); + mShm->mStartup.mAppProcessHandle = duplicateHandle(GetCurrentProcess()); + mShm->mStartup.mSignalApp = duplicateHandle(mSignalAppEvent); + mShm->mStartup.mSignalMonitor = duplicateHandle(mSignalMonitorEvent); + + std::wstringstream ss; + ss << "--crash-monitor " << std::hex << duplicateHandle(mShmHandle); + std::wstring arguments(ss.str()); + + STARTUPINFOW si; + ZeroMemory(&si, sizeof(si)); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + if (!CreateProcessW(executablePath.data(), arguments.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + throw std::runtime_error("Could not start crash monitor process"); + + waitMonitor(); + } + + LONG CrashCatcher::vectoredExceptionHandler(PEXCEPTION_POINTERS info) + { + switch (info->ExceptionRecord->ExceptionCode) + { + case EXCEPTION_SINGLE_STEP: + case EXCEPTION_BREAKPOINT: + case DBG_PRINTEXCEPTION_C: + return EXCEPTION_EXECUTE_HANDLER; + } + if (!sInstance) + return EXCEPTION_EXECUTE_HANDLER; + + sInstance->handleVectoredException(info); + + _Exit(1); + + return EXCEPTION_CONTINUE_SEARCH; + } + + void CrashCatcher::handleVectoredException(PEXCEPTION_POINTERS info) + { + shmLock(); + + mShm->mEvent = CrashSHM::Event::Crashed; + mShm->mCrashed.mThreadId = GetCurrentThreadId(); + mShm->mCrashed.mContext = *info->ContextRecord; + mShm->mCrashed.mExceptionRecord = *info->ExceptionRecord; + + shmUnlock(); + + signalMonitor(); + + // must remain until monitor has finished + waitMonitor(); + + std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !"; + SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); + } + +} // namespace Crash diff --git a/components/crashcatcher/windows_crashcatcher.hpp b/components/crashcatcher/windows_crashcatcher.hpp new file mode 100644 index 000000000..e1857271e --- /dev/null +++ b/components/crashcatcher/windows_crashcatcher.hpp @@ -0,0 +1,79 @@ +#ifndef WINDOWS_CRASHCATCHER_HPP +#define WINDOWS_CRASHCATCHER_HPP + +#include + +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include + +#include + +namespace Crash +{ + + // The implementation spawns the current executable as a monitor process which waits + // for a global synchronization event which is sent when the parent process crashes. + // The monitor process then extracts crash information from the parent process while + // the parent process waits for the monitor process to finish. The crashed process + // quits and the monitor writes the crash information to a file. + // + // To detect unexpected shutdowns of the application which are not handled by the + // crash handler, the monitor periodically checks the exit code of the parent + // process and exits if it does not return STILL_ACTIVE. You can test this by closing + // the main openmw process in task manager. + + static constexpr const int CrashCatcherTimeout = 2500; + + struct CrashSHM; + + class CrashCatcher final + { + public: + + CrashCatcher(int argc, char **argv, const std::string& crashLogPath); + ~CrashCatcher(); + + private: + + static CrashCatcher* sInstance; + + // mapped SHM area + CrashSHM* mShm = nullptr; + // the handle is allocated by the catcher and passed to the monitor + // process via the command line which maps the SHM and sends / receives + // events through it + HANDLE mShmHandle = nullptr; + // mutex which guards SHM area + HANDLE mShmMutex = nullptr; + + // triggered when the monitor signals the application + HANDLE mSignalAppEvent = INVALID_HANDLE_VALUE; + + // triggered when the application wants to wake the monitor process + HANDLE mSignalMonitorEvent = INVALID_HANDLE_VALUE; + + void setupIpc(); + + void shmLock(); + + void shmUnlock(); + + void startMonitorProcess(const std::string& crashLogPath); + + void waitMonitor(); + + void signalMonitor(); + + void installHandler(); + + void handleVectoredException(PEXCEPTION_POINTERS info); + + public: + + static LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS info); + }; + +} // namespace Crash + +#endif // WINDOWS_CRASHCATCHER_HPP diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp new file mode 100644 index 000000000..8976deb2e --- /dev/null +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -0,0 +1,188 @@ +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include + +#include +#include +#include + +#include "windows_crashcatcher.hpp" +#include "windows_crashmonitor.hpp" +#include "windows_crashshm.hpp" +#include + +namespace Crash +{ + + CrashMonitor::CrashMonitor(HANDLE shmHandle) + : mShmHandle(shmHandle) + { + mShm = reinterpret_cast(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM))); + if (mShm == nullptr) + throw std::runtime_error("Failed to map crash monitor shared memory"); + + // accessing SHM without lock is OK here, the parent waits for a signal before continuing + + mShmMutex = mShm->mStartup.mShmMutex; + mAppProcessHandle = mShm->mStartup.mAppProcessHandle; + mSignalAppEvent = mShm->mStartup.mSignalApp; + mSignalMonitorEvent = mShm->mStartup.mSignalMonitor; + } + + CrashMonitor::~CrashMonitor() + { + if (mShm) + UnmapViewOfFile(mShm); + + // the handles received from the app are duplicates, we must close them + + if (mShmHandle) + CloseHandle(mShmHandle); + + if (mShmMutex) + CloseHandle(mShmMutex); + + if (mSignalAppEvent) + CloseHandle(mSignalAppEvent); + + if (mSignalMonitorEvent) + CloseHandle(mSignalMonitorEvent); + } + + void CrashMonitor::shmLock() + { + if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0) + throw std::runtime_error("SHM monitor lock timed out"); + } + + void CrashMonitor::shmUnlock() + { + ReleaseMutex(mShmMutex); + } + + void CrashMonitor::signalApp() const + { + SetEvent(mSignalAppEvent); + } + + bool CrashMonitor::waitApp() const + { + return WaitForSingleObject(mSignalMonitorEvent, CrashCatcherTimeout) == WAIT_OBJECT_0; + } + + bool CrashMonitor::isAppAlive() const + { + DWORD code = 0; + GetExitCodeProcess(mAppProcessHandle, &code); + return code == STILL_ACTIVE; + } + + void CrashMonitor::run() + { + try + { + // app waits for monitor start up, let it continue + signalApp(); + + bool running = true; + while (isAppAlive() && running) + { + if (waitApp()) + { + shmLock(); + + switch (mShm->mEvent) + { + case CrashSHM::Event::None: + break; + case CrashSHM::Event::Crashed: + handleCrash(); + running = false; + break; + case CrashSHM::Event::Shutdown: + running = false; + break; + case CrashSHM::Event::Startup: + break; + } + + shmUnlock(); + } + } + + } + catch (...) + { + Log(Debug::Error) << "Exception in crash monitor, exiting"; + } + signalApp(); + } + + std::wstring utf8ToUtf16(const std::string& utf8) + { + const int nLenWide = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), nullptr, 0); + + std::wstring utf16; + utf16.resize(nLenWide); + if (MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), utf16.data(), nLenWide) != nLenWide) + return {}; + + return utf16; + } + + void CrashMonitor::handleCrash() + { + DWORD processId = GetProcessId(mAppProcessHandle); + + try + { + HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); + if (dbghelp == NULL) + return; + + using MiniDumpWirteDumpFn = BOOL (WINAPI*)( + HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + + MiniDumpWirteDumpFn miniDumpWriteDump = (MiniDumpWirteDumpFn)GetProcAddress(dbghelp, "MiniDumpWriteDump"); + if (miniDumpWriteDump == NULL) + return; + + std::wstring utf16Path = utf8ToUtf16(mShm->mStartup.mLogFilePath); + if (utf16Path.empty()) + return; + + if (utf16Path.length() > MAX_PATH) + utf16Path = LR"(\\?\)" + utf16Path; + + HANDLE hCrashLog = CreateFileW(utf16Path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hCrashLog == NULL || hCrashLog == INVALID_HANDLE_VALUE) + return; + if (auto err = GetLastError(); err != ERROR_ALREADY_EXISTS && err != 0) + return; + + EXCEPTION_POINTERS exp; + exp.ContextRecord = &mShm->mCrashed.mContext; + exp.ExceptionRecord = &mShm->mCrashed.mExceptionRecord; + MINIDUMP_EXCEPTION_INFORMATION infos = {}; + infos.ThreadId = mShm->mCrashed.mThreadId; + infos.ExceptionPointers = &exp; + infos.ClientPointers = FALSE; + MINIDUMP_TYPE type = (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithHandleData); + miniDumpWriteDump(mAppProcessHandle, processId, hCrashLog, type, &infos, 0, 0); + } + catch (const std::exception&e) + { + Log(Debug::Error) << "CrashMonitor: " << e.what(); + } + catch (...) + { + Log(Debug::Error) << "CrashMonitor: unknown exception"; + } + } + +} // namespace Crash diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp new file mode 100644 index 000000000..678d38435 --- /dev/null +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -0,0 +1,49 @@ +#ifndef WINDOWS_CRASHMONITOR_HPP +#define WINDOWS_CRASHMONITOR_HPP + +#include + +namespace Crash +{ + +struct CrashSHM; + +class CrashMonitor final +{ +public: + + CrashMonitor(HANDLE shmHandle); + + ~CrashMonitor(); + + void run(); + +private: + + HANDLE mAppProcessHandle = nullptr; + + // triggered when the monitor process wants to wake the parent process (received via SHM) + HANDLE mSignalAppEvent = nullptr; + // triggered when the application wants to wake the monitor process (received via SHM) + HANDLE mSignalMonitorEvent = nullptr; + + CrashSHM* mShm = nullptr; + HANDLE mShmHandle = nullptr; + HANDLE mShmMutex = nullptr; + + void signalApp() const; + + bool waitApp() const; + + bool isAppAlive() const; + + void shmLock(); + + void shmUnlock(); + + void handleCrash(); +}; + +} // namespace Crash + +#endif // WINDOWS_CRASHMONITOR_HPP diff --git a/components/crashcatcher/windows_crashshm.hpp b/components/crashcatcher/windows_crashshm.hpp new file mode 100644 index 000000000..47929a45f --- /dev/null +++ b/components/crashcatcher/windows_crashshm.hpp @@ -0,0 +1,45 @@ +#ifndef WINDOWS_CRASHSHM_HPP +#define WINDOWS_CRASHSHM_HPP + +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include + +namespace Crash +{ + + // Used to communicate between the app and the monitor, fields are is overwritten with each event. + static constexpr const int MAX_LONG_PATH = 0x7fff; + + struct CrashSHM + { + enum class Event + { + None, + Startup, + Crashed, + Shutdown + }; + + Event mEvent; + + struct Startup + { + HANDLE mAppProcessHandle; + HANDLE mSignalApp; + HANDLE mSignalMonitor; + HANDLE mShmMutex; + char mLogFilePath[MAX_LONG_PATH]; + } mStartup; + + struct Crashed + { + DWORD mThreadId; + CONTEXT mContext; + EXCEPTION_RECORD mExceptionRecord; + } mCrashed; + }; + +} // namespace Crash + +#endif // WINDOWS_CRASHSHM_HPP diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 88219dcbe..e9bcf8ad7 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -1,10 +1,13 @@ #include "debugging.hpp" #include +#include +#include #include #ifdef _WIN32 +# include # undef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # include @@ -133,11 +136,19 @@ namespace Debug } } +static std::unique_ptr rawStdout = nullptr; + +std::ostream& getRawStdout() +{ + return rawStdout ? *rawStdout : std::cout; +} + int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName) { #if defined _WIN32 (void)Debug::attachParentConsole(); #endif + rawStdout = std::make_unique(std::cout.rdbuf()); // Some objects used to redirect cout and cerr // Scope must be here, so this still works inside the catch block for logging exceptions @@ -154,7 +165,6 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c #endif const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; - const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log"; boost::filesystem::ofstream logfile; int ret = 0; @@ -178,13 +188,18 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c std::cerr.rdbuf (&cerrsb); #endif +#if defined(_WIN32) + const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp"; + Crash::CrashCatcher crashy(argc, argv, (cfgMgr.getLogPath() / crashLogName).make_preferred().string()); +#else + const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log"; // install the crash handler as soon as possible. note that the log path // does not depend on config being read. crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string()); - +#endif ret = innerApplication(argc, argv); } - catch (std::exception& e) + catch (const std::exception& e) { #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) if (!isatty(fileno(stdin))) diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp index 39390446f..d8849cd89 100644 --- a/components/debug/debugging.hpp +++ b/components/debug/debugging.hpp @@ -135,6 +135,9 @@ namespace Debug #endif } +// Can be used to print messages without timestamps +std::ostream& getRawStdout(); + int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName); #endif diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 711272abb..022e5224a 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -11,9 +11,8 @@ namespace Nif { // An extra data record. All the extra data connected to an object form a linked list. -class Extra : public Record +struct Extra : public Record { -public: std::string name; ExtraPtr next; // Next extra data record in the list @@ -31,9 +30,8 @@ public: void post(NIFFile *nif) override { next.post(nif); } }; -class Controller : public Record +struct Controller : public Record { -public: ControllerPtr next; int flags; float frequency, phase; @@ -45,9 +43,8 @@ public: }; /// Has name, extra-data and controller -class Named : public Record +struct Named : public Record { -public: std::string name; ExtraPtr extra; ExtraList extralist; diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index ab2b8dc17..c8116ce74 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -47,6 +47,11 @@ namespace Nif data.post(nif); } + void BSShaderTextureSet::read(NIFStream *nif) + { + nif->getSizedStrings(textures, nif->getUInt()); + } + void NiParticleModifier::read(NIFStream *nif) { next.read(nif); diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index 57d538f83..0d93381ea 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -29,9 +29,8 @@ namespace Nif { -class NiSourceTexture : public Named +struct NiSourceTexture : public Named { -public: // Is this an external (references a separate texture file) or // internal (data is inside the nif itself) texture? bool external; @@ -66,6 +65,24 @@ public: void post(NIFFile *nif) override; }; +struct BSShaderTextureSet : public Record +{ + enum TextureType + { + TextureType_Base = 0, + TextureType_Normal = 1, + TextureType_Glow = 2, + TextureType_Parallax = 3, + TextureType_Env = 4, + TextureType_EnvMask = 5, + TextureType_Subsurface = 6, + TextureType_BackLighting = 7 + }; + std::vector textures; + + void read(NIFStream *nif) override; +}; + struct NiParticleModifier : public Record { NiParticleModifierPtr next; @@ -75,27 +92,24 @@ struct NiParticleModifier : public Record void post(NIFFile *nif) override; }; -class NiParticleGrowFade : public NiParticleModifier +struct NiParticleGrowFade : public NiParticleModifier { -public: float growTime; float fadeTime; void read(NIFStream *nif) override; }; -class NiParticleColorModifier : public NiParticleModifier +struct NiParticleColorModifier : public NiParticleModifier { -public: NiColorDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; -class NiGravity : public NiParticleModifier +struct NiGravity : public NiParticleModifier { -public: float mForce; /* 0 - Wind (fixed direction) * 1 - Point (fixed origin) @@ -115,27 +129,24 @@ struct NiParticleCollider : public NiParticleModifier }; // NiPinaColada -class NiPlanarCollider : public NiParticleCollider +struct NiPlanarCollider : public NiParticleCollider { -public: void read(NIFStream *nif) override; osg::Vec3f mPlaneNormal; float mPlaneDistance; }; -class NiSphericalCollider : public NiParticleCollider +struct NiSphericalCollider : public NiParticleCollider { -public: float mRadius; osg::Vec3f mCenter; void read(NIFStream *nif) override; }; -class NiParticleRotation : public NiParticleModifier +struct NiParticleRotation : public NiParticleModifier { -public: void read(NIFStream *nif) override; }; diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 1e909120e..704c4928e 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -325,4 +325,15 @@ namespace Nif data.post(nif); } + void NiColorInterpolator::read(NIFStream *nif) + { + defaultVal = nif->getVector4(); + data.read(nif); + } + + void NiColorInterpolator::post(NIFFile *nif) + { + data.post(nif); + } + } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 6b84d3749..503710fe9 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -29,15 +29,14 @@ namespace Nif { -class NiParticleSystemController : public Controller +struct NiParticleSystemController : public Controller { -public: struct Particle { osg::Vec3f velocity; float lifetime; float lifespan; float timestamp; - int vertex; + unsigned short vertex; }; float velocity; @@ -80,9 +79,8 @@ public: }; using NiBSPArrayController = NiParticleSystemController; -class NiMaterialColorController : public Controller +struct NiMaterialColorController : public Controller { -public: NiPoint3InterpolatorPtr interpolator; NiPosDataPtr data; unsigned int targetColor; @@ -91,9 +89,8 @@ public: void post(NIFFile *nif) override; }; -class NiPathController : public Controller +struct NiPathController : public Controller { -public: NiPosDataPtr posData; NiFloatDataPtr floatData; @@ -115,9 +112,8 @@ public: void post(NIFFile *nif) override; }; -class NiLookAtController : public Controller +struct NiLookAtController : public Controller { -public: NodePtr target; unsigned short lookAtFlags{0}; @@ -125,9 +121,8 @@ public: void post(NIFFile *nif) override; }; -class NiUVController : public Controller +struct NiUVController : public Controller { -public: NiUVDataPtr data; unsigned int uvSet; @@ -135,9 +130,8 @@ public: void post(NIFFile *nif) override; }; -class NiKeyframeController : public Controller +struct NiKeyframeController : public Controller { -public: NiKeyframeDataPtr data; NiTransformInterpolatorPtr interpolator; @@ -154,12 +148,11 @@ struct NiFloatInterpController : public Controller void post(NIFFile *nif) override; }; -class NiAlphaController : public NiFloatInterpController { }; -class NiRollController : public NiFloatInterpController { }; +struct NiAlphaController : public NiFloatInterpController { }; +struct NiRollController : public NiFloatInterpController { }; -class NiGeomMorpherController : public Controller +struct NiGeomMorpherController : public Controller { -public: NiMorphDataPtr data; NiFloatInterpolatorList interpolators; @@ -167,18 +160,16 @@ public: void post(NIFFile *nif) override; }; -class NiVisController : public Controller +struct NiVisController : public Controller { -public: NiVisDataPtr data; void read(NIFStream *nif) override; void post(NIFFile *nif) override; }; -class NiFlipController : public Controller +struct NiFlipController : public Controller { -public: NiFloatInterpolatorPtr mInterpolator; int mTexSlot; // NiTexturingProperty::TextureType float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources @@ -230,5 +221,13 @@ struct NiTransformInterpolator : public Interpolator void post(NIFFile *nif) override; }; +struct NiColorInterpolator : public Interpolator +{ + osg::Vec4f defaultVal; + NiColorDataPtr data; + void read(NIFStream *nif) override; + void post(NIFFile *nif) override; +}; + } // Namespace #endif diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 908313f50..66a391afc 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -32,9 +32,8 @@ namespace Nif { // Common ancestor for several data classes -class NiGeometryData : public Record +struct NiGeometryData : public Record { -public: std::vector vertices, normals, tangents, bitangents; std::vector colors; std::vector< std::vector > uvlist; @@ -44,18 +43,16 @@ public: void read(NIFStream *nif) override; }; -class NiTriShapeData : public NiGeometryData +struct NiTriShapeData : public NiGeometryData { -public: // Triangles, three vertex indices per triangle std::vector triangles; void read(NIFStream *nif) override; }; -class NiTriStripsData : public NiGeometryData +struct NiTriStripsData : public NiGeometryData { -public: // Triangle strips, series of vertex indices. std::vector> strips; @@ -70,9 +67,8 @@ struct NiLinesData : public NiGeometryData void read(NIFStream *nif) override; }; -class NiParticlesData : public NiGeometryData +struct NiParticlesData : public NiGeometryData { -public: int numParticles{0}; int activeCount; @@ -84,39 +80,34 @@ public: void read(NIFStream *nif) override; }; -class NiRotatingParticlesData : public NiParticlesData +struct NiRotatingParticlesData : public NiParticlesData { -public: void read(NIFStream *nif) override; }; -class NiPosData : public Record +struct NiPosData : public Record { -public: Vector3KeyMapPtr mKeyList; void read(NIFStream *nif) override; }; -class NiUVData : public Record +struct NiUVData : public Record { -public: FloatKeyMapPtr mKeyList[4]; void read(NIFStream *nif) override; }; -class NiFloatData : public Record +struct NiFloatData : public Record { -public: FloatKeyMapPtr mKeyList; void read(NIFStream *nif) override; }; -class NiPixelData : public Record +struct NiPixelData : public Record { -public: enum Format { NIPXFMT_RGB8, @@ -150,17 +141,15 @@ public: void post(NIFFile *nif) override; }; -class NiColorData : public Record +struct NiColorData : public Record { -public: Vector4KeyMapPtr mKeyMap; void read(NIFStream *nif) override; }; -class NiVisData : public Record +struct NiVisData : public Record { -public: struct VisData { float time; bool isSet; @@ -170,9 +159,8 @@ public: void read(NIFStream *nif) override; }; -class NiSkinInstance : public Record +struct NiSkinInstance : public Record { -public: NiSkinDataPtr data; NiSkinPartitionPtr partitions; NodePtr root; @@ -182,9 +170,8 @@ public: void post(NIFFile *nif) override; }; -class NiSkinData : public Record +struct NiSkinData : public Record { -public: struct VertWeight { unsigned short vertex; @@ -251,9 +238,8 @@ struct NiKeyframeData : public Record void read(NIFStream *nif) override; }; -class NiPalette : public Record +struct NiPalette : public Record { -public: // 32-bit RGBA colors that correspond to 8-bit indices std::vector colors; diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 5d7aa0c3b..6d345a18e 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -29,15 +29,13 @@ namespace Nif { -class NiVertWeightsExtraData : public Extra +struct NiVertWeightsExtraData : public Extra { -public: void read(NIFStream *nif) override; }; -class NiTextKeyExtraData : public Extra +struct NiTextKeyExtraData : public Extra { -public: struct TextKey { float time; @@ -48,9 +46,8 @@ public: void read(NIFStream *nif) override; }; -class NiStringExtraData : public Extra +struct NiStringExtraData : public Extra { -public: /* Two known meanings: "MRK" - marker, only visible in the editor, not rendered in-game "NCO" - no collision diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 67c864f47..665533c91 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -129,6 +129,10 @@ static std::map makeFactory() factory["NiPoint3Interpolator"] = {&construct , RC_NiPoint3Interpolator }; factory["NiTransformController"] = {&construct , RC_NiKeyframeController }; factory["NiTransformInterpolator"] = {&construct , RC_NiTransformInterpolator }; + factory["NiColorInterpolator"] = {&construct , RC_NiColorInterpolator }; + factory["BSShaderTextureSet"] = {&construct , RC_BSShaderTextureSet }; + factory["BSLODTriShape"] = {&construct , RC_BSLODTriShape }; + factory["BSShaderProperty"] = {&construct , RC_BSShaderProperty }; return factory; } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 58397bdca..1d082b8f9 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -131,9 +131,8 @@ struct NiBoundingVolume parent node (unless it's the root), and transformation (location and rotation) relative to it's parent. */ -class Node : public Named +struct Node : public Named { -public: // Node flags. Interpretation depends somewhat on the type of node. unsigned int flags; Transformation trafo; @@ -240,43 +239,6 @@ struct NiNode : Node }; struct NiGeometry : Node -{ - struct MaterialData - { - std::vector materialNames; - std::vector materialExtraData; - unsigned int activeMaterial{0}; - bool materialNeedsUpdate{false}; - void read(NIFStream *nif) - { - if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) - return; - unsigned int numMaterials = 0; - if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3)) - numMaterials = nif->getBoolean(); // Has Shader - else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) - numMaterials = nif->getUInt(); - if (numMaterials) - { - nif->getStrings(materialNames, numMaterials); - nif->getInts(materialExtraData, numMaterials); - } - if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) - activeMaterial = nif->getUInt(); - if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) - { - materialNeedsUpdate = nif->getBoolean(); - if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) - nif->skip(8); - } - } - }; - - NiSkinInstancePtr skin; - MaterialData materialData; -}; - -struct NiTriShape : NiGeometry { /* Possible flags: 0x40 - mesh has no vertex normals ? @@ -285,14 +247,50 @@ struct NiTriShape : NiGeometry been observed so far. */ - NiTriShapeDataPtr data; + struct MaterialData + { + std::vector names; + std::vector extra; + unsigned int active{0}; + bool needsUpdate{false}; + void read(NIFStream *nif) + { + if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) + return; + unsigned int num = 0; + if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3)) + num = nif->getBoolean(); // Has Shader + else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) + num = nif->getUInt(); + if (num) + { + nif->getStrings(names, num); + nif->getInts(extra, num); + } + if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) + active = nif->getUInt(); + if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) + needsUpdate = nif->getBoolean(); + } + }; + + NiGeometryDataPtr data; + NiSkinInstancePtr skin; + MaterialData material; + BSShaderPropertyPtr shaderprop; + NiAlphaPropertyPtr alphaprop; void read(NIFStream *nif) override { Node::read(nif); data.read(nif); skin.read(nif); - materialData.read(nif); + material.read(nif); + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + { + shaderprop.read(nif); + alphaprop.read(nif); + } } void post(NIFFile *nif) override @@ -300,53 +298,28 @@ struct NiTriShape : NiGeometry Node::post(nif); data.post(nif); skin.post(nif); - if (!skin.empty()) + shaderprop.post(nif); + alphaprop.post(nif); + if (recType != RC_NiParticles && !skin.empty()) nif->setUseSkinning(true); } }; -struct NiTriStrips : NiGeometry +struct NiTriShape : NiGeometry {}; +struct BSLODTriShape : NiTriShape { - NiTriStripsDataPtr data; - + unsigned int lod0, lod1, lod2; void read(NIFStream *nif) override { - Node::read(nif); - data.read(nif); - skin.read(nif); - materialData.read(nif); - } - - void post(NIFFile *nif) override - { - Node::post(nif); - data.post(nif); - skin.post(nif); - if (!skin.empty()) - nif->setUseSkinning(true); - } -}; - -struct NiLines : NiGeometry -{ - NiLinesDataPtr data; - - void read(NIFStream *nif) override - { - Node::read(nif); - data.read(nif); - skin.read(nif); - } - - void post(NIFFile *nif) override - { - Node::post(nif); - data.post(nif); - skin.post(nif); - if (!skin.empty()) - nif->setUseSkinning(true); + NiTriShape::read(nif); + lod0 = nif->getUInt(); + lod1 = nif->getUInt(); + lod2 = nif->getUInt(); } }; +struct NiTriStrips : NiGeometry {}; +struct NiLines : NiGeometry {}; +struct NiParticles : NiGeometry { }; struct NiCamera : Node { @@ -401,25 +374,6 @@ struct NiCamera : Node } }; -struct NiParticles : NiGeometry -{ - NiParticlesDataPtr data; - void read(NIFStream *nif) override - { - Node::read(nif); - data.read(nif); - skin.read(nif); - materialData.read(nif); - } - - void post(NIFFile *nif) override - { - Node::post(nif); - data.post(nif); - skin.post(nif); - } -}; - // A node used as the base to switch between child nodes, such as for LOD levels. struct NiSwitchNode : public NiNode { diff --git a/components/nif/property.cpp b/components/nif/property.cpp index e6ae71c24..d5357e123 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -99,6 +99,25 @@ void NiTexturingProperty::post(NIFFile *nif) shaderTextures[i].post(nif); } +void BSShaderProperty::read(NIFStream *nif) +{ + NiShadeProperty::read(nif); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + { + type = nif->getUInt(); + flags1 = nif->getUInt(); + flags2 = nif->getUInt(); + envMapIntensity = nif->getFloat(); + } +} + +void BSShaderLightingProperty::read(NIFStream *nif) +{ + BSShaderProperty::read(nif); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + clamp = nif->getUInt(); +} + void NiFogProperty::read(NIFStream *nif) { Property::read(nif); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index c821b7c37..eccb442f7 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -29,11 +29,10 @@ namespace Nif { -class Property : public Named { }; +struct Property : public Named { }; -class NiTexturingProperty : public Property +struct NiTexturingProperty : public Property { -public: unsigned short flags{0u}; // A sub-texture @@ -96,9 +95,8 @@ public: void post(NIFFile *nif) override; }; -class NiFogProperty : public Property +struct NiFogProperty : public Property { -public: unsigned short mFlags; float mFogDepth; osg::Vec3f mColour; @@ -118,6 +116,19 @@ struct NiShadeProperty : public Property } }; +struct BSShaderProperty : public NiShadeProperty +{ + unsigned int type{0u}, flags1{0u}, flags2{0u}; + float envMapIntensity{0.f}; + void read(NIFStream *nif) override; +}; + +struct BSShaderLightingProperty : public BSShaderProperty +{ + unsigned int clamp{0u}; + void read(NIFStream *nif) override; +}; + struct NiDitherProperty : public Property { unsigned short flags; @@ -294,8 +305,8 @@ struct S_StencilProperty void read(NIFStream *nif); }; -class NiAlphaProperty : public StructPropT { }; -class NiVertexColorProperty : public StructPropT { }; +struct NiAlphaProperty : public StructPropT { }; +struct NiVertexColorProperty : public StructPropT { }; struct NiStencilProperty : public Property { S_StencilProperty data; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 2217c588e..efacd8246 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -118,7 +118,11 @@ enum RecordType RC_NiFloatInterpolator, RC_NiPoint3Interpolator, RC_NiBoolInterpolator, - RC_NiTransformInterpolator + RC_NiTransformInterpolator, + RC_NiColorInterpolator, + RC_BSShaderTextureSet, + RC_BSLODTriShape, + RC_BSShaderProperty }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index ed8f7ef6b..b30d99fbe 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -120,33 +120,34 @@ public: }; -class Node; -class Extra; -class Property; -class NiUVData; -class NiPosData; -class NiVisData; -class Controller; -class Named; -class NiSkinData; -class NiFloatData; +struct Node; +struct Extra; +struct Property; +struct NiUVData; +struct NiPosData; +struct NiVisData; +struct Controller; +struct Named; +struct NiSkinData; +struct NiFloatData; struct NiMorphData; -class NiPixelData; -class NiColorData; +struct NiPixelData; +struct NiColorData; struct NiKeyframeData; -class NiTriShapeData; -class NiTriStripsData; -class NiSkinInstance; -class NiSourceTexture; -class NiParticlesData; -class NiPalette; +struct NiTriStripsData; +struct NiSkinInstance; +struct NiSourceTexture; +struct NiPalette; struct NiParticleModifier; -struct NiLinesData; struct NiBoolData; struct NiSkinPartition; struct NiFloatInterpolator; struct NiPoint3Interpolator; struct NiTransformInterpolator; +struct BSShaderTextureSet; +struct NiGeometryData; +struct BSShaderProperty; +struct NiAlphaProperty; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -161,12 +162,8 @@ using NiPixelDataPtr = RecordPtrT; using NiFloatDataPtr = RecordPtrT; using NiColorDataPtr = RecordPtrT; using NiKeyframeDataPtr = RecordPtrT; -using NiTriShapeDataPtr = RecordPtrT; -using NiTriStripsDataPtr = RecordPtrT; -using NiLinesDataPtr = RecordPtrT; using NiSkinInstancePtr = RecordPtrT; using NiSourceTexturePtr = RecordPtrT; -using NiParticlesDataPtr = RecordPtrT; using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; @@ -174,12 +171,17 @@ using NiSkinPartitionPtr = RecordPtrT; using NiFloatInterpolatorPtr = RecordPtrT; using NiPoint3InterpolatorPtr = RecordPtrT; using NiTransformInterpolatorPtr = RecordPtrT; +using BSShaderTextureSetPtr = RecordPtrT; +using NiGeometryDataPtr = RecordPtrT; +using BSShaderPropertyPtr = RecordPtrT; +using NiAlphaPropertyPtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; using ExtraList = RecordListT; using NiSourceTextureList = RecordListT; using NiFloatInterpolatorList = RecordListT; +using NiTriStripsDataList = RecordListT; } // Namespace #endif diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index db9d5ae43..d72cef194 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -34,11 +34,10 @@ bool pathFileNameStartsWithX(const std::string& path) void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform) { - mesh.preallocateVertices(static_cast(data.vertices.size())); - mesh.preallocateIndices(static_cast(data.triangles.size())); - const std::vector &vertices = data.vertices; const std::vector &triangles = data.triangles; + mesh.preallocateVertices(static_cast(vertices.size())); + mesh.preallocateIndices(static_cast(triangles.size())); for (std::size_t i = 0; i < triangles.size(); i += 3) { @@ -54,8 +53,6 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co { const std::vector &vertices = data.vertices; const std::vector> &strips = data.strips; - if (vertices.empty() || strips.empty()) - return; mesh.preallocateVertices(static_cast(vertices.size())); int numTriangles = 0; for (const std::vector& strip : strips) @@ -102,12 +99,12 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co } } -void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* nifNode, const osg::Matrixf &transform = osg::Matrixf()) +void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiGeometry* geometry, const osg::Matrixf &transform = osg::Matrixf()) { - if (nifNode->recType == Nif::RC_NiTriShape) - fillTriangleMesh(mesh, static_cast(nifNode)->data.get(), transform); - else if (nifNode->recType == Nif::RC_NiTriStrips) - fillTriangleMesh(mesh, static_cast(nifNode)->data.get(), transform); + if (geometry->recType == Nif::RC_NiTriShape || geometry->recType == Nif::RC_BSLODTriShape) + fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); + else if (geometry->recType == Nif::RC_NiTriStrips) + fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); } } @@ -145,12 +142,12 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { if (findBoundingBox(node, filename)) { - const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); - const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); + const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.extents); + const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.center); std::unique_ptr compound (new btCompoundShape); - std::unique_ptr boxShape(new btBoxShape(halfExtents)); + std::unique_ptr boxShape(new btBoxShape(extents)); btTransform transform = btTransform::getIdentity(); - transform.setOrigin(origin); + transform.setOrigin(center); compound->addChildShape(transform, boxShape.get()); boxShape.release(); @@ -208,8 +205,8 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& switch (type) { case Nif::NiBoundingVolume::Type::BOX_BV: - mShape->mCollisionBoxHalfExtents = node->bounds.box.extents; - mShape->mCollisionBoxTranslate = node->bounds.box.center; + mShape->mCollisionBox.extents = node->bounds.box.extents; + mShape->mCollisionBox.center = node->bounds.box.center; break; default: { @@ -312,7 +309,9 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape || node->recType == Nif::RC_NiTriStrips)) + if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape + || node->recType == Nif::RC_NiTriStrips + || node->recType == Nif::RC_BSLODTriShape)) { handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid); } @@ -341,23 +340,31 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons if ((flags & 0x800)) return; - if (nifNode->recType == Nif::RC_NiTriShape) + auto niGeometry = static_cast(nifNode); + if (niGeometry->data.empty() || niGeometry->data->vertices.empty()) + return; + + if (niGeometry->recType == Nif::RC_NiTriShape || niGeometry->recType == Nif::RC_BSLODTriShape) { - const Nif::NiTriShape* shape = static_cast(nifNode); - if (!shape->skin.empty()) - isAnimated = false; - if (shape->data.empty() || shape->data->triangles.empty()) + if (niGeometry->data->recType != Nif::RC_NiTriShapeData) + return; + + auto data = static_cast(niGeometry->data.getPtr()); + if (data->triangles.empty()) return; } - else + else if (niGeometry->recType == Nif::RC_NiTriStrips) { - const Nif::NiTriStrips* shape = static_cast(nifNode); - if (!shape->skin.empty()) - isAnimated = false; - if (shape->data.empty() || shape->data->strips.empty()) + if (niGeometry->data->recType != Nif::RC_NiTriStripsData) + return; + + auto data = static_cast(niGeometry->data.getPtr()); + if (data->strips.empty()) return; } + if (!niGeometry->skin.empty()) + isAnimated = false; if (isAnimated) { @@ -366,7 +373,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons std::unique_ptr childMesh(new btTriangleMesh); - fillTriangleMesh(*childMesh, nifNode); + fillTriangleMesh(*childMesh, niGeometry); std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); @@ -394,7 +401,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons if (!mAvoidStaticMesh) mAvoidStaticMesh.reset(new btTriangleMesh(false)); - fillTriangleMesh(*mAvoidStaticMesh, nifNode, transform); + fillTriangleMesh(*mAvoidStaticMesh, niGeometry, transform); } else { @@ -402,7 +409,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons mStaticMesh.reset(new btTriangleMesh(false)); // Static shape, just transform all vertices into position - fillTriangleMesh(*mStaticMesh, nifNode, transform); + fillTriangleMesh(*mStaticMesh, niGeometry, transform); } } diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 0063b2ec0..96beafcbb 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -66,7 +66,9 @@ namespace NifOsg std::conjunction_v< std::disjunction< std::is_same, - std::is_same + std::is_same, + std::is_same, + std::is_same >, std::is_same >, diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0db15237e..902f15fb3 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -67,6 +67,7 @@ namespace case Nif::RC_NiTriShape: case Nif::RC_NiTriStrips: case Nif::RC_NiLines: + case Nif::RC_BSLODTriShape: return true; } return false; @@ -95,6 +96,15 @@ namespace } } } + + auto geometry = dynamic_cast(nifNode); + if (geometry) + { + if (!geometry->shaderprop.empty()) + out.emplace_back(geometry->shaderprop.getPtr()); + if (!geometry->alphaprop.empty()) + out.emplace_back(geometry->alphaprop.getPtr()); + } } // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale @@ -365,6 +375,11 @@ namespace NifOsg handleProperty(props[i].getPtr(), applyTo, composite, imageManager, boundTextures, animflags); } } + + auto geometry = dynamic_cast(nifNode); + // NiGeometry's NiAlphaProperty doesn't get handled here because it's a drawable property + if (geometry && !geometry->shaderprop.empty()) + handleProperty(geometry->shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags); } void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags) @@ -466,19 +481,12 @@ namespace NifOsg texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); - osg::ref_ptr texEnv = new osg::TexEnvCombine; - texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - int texUnit = 3; // FIXME osg::StateSet* stateset = node->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); } @@ -946,11 +954,11 @@ namespace NifOsg // Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and colors. void handleParticleInitialState(const Nif::Node* nifNode, osgParticle::ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) { - const auto particleNode = static_cast(nifNode); - if (particleNode->data.empty()) + auto particleNode = static_cast(nifNode); + if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData) return; - const Nif::NiParticlesData* particledata = particleNode->data.getPtr(); + auto particledata = static_cast(particleNode->data.getPtr()); osg::BoundingBox box; @@ -963,6 +971,9 @@ namespace NifOsg if (particle.lifespan <= 0) continue; + if (particle.vertex >= particledata->vertices.size()) + continue; + ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime)); osgParticle::Particle* created = partsys->createParticle(&particletemplate); @@ -971,16 +982,16 @@ namespace NifOsg // Note this position and velocity is not correct for a particle system with absolute reference frame, // which can not be done in this loader since we are not attached to the scene yet. Will be fixed up post-load in the SceneManager. created->setVelocity(particle.velocity); - const osg::Vec3f& position = particledata->vertices.at(particle.vertex); + const osg::Vec3f& position = particledata->vertices[particle.vertex]; created->setPosition(position); osg::Vec4f partcolor (1.f,1.f,1.f,1.f); - if (particle.vertex < int(particledata->colors.size())) - partcolor = particledata->colors.at(particle.vertex); + if (particle.vertex < particledata->colors.size()) + partcolor = particledata->colors[particle.vertex]; float size = partctrl->size; - if (particle.vertex < int(particledata->sizes.size())) - size *= particledata->sizes.at(particle.vertex); + if (particle.vertex < particledata->sizes.size()) + size *= particledata->sizes[particle.vertex]; created->setSizeRange(osgParticle::rangef(size, size)); box.expandBy(osg::BoundingSphere(position, size)); @@ -1177,51 +1188,50 @@ namespace NifOsg void handleNiGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - const Nif::NiGeometryData* niGeometryData = nullptr; - if (nifNode->recType == Nif::RC_NiTriShape) + const Nif::NiGeometry* niGeometry = static_cast(nifNode); + if (niGeometry->data.empty()) + return; + const Nif::NiGeometryData* niGeometryData = niGeometry->data.getPtr(); + + if (niGeometry->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_BSLODTriShape) { - const Nif::NiTriShape* triShape = static_cast(nifNode); - if (!triShape->data.empty()) - { - const Nif::NiTriShapeData* data = triShape->data.getPtr(); - niGeometryData = static_cast(data); - if (!data->triangles.empty()) - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(), - (unsigned short*)data->triangles.data())); - } + if (niGeometryData->recType != Nif::RC_NiTriShapeData) + return; + auto triangles = static_cast(niGeometryData)->triangles; + if (triangles.empty()) + return; + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, triangles.size(), + (unsigned short*)triangles.data())); } - else if (nifNode->recType == Nif::RC_NiTriStrips) + else if (niGeometry->recType == Nif::RC_NiTriStrips) { - const Nif::NiTriStrips* triStrips = static_cast(nifNode); - if (!triStrips->data.empty()) + if (niGeometryData->recType != Nif::RC_NiTriStripsData) + return; + auto data = static_cast(niGeometryData); + bool hasGeometry = false; + for (const auto& strip : data->strips) { - const Nif::NiTriStripsData* data = triStrips->data.getPtr(); - niGeometryData = static_cast(data); - if (!data->strips.empty()) - { - for (const auto& strip : data->strips) - { - if (strip.size() >= 3) - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), - (unsigned short*)strip.data())); - } - } + if (strip.size() < 3) + continue; + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), + (unsigned short*)strip.data())); + hasGeometry = true; } + if (!hasGeometry) + return; } - else if (nifNode->recType == Nif::RC_NiLines) + else if (niGeometry->recType == Nif::RC_NiLines) { - const Nif::NiLines* lines = static_cast(nifNode); - if (!lines->data.empty()) - { - const Nif::NiLinesData* data = lines->data.getPtr(); - niGeometryData = static_cast(data); - const auto& line = data->lines; - if (!line.empty()) - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), (unsigned short*)line.data())); - } + if (niGeometryData->recType != Nif::RC_NiLinesData) + return; + auto data = static_cast(niGeometryData); + const auto& line = data->lines; + if (line.empty()) + return; + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), + (unsigned short*)line.data())); } - if (niGeometryData) - handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name); + handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name); // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. @@ -1229,15 +1239,18 @@ namespace NifOsg // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(nifNode, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, niGeometryData && !niGeometryData->colors.empty(), animflags); + applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->colors.empty(), animflags); } void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { assert(isTypeGeometry(nifNode->recType)); - osg::ref_ptr drawable; osg::ref_ptr geom (new osg::Geometry); handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); + // If the record had no valid geometry data in it, early-out + if (geom->empty()) + return; + osg::ref_ptr drawable; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) @@ -1282,6 +1295,8 @@ namespace NifOsg assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr geometry (new osg::Geometry); handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); + if (geometry->empty()) + return; osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); rig->setName(nifNode->name); @@ -1481,6 +1496,17 @@ namespace NifOsg return image; } + osg::ref_ptr createEmissiveTexEnv() + { + osg::ref_ptr texEnv(new osg::TexEnvCombine); + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + return texEnv; + } + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { if (!boundTextures.empty()) @@ -1567,14 +1593,7 @@ namespace NifOsg if (i == Nif::NiTexturingProperty::GlowTexture) { - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); } else if (i == Nif::NiTexturingProperty::DarkTexture) { diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index dd89f4501..b0a46f0ca 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -14,10 +14,10 @@ namespace Nif { - class NiGravity; - class NiPlanarCollider; - class NiSphericalCollider; - class NiColorData; + struct NiGravity; + struct NiPlanarCollider; + struct NiSphericalCollider; + struct NiColorData; } namespace NifOsg diff --git a/components/resource/animation.cpp b/components/resource/animation.cpp index 34ae162ee..d2d7d08d5 100644 --- a/components/resource/animation.cpp +++ b/components/resource/animation.cpp @@ -10,7 +10,7 @@ namespace Resource mStartTime(0.0f) { const osgAnimation::ChannelList& channels = anim.getChannels(); - for (const osg::ref_ptr channel: channels) + for (const auto& channel: channels) addChannel(channel.get()->clone()); } @@ -31,7 +31,7 @@ namespace Resource bool Animation::update (double time) { - for (const osg::ref_ptr channel: mChannels) + for (const auto& channel: mChannels) { channel->update(time, 1.0f, 0); } diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 7dd0964e8..fc68c5545 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -20,8 +20,7 @@ BulletShape::BulletShape() BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape)) , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape)) - , mCollisionBoxHalfExtents(copy.mCollisionBoxHalfExtents) - , mCollisionBoxTranslate(copy.mCollisionBoxTranslate) + , mCollisionBox(copy.mCollisionBox) , mAnimatedShapes(copy.mAnimatedShapes) { } @@ -106,8 +105,7 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) : BulletShape() , mSource(source) { - mCollisionBoxHalfExtents = source->mCollisionBoxHalfExtents; - mCollisionBoxTranslate = source->mCollisionBoxTranslate; + mCollisionBox = source->mCollisionBox; mAnimatedShapes = source->mAnimatedShapes; diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index e77c96327..7d9577ba0 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -27,10 +27,14 @@ namespace Resource btCollisionShape* mCollisionShape; btCollisionShape* mAvoidCollisionShape; + struct CollisionBox + { + osg::Vec3f extents; + osg::Vec3f center; + }; // Used for actors. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures. // For now, use one file <-> one resource for simplicity. - osg::Vec3f mCollisionBoxHalfExtents; - osg::Vec3f mCollisionBoxTranslate; + CollisionBox mCollisionBox; // Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape // will be a btCompoundShape (which consists of one or more child shapes). diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index af0f365ee..d739392e8 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -21,7 +21,7 @@ namespace Resource void RetrieveAnimationsVisitor::apply(osg::Node& node) { - if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("root")) + if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("bip01")) { osg::ref_ptr callback = new SceneUtil::OsgAnimationController(); @@ -40,11 +40,9 @@ namespace Resource std::string animationName = animation->getName(); std::string start = animationName + std::string(": start"); std::string stop = animationName + std::string(": stop"); - std::string loopstart = animationName + std::string(": loop start"); - std::string loopstop = animationName + std::string(": loop stop"); const osgAnimation::ChannelList& channels = animation->getChannels(); - for (const osg::ref_ptr channel: channels) + for (const auto& channel: channels) { mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? } @@ -60,8 +58,6 @@ namespace Resource // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" mTarget.mTextKeys.emplace(startTime, std::move(start)); mTarget.mTextKeys.emplace(stopTime, std::move(stop)); - mTarget.mTextKeys.emplace(startTime, std::move(loopstart)); - mTarget.mTextKeys.emplace(stopTime, std::move(loopstop)); SceneUtil::EmulatedAnimation emulatedAnimation; emulatedAnimation.mStartTime = startTime; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index e16c07539..5ebed8f12 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -967,7 +967,9 @@ void SceneUtil::MWShadowTechnique::shareShadowMap(osgUtil::CullVisitor& cv, View bool MWShadowTechnique::trySharedShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* vdd) { - auto* sharedConfig = dynamic_cast(cv.getCurrentCamera()->getUserData()); + auto* sharedConfig = dynamic_cast(cv.getUserData()); + if (!sharedConfig) + sharedConfig = dynamic_cast(cv.getCurrentCamera()->getUserData()); if (!sharedConfig) { return false; @@ -1002,7 +1004,9 @@ bool MWShadowTechnique::trySharedShadowMap(osgUtil::CullVisitor& cv, ViewDepende void SceneUtil::MWShadowTechnique::endSharedShadowMap(osgUtil::CullVisitor& cv) { - auto* sharedConfig = dynamic_cast(cv.getCurrentCamera()->getUserData()); + auto* sharedConfig = dynamic_cast(cv.getUserData()); + if (!sharedConfig) + sharedConfig = dynamic_cast(cv.getCurrentCamera()->getUserData()); if (!sharedConfig) { return; diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 87e6f02fe..37aa525af 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -31,7 +31,7 @@ namespace SceneUtil void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) { const osgAnimation::ChannelList& channels = mAnimation->getChannels(); - for (const osg::ref_ptr channel: channels) + for (const auto& channel: channels) { const std::string& channelName = channel->getName(); const std::string& channelTargetName = channel->getTargetName(); @@ -83,7 +83,7 @@ namespace SceneUtil { osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(cb); if (umt) - if (node.getName() != "root") link(umt); + if (node.getName() != "bip01") link(umt); cb = cb->getNestedCallback(); } @@ -117,7 +117,7 @@ namespace SceneUtil //Find the correct animation based on time for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) { - if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) + if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime) { newTime = time - emulatedAnimation.mStartTime; animationName = emulatedAnimation.mName; @@ -125,15 +125,15 @@ namespace SceneUtil } //Find the root transform track in animation - for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) + for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() != animationName) continue; const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); - for (const osg::ref_ptr channel: channels) + for (const auto& channel: channels) { - if (channel->getTargetName() != "root" || channel->getName() != "transform") continue; + if (channel->getTargetName() != "bip01" || channel->getName() != "transform") continue; if ( osgAnimation::MatrixLinearSampler* templateSampler = dynamic_cast (channel->getSampler()) ) { @@ -150,7 +150,7 @@ namespace SceneUtil void OsgAnimationController::update(float time, std::string animationName) { - for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) + for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (mergedAnimationTrack->getName() == animationName) mergedAnimationTrack->update(time); } @@ -162,7 +162,7 @@ namespace SceneUtil { if (mNeedToLink) { - for (const osg::ref_ptr mergedAnimationTrack : mMergedAnimationTracks) + for (const auto& mergedAnimationTrack : mMergedAnimationTracks) { if (!mLinker.valid()) mLinker = new LinkVisitor(); mLinker->setAnimation(mergedAnimationTrack); diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index 23a108ee9..3e8f62b4b 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -32,7 +32,7 @@ namespace Gui MyGUI::IntSize AutoSizedTextBox::getRequestedSize() { - return getTextSize(); + return getCaption().empty() ? MyGUI::IntSize{0, 0} : getTextSize(); } void AutoSizedTextBox::setCaption(const MyGUI::UString& _value) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index fd93eba61..4e1fe1318 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -428,3 +428,14 @@ even if the fighting NPC is knocked out. This setting allows the player to steal items from fighting NPCs that were knocked out if enabled. This setting can be controlled in Advanced tab of the launcher. + +graphic herbalism +----------------- + +:Type: boolean +:Range: True/False +:Default: True + +Some mods add harvestable container models. When this setting is enabled, activating a container using a harvestable model will visually harvest from it instead of opening the menu. + +When this setting is turned off or when activating a regular container, the menu will open as usual. diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index 70246fa4e..59050f932 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -267,6 +267,12 @@ + + + + + + diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 3f53180da..a3601ce94 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -724,6 +724,16 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C + + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + + + Enable graphic herbalism + + +