Merge remote-tracking branch 'remotes/origin/master' into openxr_vr_geometryshader_feature_branch

pull/615/head
Mads Buvik Sandvei 4 years ago
commit 3e82cae500

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

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

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

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

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

@ -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<const Record<ESM::Clothing>&>(record).get();
std::vector<ESM::PartReferenceType> parts;
if (clothing.mData.mType == ESM::Clothing::Robe)
{
auto reservedList = std::vector<ESM::PartReference>();
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>();
parts = {ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg};
}
std::vector<ESM::PartReference> reservedList;
for (const auto& p : parts)
{
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);
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);

@ -64,10 +64,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
// ignore content file number
std::map<ESM::RefNum, std::string>::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;
}

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

@ -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<Resource::ResourceSystem> 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<osg::Geometry> SceneWidget::createGradientRectangle(QColor bgColour, QColor gradientColour)
{
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
osg::ref_ptr<osg::Vec3Array> 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<osg::DrawElementsUShort> 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 <osg::Vec4ubArray> 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<osg::Camera> SceneWidget::createGradientCamera(QColor bgColour, QColor gradientColour)
{
osg::ref_ptr<osg::Camera> 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<osg::Geometry> gradientQuad = createGradientRectangle(bgColour, gradientColour);
camera->addChild(gradientQuad);
return camera;
}
void SceneWidget::updateGradientCamera(QColor bgColour, QColor gradientColour)
{
osg::ref_ptr<osg::Geometry> 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)

@ -100,10 +100,15 @@ namespace CSVRender
void mouseMoveEvent (QMouseEvent *event) override;
void wheelEvent (QWheelEvent *event) override;
osg::ref_ptr<osg::Geometry> createGradientRectangle(QColor bgColour, QColor gradientColour);
osg::ref_ptr<osg::Camera> createGradientCamera(QColor bgColour, QColor gradientColour);
void updateGradientCamera(QColor bgColour, QColor gradientColour);
std::shared_ptr<Resource::ResourceSystem> mResourceSystem;
Lighting* mLighting;
osg::ref_ptr<osg::Camera> mGradientCamera;
osg::Vec4f mDefaultAmbient;
bool mHasDefaultAmbient;
bool mIsExterior;

@ -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<Files::EscapePath>().mPath.string());
std::cout << v.describe() << std::endl;
getRawStdout() << v.describe() << std::endl;
return false;
}

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

@ -1,10 +1,12 @@
#include "loadingscreen.hpp"
#include <array>
#include <condition_variable>
#include <osgViewer/Viewer>
#include <osg/Texture2D>
#include <osg/Version>
#include <MyGUI_RenderManager.h>
#include <MyGUI_ScrollBar.h>
@ -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<std::mutex> 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<std::mutex> lock(mMutex);
mOneshot = false;
}
mSignal.notify_all();
}
void wait()
{
std::unique_lock<std::mutex> lock(mMutex);
while (mOneshot)
mSignal.wait(lock);
}
void waitUntilInvoked()
{
std::unique_lock<std::mutex> 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<osg::Texture2D> 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();
}

@ -3,6 +3,7 @@
#include <memory>
#include <osg/Camera>
#include <osg/Timer>
#include <osg/ref_ptr>
@ -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<std::string> mSplashScreens;
osg::ref_ptr<osg::Texture2D> mTexture;
osg::ref_ptr<CopyFramebufferToTextureCallback> mCopyFramebufferToTextureCallback;
osg::ref_ptr<osg::Camera::DrawCallback> mOldCallback;
bool mHasCallback;
std::unique_ptr<MyGUI::ITexture> mGuiTexture;
void changeWallpaper();

@ -339,6 +339,15 @@ namespace MWGui
{
int max = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().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()) + "/"

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

@ -87,6 +87,8 @@ namespace MWMechanics
int getLevelProgress() const;
int getLevelUpAttributeIncrease(int attribute) const;
int getLevelupAttributeMultiplier(int attribute) const;
int getSkillIncreasesForSpecialization(int spec) const;

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

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

@ -30,12 +30,9 @@ namespace MWPhysics
return btScalar(1);
auto* targetHolder = static_cast<PtrHolder*>(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;

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

@ -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<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& 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;
updateMechanics(data);
if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
// 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);
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;
}
// Remove actors that were deleted while the background thread was running
for (auto& data : mActorsFrameData)
{
if (!data.mActor.lock())
mMovementResults.erase(data.mPtr);
return mMovedActors;
}
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<MWWorld::Ptr>& 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,17 +348,17 @@ namespace MWPhysics
mCollisionWorld->removeCollisionObject(collisionObject);
}
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr)
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate)
{
if (mDeferAabbUpdate)
if (!mDeferAabbUpdate || immediate)
{
std::unique_lock lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
std::unique_lock lock(mCollisionWorldMutex);
updatePtrAabb(ptr);
}
else
{
std::unique_lock lock(mCollisionWorldMutex);
updatePtrAabb(ptr);
std::unique_lock lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
}
}
@ -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);
}
}
}

@ -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<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const std::vector<MWWorld::Ptr>& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const PtrPositionList& resetSimulation(const ActorMap& actors);
const std::vector<MWWorld::Ptr>& 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<PtrHolder> ptr);
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
private:
@ -60,8 +60,7 @@ namespace MWPhysics
std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<ActorFrameData> mActorsFrameData;
PtrPositionList mMovementResults;
PtrPositionList mPreviousMovementResults;
std::vector<MWWorld::Ptr> mMovedActors;
const float mPhysicsDt;
float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld;

@ -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<const Resource::BulletShape> 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<MWWorld::Ptr>& 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)
{

@ -50,8 +50,6 @@ class btVector3;
namespace MWPhysics
{
using PtrPositionList = std::map<MWWorld::Ptr, osg::Vec3f>;
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<MWWorld::Ptr>& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
/// Clear the queued movements list without applying.
void clearQueuedMovement();

@ -55,7 +55,7 @@ Projectile::~Projectile()
void Projectile::commitPositionChange()
{
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<MWWorld::Ptr>& 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;
}
}

@ -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<MWWorld::Ptr>& targets);
bool isValidTarget(const MWWorld::Ptr& target) const;
private:
std::unique_ptr<btCollisionShape> mShape;
@ -87,7 +91,9 @@ namespace MWPhysics
btVector3 mHitPosition;
btVector3 mHitNormal;
mutable std::mutex mPositionMutex;
std::vector<MWWorld::Ptr> mValidTargets;
mutable std::mutex mMutex;
osg::Vec3f mPosition;

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

@ -32,11 +32,7 @@ namespace MWScript
std::vector<MWWorld::Ptr> 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<class R>
@ -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<MWScript::InterpreterContext&>(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<MWScript::InterpreterContext&>(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));
}
};

@ -401,7 +401,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.
@ -437,6 +437,7 @@ namespace MWWorld
std::vector<MWWorld::Ptr> 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?
@ -483,7 +484,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
@ -513,6 +514,7 @@ namespace MWWorld
std::vector<MWWorld::Ptr> 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?
@ -561,7 +563,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;
@ -597,7 +599,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;
@ -621,32 +623,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<MWWorld::Ptr> 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);

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

@ -1293,6 +1293,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))
@ -1386,12 +1398,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()
@ -1538,20 +1545,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()

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

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

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

@ -3,6 +3,7 @@
#include <boost/filesystem/fstream.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
@ -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<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken()->composing())
("content", boost::program_options::value<std::vector<std::string> >()->default_value(std::vector<std::string>(), "")
->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon")
("data-local", boost::program_options::value<std::string>()->default_value(""));
("data", boost::program_options::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing())
("content", boost::program_options::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
("data-local", boost::program_options::value<Files::EscapePath>()->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<Files::PathContainer>());
dataDirs = Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>());
}
std::string local = variables["data-local"].as<std::string>();
Files::PathContainer::value_type local(variables["data-local"].as<Files::EscapePath>().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<std::string> contentFiles = variables["content"].as<std::vector<std::string> >();
std::vector<std::string> contentFiles = variables["content"].as<Files::EscapeStringVector>().toStdStringVector();
for (auto & contentFile : contentFiles)
mContentFiles.push_back(collections.getPath(contentFile));
}

@ -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<Nif::Node&>(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<Nif::NiGeometry&>(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<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> 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<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> 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<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> 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<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> 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<btBoxShape> box(new btBoxShape(btVector3(4, 5, 6)));
std::unique_ptr<btCompoundShape> 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>({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<Nif::NiTriShapeData*>(mNiTriShape.data.getPtr());
data->triangles.clear();
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));

@ -157,6 +157,8 @@ void Wizard::MainWizard::setupGameSettings()
mGameSettings.readUserFile(stream);
}
file.close();
// Now the rest
QStringList paths;
paths.append(userPath + QLatin1String("openmw.cfg"));

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

@ -0,0 +1,205 @@
#include <cassert>
#include <cwchar>
#include <iostream>
#include <sstream>
#include <thread>
#include "windows_crashcatcher.hpp"
#include "windows_crashmonitor.hpp"
#include "windows_crashshm.hpp"
#include <SDL_messagebox.h>
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; ++i)
{
if (strcmp(argv[i], "--crash-monitor"))
continue;
if (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<CrashSHM*>(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

@ -0,0 +1,79 @@
#ifndef WINDOWS_CRASHCATCHER_HPP
#define WINDOWS_CRASHCATCHER_HPP
#include <string>
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <components/crashcatcher/crashcatcher.hpp>
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

@ -0,0 +1,188 @@
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Psapi.h>
#include <DbgHelp.h>
#include <iostream>
#include <memory>
#include <sstream>
#include "windows_crashcatcher.hpp"
#include "windows_crashmonitor.hpp"
#include "windows_crashshm.hpp"
#include <components/debug/debuglog.hpp>
namespace Crash
{
CrashMonitor::CrashMonitor(HANDLE shmHandle)
: mShmHandle(shmHandle)
{
mShm = reinterpret_cast<CrashSHM*>(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

@ -0,0 +1,49 @@
#ifndef WINDOWS_CRASHMONITOR_HPP
#define WINDOWS_CRASHMONITOR_HPP
#include <windef.h>
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

@ -0,0 +1,45 @@
#ifndef WINDOWS_CRASHSHM_HPP
#define WINDOWS_CRASHSHM_HPP
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
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

@ -1,10 +1,13 @@
#include "debugging.hpp"
#include <chrono>
#include <memory>
#include <functional>
#include <components/crashcatcher/crashcatcher.hpp>
#ifdef _WIN32
# include <components/crashcatcher/windows_crashcatcher.hpp>
# undef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
@ -133,11 +136,19 @@ namespace Debug
}
}
static std::unique_ptr<std::ostream> 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::ostream>(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)))

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

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

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

@ -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<std::string> 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;
};

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

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

@ -32,9 +32,8 @@ namespace Nif
{
// Common ancestor for several data classes
class NiGeometryData : public Record
struct NiGeometryData : public Record
{
public:
std::vector<osg::Vec3f> vertices, normals, tangents, bitangents;
std::vector<osg::Vec4f> colors;
std::vector< std::vector<osg::Vec2f> > 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<unsigned short> triangles;
void read(NIFStream *nif) override;
};
class NiTriStripsData : public NiGeometryData
struct NiTriStripsData : public NiGeometryData
{
public:
// Triangle strips, series of vertex indices.
std::vector<std::vector<unsigned short>> 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<unsigned int> colors;

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

@ -129,6 +129,10 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
factory["NiPoint3Interpolator"] = {&construct <NiPoint3Interpolator> , RC_NiPoint3Interpolator };
factory["NiTransformController"] = {&construct <NiKeyframeController> , RC_NiKeyframeController };
factory["NiTransformInterpolator"] = {&construct <NiTransformInterpolator> , RC_NiTransformInterpolator };
factory["NiColorInterpolator"] = {&construct <NiColorInterpolator> , RC_NiColorInterpolator };
factory["BSShaderTextureSet"] = {&construct <BSShaderTextureSet> , RC_BSShaderTextureSet };
factory["BSLODTriShape"] = {&construct <BSLODTriShape> , RC_BSLODTriShape };
factory["BSShaderProperty"] = {&construct <BSShaderProperty> , RC_BSShaderProperty };
return factory;
}

@ -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;
@ -241,80 +240,57 @@ struct NiNode : Node
struct NiGeometry : Node
{
/* Possible flags:
0x40 - mesh has no vertex normals ?
Only flags included in 0x47 (ie. 0x01, 0x02, 0x04 and 0x40) have
been observed so far.
*/
struct MaterialData
{
std::vector<std::string> materialNames;
std::vector<int> materialExtraData;
unsigned int activeMaterial{0};
bool materialNeedsUpdate{false};
std::vector<std::string> names;
std::vector<int> extra;
unsigned int active{0};
bool needsUpdate{false};
void read(NIFStream *nif)
{
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0))
return;
unsigned int numMaterials = 0;
unsigned int num = 0;
if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3))
numMaterials = nif->getBoolean(); // Has Shader
num = nif->getBoolean(); // Has Shader
else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
numMaterials = nif->getUInt();
if (numMaterials)
num = nif->getUInt();
if (num)
{
nif->getStrings(materialNames, numMaterials);
nif->getInts(materialExtraData, numMaterials);
nif->getStrings(names, num);
nif->getInts(extra, num);
}
if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
activeMaterial = nif->getUInt();
active = 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);
}
needsUpdate = nif->getBoolean();
}
};
NiGeometryDataPtr data;
NiSkinInstancePtr skin;
MaterialData materialData;
};
struct NiTriShape : NiGeometry
{
/* Possible flags:
0x40 - mesh has no vertex normals ?
Only flags included in 0x47 (ie. 0x01, 0x02, 0x04 and 0x40) have
been observed so far.
*/
NiTriShapeDataPtr data;
MaterialData material;
BSShaderPropertyPtr shaderprop;
NiAlphaPropertyPtr alphaprop;
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 NiTriStrips : NiGeometry
{
NiTriStripsDataPtr data;
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
@ -322,31 +298,28 @@ struct NiTriStrips : 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 NiLines : NiGeometry
struct NiTriShape : NiGeometry {};
struct BSLODTriShape : NiTriShape
{
NiLinesDataPtr data;
unsigned int lod0, lod1, lod2;
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
{

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

@ -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<S_AlphaProperty> { };
class NiVertexColorProperty : public StructPropT<S_VertexColorProperty> { };
struct NiAlphaProperty : public StructPropT<S_AlphaProperty> { };
struct NiVertexColorProperty : public StructPropT<S_VertexColorProperty> { };
struct NiStencilProperty : public Property
{
S_StencilProperty data;

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

@ -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<Node>;
using ExtraPtr = RecordPtrT<Extra>;
@ -161,12 +162,8 @@ using NiPixelDataPtr = RecordPtrT<NiPixelData>;
using NiFloatDataPtr = RecordPtrT<NiFloatData>;
using NiColorDataPtr = RecordPtrT<NiColorData>;
using NiKeyframeDataPtr = RecordPtrT<NiKeyframeData>;
using NiTriShapeDataPtr = RecordPtrT<NiTriShapeData>;
using NiTriStripsDataPtr = RecordPtrT<NiTriStripsData>;
using NiLinesDataPtr = RecordPtrT<NiLinesData>;
using NiSkinInstancePtr = RecordPtrT<NiSkinInstance>;
using NiSourceTexturePtr = RecordPtrT<NiSourceTexture>;
using NiParticlesDataPtr = RecordPtrT<NiParticlesData>;
using NiPalettePtr = RecordPtrT<NiPalette>;
using NiParticleModifierPtr = RecordPtrT<NiParticleModifier>;
using NiBoolDataPtr = RecordPtrT<NiBoolData>;
@ -174,12 +171,17 @@ using NiSkinPartitionPtr = RecordPtrT<NiSkinPartition>;
using NiFloatInterpolatorPtr = RecordPtrT<NiFloatInterpolator>;
using NiPoint3InterpolatorPtr = RecordPtrT<NiPoint3Interpolator>;
using NiTransformInterpolatorPtr = RecordPtrT<NiTransformInterpolator>;
using BSShaderTextureSetPtr = RecordPtrT<BSShaderTextureSet>;
using NiGeometryDataPtr = RecordPtrT<NiGeometryData>;
using BSShaderPropertyPtr = RecordPtrT<BSShaderProperty>;
using NiAlphaPropertyPtr = RecordPtrT<NiAlphaProperty>;
using NodeList = RecordListT<Node>;
using PropertyList = RecordListT<Property>;
using ExtraList = RecordListT<Extra>;
using NiSourceTextureList = RecordListT<NiSourceTexture>;
using NiFloatInterpolatorList = RecordListT<NiFloatInterpolator>;
using NiTriStripsDataList = RecordListT<NiTriStripsData>;
} // Namespace
#endif

@ -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<int>(data.vertices.size()));
mesh.preallocateIndices(static_cast<int>(data.triangles.size()));
const std::vector<osg::Vec3f> &vertices = data.vertices;
const std::vector<unsigned short> &triangles = data.triangles;
mesh.preallocateVertices(static_cast<int>(vertices.size()));
mesh.preallocateIndices(static_cast<int>(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<osg::Vec3f> &vertices = data.vertices;
const std::vector<std::vector<unsigned short>> &strips = data.strips;
if (vertices.empty() || strips.empty())
return;
mesh.preallocateVertices(static_cast<int>(vertices.size()));
int numTriangles = 0;
for (const std::vector<unsigned short>& 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<const Nif::NiTriShape*>(nifNode)->data.get(), transform);
else if (nifNode->recType == Nif::RC_NiTriStrips)
fillTriangleMesh(mesh, static_cast<const Nif::NiTriStrips*>(nifNode)->data.get(), transform);
if (geometry->recType == Nif::RC_NiTriShape || geometry->recType == Nif::RC_BSLODTriShape)
fillTriangleMesh(mesh, static_cast<const Nif::NiTriShapeData&>(geometry->data.get()), transform);
else if (geometry->recType == Nif::RC_NiTriStrips)
fillTriangleMesh(mesh, static_cast<const Nif::NiTriStripsData&>(geometry->data.get()), transform);
}
}
@ -145,12 +142,12 @@ osg::ref_ptr<Resource::BulletShape> 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<btCompoundShape> compound (new btCompoundShape);
std::unique_ptr<btBoxShape> boxShape(new btBoxShape(halfExtents));
std::unique_ptr<btBoxShape> 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<const Nif::NiGeometry*>(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<const Nif::NiTriShape*>(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<const Nif::NiTriShapeData*>(niGeometry->data.getPtr());
if (data->triangles.empty())
return;
}
else
else if (niGeometry->recType == Nif::RC_NiTriStrips)
{
const Nif::NiTriStrips* shape = static_cast<const Nif::NiTriStrips*>(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<const Nif::NiTriStripsData*>(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<btTriangleMesh> childMesh(new btTriangleMesh);
fillTriangleMesh(*childMesh, nifNode);
fillTriangleMesh(*childMesh, niGeometry);
std::unique_ptr<Resource::TriangleMeshShape> 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);
}
}

@ -66,7 +66,9 @@ namespace NifOsg
std::conjunction_v<
std::disjunction<
std::is_same<ValueT, float>,
std::is_same<ValueT, osg::Vec3f>
std::is_same<ValueT, osg::Vec3f>,
std::is_same<ValueT, bool>,
std::is_same<ValueT, osg::Vec4f>
>,
std::is_same<decltype(T::defaultVal), ValueT>
>,

@ -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<const Nif::NiGeometry*>(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<const Nif::NiGeometry*>(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<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);
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<const Nif::NiParticles*>(nifNode);
if (particleNode->data.empty())
auto particleNode = static_cast<const Nif::NiParticles*>(nifNode);
if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData)
return;
const Nif::NiParticlesData* particledata = particleNode->data.getPtr();
auto particledata = static_cast<const Nif::NiParticlesData*>(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<unsigned int>& boundTextures, int animflags)
{
const Nif::NiGeometryData* niGeometryData = nullptr;
if (nifNode->recType == Nif::RC_NiTriShape)
const Nif::NiGeometry* niGeometry = static_cast<const Nif::NiGeometry*>(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<const Nif::NiTriShape*>(nifNode);
if (!triShape->data.empty())
{
const Nif::NiTriShapeData* data = triShape->data.getPtr();
niGeometryData = static_cast<const Nif::NiGeometryData*>(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<const Nif::NiTriShapeData*>(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<const Nif::NiTriStrips*>(nifNode);
if (!triStrips->data.empty())
if (niGeometryData->recType != Nif::RC_NiTriStripsData)
return;
auto data = static_cast<const Nif::NiTriStripsData*>(niGeometryData);
bool hasGeometry = false;
for (const auto& strip : data->strips)
{
const Nif::NiTriStripsData* data = triStrips->data.getPtr();
niGeometryData = static_cast<const Nif::NiGeometryData*>(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<const Nif::NiLines*>(nifNode);
if (!lines->data.empty())
{
const Nif::NiLinesData* data = lines->data.getPtr();
niGeometryData = static_cast<const Nif::NiGeometryData*>(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<const Nif::NiLinesData*>(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<const Nif::Property*> 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<unsigned int>& boundTextures, int animflags)
{
assert(isTypeGeometry(nifNode->recType));
osg::ref_ptr<osg::Drawable> drawable;
osg::ref_ptr<osg::Geometry> 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<osg::Drawable> 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<osg::Geometry> geometry (new osg::Geometry);
handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags);
if (geometry->empty())
return;
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
rig->setSourceGeometry(geometry);
rig->setName(nifNode->name);
@ -1481,6 +1496,17 @@ namespace NifOsg
return image;
}
osg::ref_ptr<osg::TexEnvCombine> createEmissiveTexEnv()
{
osg::ref_ptr<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);
return texEnv;
}
void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector<unsigned int>& 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)
{

@ -14,10 +14,10 @@
namespace Nif
{
class NiGravity;
class NiPlanarCollider;
class NiSphericalCollider;
class NiColorData;
struct NiGravity;
struct NiPlanarCollider;
struct NiSphericalCollider;
struct NiColorData;
}
namespace NifOsg

@ -10,7 +10,7 @@ namespace Resource
mStartTime(0.0f)
{
const osgAnimation::ChannelList& channels = anim.getChannels();
for (const osg::ref_ptr<osgAnimation::Channel> 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<osgAnimation::Channel> channel: mChannels)
for (const auto& channel: mChannels)
{
channel->update(time, 1.0f, 0);
}

@ -20,8 +20,7 @@ BulletShape::BulletShape()
BulletShape::BulletShape(const BulletShape &copy, const osg::CopyOp &copyop)
: 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<const BulletShape> source)
: BulletShape()
, mSource(source)
{
mCollisionBoxHalfExtents = source->mCollisionBoxHalfExtents;
mCollisionBoxTranslate = source->mCollisionBoxTranslate;
mCollisionBox = source->mCollisionBox;
mAnimatedShapes = source->mAnimatedShapes;

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

@ -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<SceneUtil::OsgAnimationController> 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<osgAnimation::Channel> 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;

@ -31,7 +31,7 @@ namespace SceneUtil
void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt)
{
const osgAnimation::ChannelList& channels = mAnimation->getChannels();
for (const osg::ref_ptr<osgAnimation::Channel> 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<osgAnimation::UpdateMatrixTransform*>(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<Resource::Animation> mergedAnimationTrack : mMergedAnimationTracks)
for (const auto& mergedAnimationTrack : mMergedAnimationTracks)
{
if (mergedAnimationTrack->getName() != animationName) continue;
const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels();
for (const osg::ref_ptr<osgAnimation::Channel> 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<osgAnimation::MatrixLinearSampler*> (channel->getSampler()) )
{
@ -150,7 +150,7 @@ namespace SceneUtil
void OsgAnimationController::update(float time, std::string animationName)
{
for (const osg::ref_ptr<Resource::Animation> 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<Resource::Animation> mergedAnimationTrack : mMergedAnimationTracks)
for (const auto& mergedAnimationTrack : mMergedAnimationTracks)
{
if (!mLinker.valid()) mLinker = new LinkVisitor();
mLinker->setAnimation(mergedAnimationTrack);

@ -33,7 +33,7 @@ namespace Gui
MyGUI::IntSize AutoSizedTextBox::getRequestedSize()
{
return getTextSize();
return getCaption().empty() ? MyGUI::IntSize{0, 0} : getTextSize();
}
void AutoSizedTextBox::setCaption(const MyGUI::UString& _value)

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

@ -267,6 +267,12 @@
<Property key="TextAlign" value="HCenter Top"/>
</Widget>
</Widget>
<Widget type="AutoSizedTextBox" skin="SandText" position="0 0 0 0" align="Left Top" name="LevelDetailText">
<Property key="AutoResize" value="true"/>
<Property key="MultiLine" value="true"/>
<Property key="Shrink" value="true"/>
</Widget>
</Widget>
<!-- Birthsign tooltip -->

@ -724,6 +724,16 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="graphicHerbalismCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable graphic herbalism</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">

Loading…
Cancel
Save