1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-19 23:23:52 +00:00

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

This commit is contained in:
Mads Buvik Sandvei 2020-12-18 20:50:58 +01:00
commit 3e82cae500
72 changed files with 1509 additions and 591 deletions

View file

@ -49,6 +49,7 @@ Programmers
Cédric Mocquillon Cédric Mocquillon
Chris Boyce (slothlife) Chris Boyce (slothlife)
Chris Robinson (KittyCat) Chris Robinson (KittyCat)
Coleman Smith (olcoal)
Cory F. Cohen (cfcohen) Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam) Cris Mihalache (Mirceam)
crussell187 crussell187
@ -122,7 +123,6 @@ Programmers
Lordrea Lordrea
Łukasz Gołębiewski (lukago) Łukasz Gołębiewski (lukago)
Lukasz Gromanowski (lgro) Lukasz Gromanowski (lgro)
Manuel Edelmann (vorenon)
Marc Bouvier (CramitDeFrog) Marc Bouvier (CramitDeFrog)
Marcin Hulist (Gohan) Marcin Hulist (Gohan)
Mark Siewert (mark76) Mark Siewert (mark76)

View file

@ -1,6 +1,7 @@
0.47.0 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 #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 #1952: Incorrect particle lighting
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong 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 #5644: Summon effects running on the player during game initialization cause crashes
Bug #5656: Sneaking characters block hits while standing Bug #5656: Sneaking characters block hits while standing
Bug #5661: Region sounds don't play at the right interval 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 #5688: Water shader broken indoors with enable indoor shadows = false
Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5695: ExplodeSpell for actors doesn't target the ground
Bug #5703: OpenMW-CS menu system crashing on XFCE 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 #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 #2386: Distant Statics in the form of Object Paging
Feature #2404: Levelled List can not be placed into a container 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 #4894: Consider actors as obstacles for pathfinding
Feature #5043: Head Bobbing Feature #5043: Head Bobbing
Feature #5199: Improve Scene Colors
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher 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 #5362: Show the soul gems' trapped soul in count dialog
Feature #5445: Handle NiLines Feature #5445: Handle NiLines
@ -93,6 +99,7 @@
Feature #5649: Skyrim SE compressed BSA format support Feature #5649: Skyrim SE compressed BSA format support
Feature #5672: Make stretch menu background configuration more accessible Feature #5672: Make stretch menu background configuration more accessible
Feature #5692: Improve spell/magic item search to factor in magic effect names 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 #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation Task #5520: Improve cell name autocompleter implementation

View file

@ -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) - 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: 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) - 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) - Flicker and crashing on XFCE4 fixed (#5703)
Miscellaneous: Miscellaneous:

View file

@ -153,6 +153,7 @@ bool Launcher::AdvancedPage::loadSettings()
if (showOwnedIndex >= 0 && showOwnedIndex <= 3) if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
showOwnedComboBox->setCurrentIndex(showOwnedIndex); showOwnedComboBox->setCurrentIndex(showOwnedIndex);
loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
} }
// Bug fixes // Bug fixes
@ -279,6 +280,7 @@ void Launcher::AdvancedPage::saveSettings()
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
} }
// Bug fixes // Bug fixes

View file

@ -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."). setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.").
setRange(10, 10000); setRange(10, 10000);
declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1); 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"); declareCategory ("Tooltips");
declareBool ("scene", "Show Tooltips in 3D scenes", true); declareBool ("scene", "Show Tooltips in 3D scenes", true);

View file

@ -593,56 +593,33 @@ namespace CSMWorld
} }
else if (type == UniversalId::Type_Clothing) 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(); auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(record).get();
std::vector<ESM::PartReferenceType> parts;
if (clothing.mData.mType == ESM::Clothing::Robe) if (clothing.mData.mType == ESM::Clothing::Robe)
{ {
auto reservedList = std::vector<ESM::PartReference>(); parts = {
ESM::PartReference pr;
pr.mMale = "";
pr.mFemale = "";
ESM::PartReferenceType parts[] = {
ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, 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_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) else if (clothing.mData.mType == ESM::Clothing::Skirt)
{ {
auto reservedList = std::vector<ESM::PartReference>(); parts = {ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg};
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);
} }
std::vector<ESM::PartReference> reservedList;
for (const auto& p : parts)
{
ESM::PartReference pr;
pr.mPart = p;
reservedList.emplace_back(pr);
}
int priority = parts.size();
addParts(clothing.mParts.mParts, priority); addParts(clothing.mParts.mParts, priority);
addParts(reservedList, priority);
// Changing parts could affect what is picked for rendering // Changing parts could affect what is picked for rendering
data->addOtherDependency(itemId); data->addOtherDependency(itemId);

View file

@ -64,10 +64,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
// ignore content file number // ignore content file number
std::map<ESM::RefNum, std::string>::iterator iter = cache.begin(); 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) for (; iter != cache.end(); ++iter)
{ {
if (ref.mRefNum.mIndex == iter->first.mIndex) if (thisIndex == iter->first.mIndex)
break; break;
} }

View file

@ -151,9 +151,9 @@ void CSVRender::CellArrow::buildShape()
osg::Vec4Array *colours = new osg::Vec4Array; osg::Vec4Array *colours = new osg::Vec4Array;
for (int i=0; i<6; ++i) 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) 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); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX);

View file

@ -70,7 +70,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f)
setLayout(layout); setLayout(layout);
mView->getCamera()->setGraphicsContext(window); 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) ); mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; 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() ); 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 // we handle lighting manually
mView->setLightingMode(osgViewer::View::NO_LIGHT); mView->setLightingMode(osgViewer::View::NO_LIGHT);
@ -250,6 +268,79 @@ SceneWidget::~SceneWidget()
mResourceSystem->releaseGLObjects(SDLUtil::GraphicsWindowSDL2::findContext(*mView)->getState()); 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) void SceneWidget::setLighting(Lighting *lighting)
{ {
if (mLighting) if (mLighting)
@ -277,12 +368,59 @@ void SceneWidget::setAmbient(const osg::Vec4f& ambient)
void SceneWidget::selectLightingMode (const std::string& mode) void SceneWidget::selectLightingMode (const std::string& mode)
{ {
if (mode=="day") QColor backgroundColour;
setLighting (&mLightingDay); QColor gradientColour;
else if (mode=="night") if (mode == "day")
setLighting (&mLightingNight); {
else if (mode=="bright") backgroundColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor();
setLighting (&mLightingBright); 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) CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent)

View file

@ -100,10 +100,15 @@ namespace CSVRender
void mouseMoveEvent (QMouseEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override;
void wheelEvent (QWheelEvent *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; std::shared_ptr<Resource::ResourceSystem> mResourceSystem;
Lighting* mLighting; Lighting* mLighting;
osg::ref_ptr<osg::Camera> mGradientCamera;
osg::Vec4f mDefaultAmbient; osg::Vec4f mDefaultAmbient;
bool mHasDefaultAmbient; bool mHasDefaultAmbient;
bool mIsExterior; bool mIsExterior;

View file

@ -135,7 +135,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
if (variables.count ("help")) if (variables.count ("help"))
{ {
std::cout << desc << std::endl; getRawStdout() << desc << std::endl;
return false; return false;
} }
@ -144,7 +144,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
cfgMgr.readConfiguration(variables, desc, true); cfgMgr.readConfiguration(variables, desc, true);
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapePath>().mPath.string()); 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; return false;
} }

View file

@ -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; 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 ///< @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 scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z, virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z,

View file

@ -1,10 +1,12 @@
#include "loadingscreen.hpp" #include "loadingscreen.hpp"
#include <array> #include <array>
#include <condition_variable>
#include <osgViewer/Viewer> #include <osgViewer/Viewer>
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/Version>
#include <MyGUI_RenderManager.h> #include <MyGUI_RenderManager.h>
#include <MyGUI_ScrollBar.h> #include <MyGUI_ScrollBar.h>
@ -43,6 +45,8 @@ namespace MWGui
, mNestedLoadingCount(0) , mNestedLoadingCount(0)
, mProgress(0) , mProgress(0)
, mShowWallpaper(true) , mShowWallpaper(true)
, mOldCallback(nullptr)
, mHasCallback(false)
{ {
mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize());
@ -136,24 +140,54 @@ namespace MWGui
{ {
public: public:
CopyFramebufferToTextureCallback(osg::Texture2D* texture) CopyFramebufferToTextureCallback(osg::Texture2D* texture)
: mTexture(texture) : mOneshot(true)
, oneshot(true) , mTexture(texture)
{ {
} }
void operator () (osg::RenderInfo& renderInfo) const override void operator () (osg::RenderInfo& renderInfo) const override
{ {
if (!oneshot) {
return; std::unique_lock<std::mutex> lock(mMutex);
oneshot = false; mOneshot = false;
}
mSignal.notify_all();
int w = renderInfo.getCurrentCamera()->getViewport()->width(); int w = renderInfo.getCurrentCamera()->getViewport()->width();
int h = renderInfo.getCurrentCamera()->getViewport()->height(); int h = renderInfo.getCurrentCamera()->getViewport()->height();
mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); 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: private:
mutable bool mOneshot;
mutable std::mutex mMutex;
mutable std::condition_variable mSignal;
osg::ref_ptr<osg::Texture2D> mTexture; osg::ref_ptr<osg::Texture2D> mTexture;
mutable bool oneshot;
}; };
class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback
@ -322,9 +356,20 @@ namespace MWGui
mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture)); mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture));
} }
// Notice that the next time this is called, the current CopyFramebufferToTextureCallback will be deleted if (!mCopyFramebufferToTextureCallback)
// 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)); 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->setBackgroundImage("");
mBackgroundImage->setVisible(false); mBackgroundImage->setVisible(false);
@ -367,6 +412,21 @@ namespace MWGui
mViewer->renderingTraversals(); mViewer->renderingTraversals();
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); 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(); mLastRenderTime = mTimer.time_m();
} }

View file

@ -3,6 +3,7 @@
#include <memory> #include <memory>
#include <osg/Camera>
#include <osg/Timer> #include <osg/Timer>
#include <osg/ref_ptr> #include <osg/ref_ptr>
@ -28,6 +29,7 @@ namespace Resource
namespace MWGui namespace MWGui
{ {
class BackgroundImage; class BackgroundImage;
class CopyFramebufferToTextureCallback;
class LoadingScreen : public WindowBase, public Loading::Listener class LoadingScreen : public WindowBase, public Loading::Listener
{ {
@ -84,6 +86,9 @@ namespace MWGui
std::vector<std::string> mSplashScreens; std::vector<std::string> mSplashScreens;
osg::ref_ptr<osg::Texture2D> mTexture; 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; std::unique_ptr<MyGUI::ITexture> mGuiTexture;
void changeWallpaper(); void changeWallpaper();

View file

@ -339,6 +339,15 @@ namespace MWGui
{ {
int max = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iLevelUpTotal")->mValue.getInteger(); int max = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iLevelUpTotal")->mValue.getInteger();
getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); 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("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress()));
levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max));
levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/"

View file

@ -322,6 +322,11 @@ void MWMechanics::NpcStats::updateHealth()
setHealth(floor(0.5f * (strength + endurance))); setHealth(floor(0.5f * (strength + endurance)));
} }
int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const
{
return mSkillIncreases[attribute];
}
int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
{ {
int num = mSkillIncreases[attribute]; int num = mSkillIncreases[attribute];

View file

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

View file

@ -20,7 +20,7 @@ namespace MWPhysics
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler) Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false) : 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) , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
, mInternalCollisionMode(true) , mInternalCollisionMode(true)
, mExternalCollisionMode(true) , mExternalCollisionMode(true)
@ -76,6 +76,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
updateScale(); updateScale();
resetPosition(); resetPosition();
addCollisionMask(getCollisionMask()); addCollisionMask(getCollisionMask());
updateCollisionObjectPosition();
} }
Actor::~Actor() Actor::~Actor()
@ -120,6 +121,8 @@ int Actor::getCollisionMask() const
void Actor::updatePositionUnsafe() void Actor::updatePositionUnsafe()
{ {
if (!mWorldPositionChanged && mWorldPosition != mPtr.getRefData().getPosition().asVec3())
mWorldPositionChanged = true;
mWorldPosition = mPtr.getRefData().getPosition().asVec3(); mWorldPosition = mPtr.getRefData().getPosition().asVec3();
} }
@ -137,7 +140,9 @@ osg::Vec3f Actor::getWorldPosition() const
void Actor::setSimulationPosition(const osg::Vec3f& position) void Actor::setSimulationPosition(const osg::Vec3f& position)
{ {
mSimulationPosition = position; if (!mResetSimulation)
mSimulationPosition = position;
mResetSimulation = false;
} }
osg::Vec3f Actor::getSimulationPosition() const osg::Vec3f Actor::getSimulationPosition() const
@ -153,6 +158,7 @@ void Actor::updateCollisionObjectPositionUnsafe()
mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition)); mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation)); mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation));
mCollisionObject->setWorldTransform(mLocalTransform); mCollisionObject->setWorldTransform(mLocalTransform);
mWorldPositionChanged = false;
} }
void Actor::updateCollisionObjectPosition() void Actor::updateCollisionObjectPosition()
@ -167,18 +173,20 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
return Misc::Convert::toOsg(mLocalTransform.getOrigin()); 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); std::scoped_lock lock(mPositionMutex);
mPreviousPosition = mPosition; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
mPosition = position; mPreviousPosition = mPosition + mPositionOffset;
mPosition = position + mPositionOffset;
mPositionOffset = osg::Vec3f();
return hasChanged;
} }
void Actor::adjustPosition(const osg::Vec3f& offset) void Actor::adjustPosition(const osg::Vec3f& offset)
{ {
std::scoped_lock lock(mPositionMutex); std::scoped_lock lock(mPositionMutex);
mPosition += offset; mPositionOffset += offset;
mPreviousPosition += offset;
} }
void Actor::resetPosition() void Actor::resetPosition()
@ -188,7 +196,9 @@ void Actor::resetPosition()
mPreviousPosition = mWorldPosition; mPreviousPosition = mWorldPosition;
mPosition = mWorldPosition; mPosition = mWorldPosition;
mSimulationPosition = mWorldPosition; mSimulationPosition = mWorldPosition;
updateCollisionObjectPositionUnsafe(); mStandingOnPtr = nullptr;
mWorldPositionChanged = false;
mResetSimulation = true;
} }
osg::Vec3f Actor::getPosition() const osg::Vec3f Actor::getPosition() const

View file

@ -90,8 +90,9 @@ namespace MWPhysics
/** /**
* Store the current position into mPreviousPosition, then move to this position. * 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 resetPosition();
void adjustPosition(const osg::Vec3f& offset); void adjustPosition(const osg::Vec3f& offset);
@ -177,6 +178,9 @@ namespace MWPhysics
osg::Vec3f mSimulationPosition; osg::Vec3f mSimulationPosition;
osg::Vec3f mPosition; osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition; osg::Vec3f mPreviousPosition;
osg::Vec3f mPositionOffset;
bool mWorldPositionChanged;
bool mResetSimulation;
btTransform mLocalTransform; btTransform mLocalTransform;
mutable std::mutex mPositionMutex; mutable std::mutex mPositionMutex;

View file

@ -30,12 +30,9 @@ namespace MWPhysics
return btScalar(1); return btScalar(1);
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer()); auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
const MWWorld::Ptr target = targetHolder->getPtr(); 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->isValidTarget(target))
if (projectileHolder->getCaster() != target)
{
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
return btScalar(1); return btScalar(1);
}
} }
btVector3 hitNormalWorld; btVector3 hitNormalWorld;

View file

@ -24,6 +24,10 @@ namespace MWPhysics
{ {
if (rayResult.m_collisionObject == mMe) if (rayResult.m_collisionObject == mMe)
return 1.f; return 1.f;
if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject())
return 1.f;
if (!mTargets.empty()) if (!mTargets.empty())
{ {
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))

View file

@ -100,15 +100,6 @@ namespace
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{ {
const float interpolationFactor = timeAccum / 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); return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
} }
@ -213,31 +204,31 @@ namespace MWPhysics
thread.join(); 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. // This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run. // While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock lock(mSimulationMutex); std::unique_lock lock(mSimulationMutex);
for (auto& data : actorsData) mMovedActors.clear();
data.updatePosition();
// start by finishing previous background computation // start by finishing previous background computation
if (mNumThreads != 0) if (mNumThreads != 0)
{ {
for (auto& data : mActorsFrameData) for (auto& data : mActorsFrameData)
{ {
// Ignore actors that were deleted while the background thread was running // Only return actors that are still part of the scene
if (!data.mActor.lock()) if (std::any_of(actorsData.begin(), actorsData.end(), [&data](const auto& newFrameData) { return data.mActorRaw->getPtr() == newFrameData.mActorRaw->getPtr(); }))
continue; {
updateMechanics(data);
updateMechanics(data); // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values
if (mAdvanceSimulation) if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn); data.mActorRaw->setStandingOnPtr(data.mStandingOn);
data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt));
if (mMovementResults.find(data.mPtr) != mMovementResults.end()) mMovedActors.emplace_back(data.mActorRaw->getPtr());
data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]); }
} }
if (mFrameNumber == frameNumber - 1) if (mFrameNumber == frameNumber - 1)
@ -252,6 +243,8 @@ namespace MWPhysics
} }
// init // init
for (auto& data : actorsData)
data.updatePosition();
mRemainingSteps = numSteps; mRemainingSteps = numSteps;
mTimeAccum = timeAccum; mTimeAccum = timeAccum;
mActorsFrameData = std::move(actorsData); mActorsFrameData = std::move(actorsData);
@ -266,52 +259,28 @@ namespace MWPhysics
if (mNumThreads == 0) if (mNumThreads == 0)
{ {
mMovementResults.clear();
syncComputation(); syncComputation();
return mMovedActors;
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);
}
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(); lock.unlock();
mHasJob.notify_all(); 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); std::unique_lock lock(mSimulationMutex);
mMovementResults.clear(); mMovedActors.clear();
mPreviousMovementResults.clear();
mActorsFrameData.clear(); mActorsFrameData.clear();
for (const auto& [_, actor] : actors) for (const auto& [_, actor] : actors)
{ {
actor->resetPosition(); actor->resetPosition();
actor->setStandingOnPtr(nullptr); actor->setSimulationPosition(actor->getWorldPosition()); // resetPosition skip next simulation, now we need to "consume" it
mMovementResults[actor->getPtr()] = actor->getWorldPosition(); actor->updateCollisionObjectPosition();
mMovedActors.emplace_back(actor->getPtr());
} }
return mMovementResults; return mMovedActors;
} }
void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
@ -379,18 +348,18 @@ namespace MWPhysics
mCollisionWorld->removeCollisionObject(collisionObject); 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));
}
else
{ {
std::unique_lock lock(mCollisionWorldMutex); std::unique_lock lock(mCollisionWorldMutex);
updatePtrAabb(ptr); updatePtrAabb(ptr);
} }
else
{
std::unique_lock lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
}
} }
bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2) bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2)
@ -493,7 +462,6 @@ namespace MWPhysics
{ {
auto& actorData = mActorsFrameData[job]; auto& actorData = mActorsFrameData[job];
handleFall(actorData, mAdvanceSimulation); handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
} }
} }
@ -511,9 +479,7 @@ namespace MWPhysics
{ {
if(const auto actor = actorData.mActor.lock()) if(const auto actor = actorData.mActor.lock())
{ {
bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition(); if (actor->setPosition(actorData.mPosition))
actorData.mActorRaw->setPosition(actorData.mPosition);
if (positionChanged)
{ {
actor->updateCollisionObjectPosition(); actor->updateCollisionObjectPosition();
mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
@ -550,8 +516,11 @@ namespace MWPhysics
for (auto& actorData : mActorsFrameData) for (auto& actorData : mActorsFrameData)
{ {
handleFall(actorData, mAdvanceSimulation); handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt); actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt));
updateMechanics(actorData); updateMechanics(actorData);
mMovedActors.emplace_back(actorData.mActorRaw->getPtr());
if (mAdvanceSimulation)
actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn);
} }
} }
} }

View file

@ -32,9 +32,9 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements /// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions /// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor /// @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 // Thread safe wrappers
void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; 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 setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask);
void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask); void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask);
void removeCollisionObject(btCollisionObject* collisionObject); 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); bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
private: private:
@ -60,8 +60,7 @@ namespace MWPhysics
std::unique_ptr<WorldFrameData> mWorldFrameData; std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<ActorFrameData> mActorsFrameData; std::vector<ActorFrameData> mActorsFrameData;
PtrPositionList mMovementResults; std::vector<MWWorld::Ptr> mMovedActors;
PtrPositionList mPreviousMovementResults;
const float mPhysicsDt; const float mPhysicsDt;
float mTimeAccum; float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld; std::shared_ptr<btCollisionWorld> mCollisionWorld;

View file

@ -437,7 +437,7 @@ namespace MWPhysics
ActorMap::iterator found = mActors.find(ptr); ActorMap::iterator found = mActors.find(ptr);
if (found == mActors.end()) if (found == mActors.end())
return ptr.getRefData().getPosition().asVec3(); return ptr.getRefData().getPosition().asVec3();
found->second->resetPosition(); resetPosition(ptr);
return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight);
} }
@ -537,6 +537,13 @@ namespace MWPhysics
if (actor->getStandingOnPtr() == old) if (actor->getStandingOnPtr() == old)
actor->setStandingOnPtr(updated); actor->setStandingOnPtr(updated);
} }
for (auto& [_, projectile] : mProjectiles)
{
if (projectile->getCaster() == old)
projectile->setCaster(updated);
}
} }
Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr) 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) void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh)
{ {
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh); osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
// Try to get shape from basic model as fallback for creatures // 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); const std::string fallbackModel = ptr.getClass().getModel(ptr);
if (fallbackModel != mesh) if (fallbackModel != mesh)
@ -676,6 +694,8 @@ namespace MWPhysics
if (found != mActors.end()) if (found != mActors.end())
{ {
bool cmode = found->second->getCollisionMode(); bool cmode = found->second->getCollisionMode();
if (cmode)
resetPosition(found->first);
cmode = !cmode; cmode = !cmode;
found->second->enableCollisionMode(cmode); found->second->enableCollisionMode(cmode);
// NB: Collision body isn't disabled for vanilla TCL compatibility // NB: Collision body isn't disabled for vanilla TCL compatibility
@ -704,7 +724,7 @@ namespace MWPhysics
mMovementQueue.clear(); 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; mTimeAccum += dt;
@ -923,7 +943,6 @@ namespace MWPhysics
void ActorFrameData::updatePosition() void ActorFrameData::updatePosition()
{ {
mActorRaw->updatePosition(); mActorRaw->updatePosition();
mOrigin = mActorRaw->getSimulationPosition();
mPosition = mActorRaw->getPosition(); mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface) if (mMoveToWaterSurface)
{ {

View file

@ -50,8 +50,6 @@ class btVector3;
namespace MWPhysics namespace MWPhysics
{ {
using PtrPositionList = std::map<MWWorld::Ptr, osg::Vec3f>;
class HeightField; class HeightField;
class Object; class Object;
class Actor; class Actor;
@ -99,7 +97,6 @@ namespace MWPhysics
float mOldHeight; float mOldHeight;
float mFallHeight; float mFallHeight;
osg::Vec3f mMovement; osg::Vec3f mMovement;
osg::Vec3f mOrigin;
osg::Vec3f mPosition; osg::Vec3f mPosition;
ESM::Position mRefpos; ESM::Position mRefpos;
}; };
@ -147,6 +144,7 @@ namespace MWPhysics
void updateScale (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr);
void updateRotation (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr);
void updatePosition (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); 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); void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);
/// Apply all queued movements, then clear the list. /// 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. /// Clear the queued movements list without applying.
void clearQueuedMovement(); void clearQueuedMovement();

View file

@ -55,7 +55,7 @@ Projectile::~Projectile()
void Projectile::commitPositionChange() void Projectile::commitPositionChange()
{ {
std::unique_lock<std::mutex> lock(mPositionMutex); std::scoped_lock lock(mMutex);
if (mTransformUpdatePending) if (mTransformUpdatePending)
{ {
mCollisionObject->setWorldTransform(mLocalTransform); mCollisionObject->setWorldTransform(mLocalTransform);
@ -65,7 +65,7 @@ void Projectile::commitPositionChange()
void Projectile::setPosition(const osg::Vec3f &position) 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)); mLocalTransform.setOrigin(Misc::Convert::toBullet(position));
mTransformUpdatePending = true; mTransformUpdatePending = true;
} }
@ -74,7 +74,7 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
{ {
if (!mActive.load(std::memory_order_acquire)) if (!mActive.load(std::memory_order_acquire))
return; return;
std::unique_lock<std::mutex> lock(mPositionMutex); std::scoped_lock lock(mMutex);
mHitTarget = target; mHitTarget = target;
mHitPosition = pos; mHitPosition = pos;
mHitNormal = normal; mHitNormal = normal;
@ -86,4 +86,46 @@ void Projectile::activate()
assert(!mActive); assert(!mActive);
mActive.store(true, std::memory_order_release); 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;
}
} }

View file

@ -62,7 +62,8 @@ namespace MWPhysics
return mHitTarget; return mHitTarget;
} }
MWWorld::Ptr getCaster() const { return mCaster; } MWWorld::Ptr getCaster() const;
void setCaster(MWWorld::Ptr caster);
osg::Vec3f getHitPos() const osg::Vec3f getHitPos() const
{ {
@ -73,6 +74,9 @@ namespace MWPhysics
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
void activate(); void activate();
void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
bool isValidTarget(const MWWorld::Ptr& target) const;
private: private:
std::unique_ptr<btCollisionShape> mShape; std::unique_ptr<btCollisionShape> mShape;
@ -87,7 +91,9 @@ namespace MWPhysics
btVector3 mHitPosition; btVector3 mHitPosition;
btVector3 mHitNormal; btVector3 mHitNormal;
mutable std::mutex mPositionMutex; std::vector<MWWorld::Ptr> mValidTargets;
mutable std::mutex mMutex;
osg::Vec3f mPosition; osg::Vec3f mPosition;

View file

@ -783,8 +783,6 @@ namespace MWRender
NodeMap::const_iterator found = nodeMap.find("bip01"); NodeMap::const_iterator found = nodeMap.find("bip01");
if (found == nodeMap.end()) if (found == nodeMap.end())
found = nodeMap.find("root bone"); found = nodeMap.find("root bone");
if (found == nodeMap.end())
found = nodeMap.find("root");
if (found != nodeMap.end()) if (found != nodeMap.end())
mAccumRoot = found->second; mAccumRoot = found->second;

View file

@ -32,11 +32,7 @@ namespace MWScript
std::vector<MWWorld::Ptr> actors; std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
for (auto& actor : actors) for (auto& actor : actors)
{ MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff);
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
actorPos += diff;
MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z());
}
} }
template<class R> template<class R>
@ -727,14 +723,12 @@ namespace MWScript
return; return;
osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; 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. // We should move actors, standing on moving object, too.
// This approach can be used to create elevators. // This approach can be used to create elevators.
moveStandingActors(ptr, diff); moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, 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()); Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
runtime.pop(); runtime.pop();
const float *objPos = ptr.getRefData().getPosition().pos;
osg::Vec3f diff; osg::Vec3f diff;
if (axis == "x") if (axis == "x")
diff.x() += movement; diff.x() = movement;
else if (axis == "y") else if (axis == "y")
diff.y() += movement; diff.y() = movement;
else if (axis == "z") else if (axis == "z")
diff.z() += movement; diff.z() = movement;
else else
return; return;
@ -771,7 +764,7 @@ namespace MWScript
// This approach can be used to create elevators. // This approach can be used to create elevators.
moveStandingActors(ptr, diff); moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, 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));
} }
}; };

View file

@ -401,7 +401,7 @@ namespace MWWorld
if (magicBoltState.mToDelete) if (magicBoltState.mToDelete)
continue; continue;
const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (!projectile->isActive()) if (!projectile->isActive())
continue; continue;
// If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. // 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; std::vector<MWWorld::Ptr> targetActors;
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
// Check for impact // Check for impact
// TODO: use a proper btRigidBody / btGhostObject? // TODO: use a proper btRigidBody / btGhostObject?
@ -483,7 +484,7 @@ namespace MWWorld
if (projectileState.mToDelete) if (projectileState.mToDelete)
continue; continue;
const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (!projectile->isActive()) if (!projectile->isActive())
continue; continue;
// gravity constant - must be way lower than the gravity affecting actors, since we're not // 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; std::vector<MWWorld::Ptr> targetActors;
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
// Check for impact // Check for impact
// TODO: use a proper btRigidBody / btGhostObject? // TODO: use a proper btRigidBody / btGhostObject?
@ -561,7 +563,7 @@ namespace MWWorld
const auto pos = projectile->getHitPos(); const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = projectileState.getCaster(); MWWorld::Ptr caster = projectileState.getCaster();
assert(target != caster); assert(target != caster);
if (!isValidTarget(caster, target)) if (!projectile->isValidTarget(target))
{ {
projectile->activate(); projectile->activate();
continue; continue;
@ -597,7 +599,7 @@ namespace MWWorld
const auto pos = projectile->getHitPos(); const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = magicBoltState.getCaster(); MWWorld::Ptr caster = magicBoltState.getCaster();
assert(target != caster); assert(target != caster);
if (!isValidTarget(caster, target)) if (!projectile->isValidTarget(target))
{ {
projectile->activate(); projectile->activate();
continue; continue;
@ -621,32 +623,6 @@ namespace MWWorld
mMagicBolts.end()); 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) void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
{ {
mParent->removeChild(state.mNode); mParent->removeChild(state.mNode);

View file

@ -132,8 +132,6 @@ namespace MWWorld
void moveProjectiles(float dt); void moveProjectiles(float dt);
void moveMagicBolts(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, 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 = ""); bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = "");
void update (State& state, float duration); void update (State& state, float duration);

View file

@ -1293,6 +1293,18 @@ namespace MWWorld
return moveObjectImp(ptr, x, y, z, true, moveToActive); 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) void World::scaleObject (const Ptr& ptr, float scale)
{ {
if (mPhysics->getActor(ptr)) if (mPhysics->getActor(ptr))
@ -1386,12 +1398,7 @@ namespace MWWorld
} }
moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z());
if (force) // force physics to use the new position mPhysics->resetPosition(ptr);
{
auto actor = mPhysics->getActor(ptr);
if(actor)
actor->resetPosition();
}
} }
void World::fixPosition() void World::fixPosition()
@ -1538,20 +1545,30 @@ namespace MWWorld
mProjectileManager->update(duration); 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(); mProjectileManager->processHits();
mDiscardMovements = false; mDiscardMovements = false;
for(const auto& [actor, position]: results) for(const auto& actor : results)
{ {
// Handle player last, in case a cell transition occurs // Handle player last, in case a cell transition occurs
if(actor != getPlayerPtr()) if(actor != getPlayerPtr())
{
auto* physactor = mPhysics->getActor(actor);
assert(physactor);
const auto position = physactor->getSimulationPosition();
moveObjectImp(actor, position.x(), position.y(), position.z(), false); 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()) 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() void World::updateNavigator()

View file

@ -385,6 +385,9 @@ namespace MWWorld
MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override;
///< @return an updated Ptr ///< @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; void scaleObject (const Ptr& ptr, float scale) override;
/// World rotates object, uses radians /// World rotates object, uses radians

View file

@ -1,5 +1,5 @@
find_package(GTest REQUIRED) find_package(GTest 1.10 REQUIRED)
find_package(GMock REQUIRED) find_package(GMock 1.10 REQUIRED)
if (GTEST_FOUND AND GMOCK_FOUND) if (GTEST_FOUND AND GMOCK_FOUND)
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})

View file

@ -15,7 +15,7 @@ namespace
struct DetourNavigatorRecastMeshObjectTest : Test struct DetourNavigatorRecastMeshObjectTest : Test
{ {
btBoxShape mBoxShape {btVector3(1, 2, 3)}; 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)}; btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)};
DetourNavigatorRecastMeshObjectTest() DetourNavigatorRecastMeshObjectTest()

View file

@ -3,6 +3,7 @@
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/esm/esmreader.hpp> #include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp> #include <components/esm/esmwriter.hpp>
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
@ -58,10 +59,10 @@ struct ContentFileTest : public ::testing::Test
boost::program_options::options_description desc("Allowed options"); boost::program_options::options_description desc("Allowed options");
desc.add_options() desc.add_options()
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) ("data", boost::program_options::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing())
("content", boost::program_options::value<std::vector<std::string> >()->default_value(std::vector<std::string>(), "") ("content", boost::program_options::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
("data-local", boost::program_options::value<std::string>()->default_value("")); ("data-local", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), ""));
boost::program_options::notify(variables); boost::program_options::notify(variables);
@ -69,12 +70,12 @@ struct ContentFileTest : public ::testing::Test
Files::PathContainer dataDirs, dataLocal; Files::PathContainer dataDirs, dataLocal;
if (!variables["data"].empty()) { 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()) { if (!local.empty()) {
dataLocal.push_back(Files::PathContainer::value_type(local)); dataLocal.push_back(local);
} }
mConfigurationManager.processPaths (dataDirs); mConfigurationManager.processPaths (dataDirs);
@ -85,7 +86,7 @@ struct ContentFileTest : public ::testing::Test
Files::Collections collections (dataDirs, true); 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) for (auto & contentFile : contentFiles)
mContentFiles.push_back(collections.getPath(contentFile)); mContentFiles.push_back(collections.getPath(contentFile));
} }

View file

@ -162,8 +162,8 @@ namespace Resource
{ {
return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape) return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape)
&& compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape) && compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape)
&& lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents && lhs.mCollisionBox.extents == rhs.mCollisionBox.extents
&& lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate && lhs.mCollisionBox.center == rhs.mCollisionBox.center
&& lhs.mAnimatedShapes == rhs.mAnimatedShapes; && lhs.mAnimatedShapes == rhs.mAnimatedShapes;
} }
@ -172,7 +172,8 @@ namespace Resource
return stream << "Resource::BulletShape {" return stream << "Resource::BulletShape {"
<< value.mCollisionShape << ", " << value.mCollisionShape << ", "
<< value.mAvoidCollisionShape << ", " << value.mAvoidCollisionShape << ", "
<< "osg::Vec3f {" << value.mCollisionBoxHalfExtents << "}" << ", " << "osg::Vec3f {" << value.mCollisionBox.extents << "}" << ", "
<< "osg::Vec3f {" << value.mCollisionBox.center << "}" << ", "
<< value.mAnimatedShapes << value.mAnimatedShapes
<< "}"; << "}";
} }
@ -258,14 +259,19 @@ namespace
value.isBone = false; value.isBone = false;
} }
void init(Nif::NiTriShape& value) void init(Nif::NiGeometry& value)
{ {
init(static_cast<Nif::Node&>(value)); init(static_cast<Nif::Node&>(value));
value.recType = Nif::RC_NiTriShape; value.data = Nif::NiGeometryDataPtr(nullptr);
value.data = Nif::NiTriShapeDataPtr(nullptr);
value.skin = Nif::NiSkinInstancePtr(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) void init(Nif::NiSkinInstance& value)
{ {
value.data = Nif::NiSkinDataPtr(nullptr); value.data = Nif::NiSkinDataPtr(nullptr);
@ -293,22 +299,22 @@ namespace
struct NifFileMock : Nif::File struct NifFileMock : Nif::File
{ {
MOCK_CONST_METHOD1(getRecord, Nif::Record* (std::size_t)); MOCK_METHOD(Nif::Record*, getRecord, (std::size_t), (const, override));
MOCK_CONST_METHOD0(numRecords, std::size_t ()); MOCK_METHOD(std::size_t, numRecords, (), (const, override));
MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t)); MOCK_METHOD(Nif::Record*, getRoot, (std::size_t), (const, override));
MOCK_CONST_METHOD0(numRoots, std::size_t ()); MOCK_METHOD(std::size_t, numRoots, (), (const, override));
MOCK_CONST_METHOD1(getString, std::string (uint32_t)); MOCK_METHOD(std::string, getString, (uint32_t), (const, override));
MOCK_METHOD1(setUseSkinning, void (bool)); MOCK_METHOD(void, setUseSkinning, (bool), (override));
MOCK_CONST_METHOD0(getUseSkinning, bool ()); MOCK_METHOD(bool, getUseSkinning, (), (const, override));
MOCK_CONST_METHOD0(getFilename, std::string ()); MOCK_METHOD(std::string, getFilename, (), (const, override));
MOCK_CONST_METHOD0(getVersion, unsigned int ()); MOCK_METHOD(unsigned int, getVersion, (), (const, override));
MOCK_CONST_METHOD0(getUserVersion, unsigned int ()); MOCK_METHOD(unsigned int, getUserVersion, (), (const, override));
MOCK_CONST_METHOD0(getBethVersion, unsigned int ()); MOCK_METHOD(unsigned int, getBethVersion, (), (const, override));
}; };
struct RecordMock : Nif::Record struct RecordMock : Nif::Record
{ {
MOCK_METHOD1(read, void (Nif::NIFStream *nif)); MOCK_METHOD(void, read, (Nif::NIFStream *nif), (override));
}; };
struct TestBulletNifLoader : Test struct TestBulletNifLoader : Test
@ -360,13 +366,15 @@ namespace
init(mNiStringExtraData2); init(mNiStringExtraData2);
init(mController); 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.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0)};
mNiTriShapeData.triangles = {0, 1, 2}; 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.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)};
mNiTriShapeData2.triangles = {0, 1, 2}; 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); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = 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<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape); std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
@ -458,8 +466,8 @@ namespace
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = 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<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape); std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
@ -488,8 +496,8 @@ namespace
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = 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<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape); std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
@ -523,8 +531,8 @@ namespace
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = 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<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape); std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
@ -558,8 +566,8 @@ namespace
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(4, 5, 6); expected.mCollisionBox.extents = osg::Vec3f(4, 5, 6);
expected.mCollisionBoxTranslate = 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<btBoxShape> box(new btBoxShape(btVector3(4, 5, 6)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape); std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release());
@ -581,8 +589,8 @@ namespace
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
EXPECT_EQ(*result, expected); EXPECT_EQ(*result, expected);
} }
@ -615,8 +623,8 @@ namespace
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
EXPECT_EQ(*result, expected); 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) 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)})); mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); 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) 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)})); mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));

View file

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

View file

@ -151,7 +151,13 @@ add_component_dir (fallback
fallback validate 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 add_component_dir (crashcatcher
crashcatcher crashcatcher
) )

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,13 @@
#include "debugging.hpp" #include "debugging.hpp"
#include <chrono> #include <chrono>
#include <memory>
#include <functional>
#include <components/crashcatcher/crashcatcher.hpp> #include <components/crashcatcher/crashcatcher.hpp>
#ifdef _WIN32 #ifdef _WIN32
# include <components/crashcatcher/windows_crashcatcher.hpp>
# undef WIN32_LEAN_AND_MEAN # undef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN
# include <windows.h> # 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) int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName)
{ {
#if defined _WIN32 #if defined _WIN32
(void)Debug::attachParentConsole(); (void)Debug::attachParentConsole();
#endif #endif
rawStdout = std::make_unique<std::ostream>(std::cout.rdbuf());
// Some objects used to redirect cout and cerr // Some objects used to redirect cout and cerr
// Scope must be here, so this still works inside the catch block for logging exceptions // 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 #endif
const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log";
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log";
boost::filesystem::ofstream logfile; boost::filesystem::ofstream logfile;
int ret = 0; int ret = 0;
@ -178,13 +188,18 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
std::cerr.rdbuf (&cerrsb); std::cerr.rdbuf (&cerrsb);
#endif #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 // install the crash handler as soon as possible. note that the log path
// does not depend on config being read. // does not depend on config being read.
crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string()); crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string());
#endif
ret = innerApplication(argc, argv); ret = innerApplication(argc, argv);
} }
catch (std::exception& e) catch (const std::exception& e)
{ {
#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix))
if (!isatty(fileno(stdin))) if (!isatty(fileno(stdin)))

View file

@ -135,6 +135,9 @@ namespace Debug
#endif #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); int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName);
#endif #endif

View file

@ -11,9 +11,8 @@
namespace Nif namespace Nif
{ {
// An extra data record. All the extra data connected to an object form a linked list. // 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; std::string name;
ExtraPtr next; // Next extra data record in the list ExtraPtr next; // Next extra data record in the list
@ -31,9 +30,8 @@ public:
void post(NIFFile *nif) override { next.post(nif); } void post(NIFFile *nif) override { next.post(nif); }
}; };
class Controller : public Record struct Controller : public Record
{ {
public:
ControllerPtr next; ControllerPtr next;
int flags; int flags;
float frequency, phase; float frequency, phase;
@ -45,9 +43,8 @@ public:
}; };
/// Has name, extra-data and controller /// Has name, extra-data and controller
class Named : public Record struct Named : public Record
{ {
public:
std::string name; std::string name;
ExtraPtr extra; ExtraPtr extra;
ExtraList extralist; ExtraList extralist;

View file

@ -47,6 +47,11 @@ namespace Nif
data.post(nif); data.post(nif);
} }
void BSShaderTextureSet::read(NIFStream *nif)
{
nif->getSizedStrings(textures, nif->getUInt());
}
void NiParticleModifier::read(NIFStream *nif) void NiParticleModifier::read(NIFStream *nif)
{ {
next.read(nif); next.read(nif);

View file

@ -29,9 +29,8 @@
namespace Nif namespace Nif
{ {
class NiSourceTexture : public Named struct NiSourceTexture : public Named
{ {
public:
// Is this an external (references a separate texture file) or // Is this an external (references a separate texture file) or
// internal (data is inside the nif itself) texture? // internal (data is inside the nif itself) texture?
bool external; bool external;
@ -66,6 +65,24 @@ public:
void post(NIFFile *nif) override; 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 struct NiParticleModifier : public Record
{ {
NiParticleModifierPtr next; NiParticleModifierPtr next;
@ -75,27 +92,24 @@ struct NiParticleModifier : public Record
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiParticleGrowFade : public NiParticleModifier struct NiParticleGrowFade : public NiParticleModifier
{ {
public:
float growTime; float growTime;
float fadeTime; float fadeTime;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiParticleColorModifier : public NiParticleModifier struct NiParticleColorModifier : public NiParticleModifier
{ {
public:
NiColorDataPtr data; NiColorDataPtr data;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiGravity : public NiParticleModifier struct NiGravity : public NiParticleModifier
{ {
public:
float mForce; float mForce;
/* 0 - Wind (fixed direction) /* 0 - Wind (fixed direction)
* 1 - Point (fixed origin) * 1 - Point (fixed origin)
@ -115,27 +129,24 @@ struct NiParticleCollider : public NiParticleModifier
}; };
// NiPinaColada // NiPinaColada
class NiPlanarCollider : public NiParticleCollider struct NiPlanarCollider : public NiParticleCollider
{ {
public:
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
osg::Vec3f mPlaneNormal; osg::Vec3f mPlaneNormal;
float mPlaneDistance; float mPlaneDistance;
}; };
class NiSphericalCollider : public NiParticleCollider struct NiSphericalCollider : public NiParticleCollider
{ {
public:
float mRadius; float mRadius;
osg::Vec3f mCenter; osg::Vec3f mCenter;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiParticleRotation : public NiParticleModifier struct NiParticleRotation : public NiParticleModifier
{ {
public:
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };

View file

@ -325,4 +325,15 @@ namespace Nif
data.post(nif); data.post(nif);
} }
void NiColorInterpolator::read(NIFStream *nif)
{
defaultVal = nif->getVector4();
data.read(nif);
}
void NiColorInterpolator::post(NIFFile *nif)
{
data.post(nif);
}
} }

View file

@ -29,15 +29,14 @@
namespace Nif namespace Nif
{ {
class NiParticleSystemController : public Controller struct NiParticleSystemController : public Controller
{ {
public:
struct Particle { struct Particle {
osg::Vec3f velocity; osg::Vec3f velocity;
float lifetime; float lifetime;
float lifespan; float lifespan;
float timestamp; float timestamp;
int vertex; unsigned short vertex;
}; };
float velocity; float velocity;
@ -80,9 +79,8 @@ public:
}; };
using NiBSPArrayController = NiParticleSystemController; using NiBSPArrayController = NiParticleSystemController;
class NiMaterialColorController : public Controller struct NiMaterialColorController : public Controller
{ {
public:
NiPoint3InterpolatorPtr interpolator; NiPoint3InterpolatorPtr interpolator;
NiPosDataPtr data; NiPosDataPtr data;
unsigned int targetColor; unsigned int targetColor;
@ -91,9 +89,8 @@ public:
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiPathController : public Controller struct NiPathController : public Controller
{ {
public:
NiPosDataPtr posData; NiPosDataPtr posData;
NiFloatDataPtr floatData; NiFloatDataPtr floatData;
@ -115,9 +112,8 @@ public:
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiLookAtController : public Controller struct NiLookAtController : public Controller
{ {
public:
NodePtr target; NodePtr target;
unsigned short lookAtFlags{0}; unsigned short lookAtFlags{0};
@ -125,9 +121,8 @@ public:
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiUVController : public Controller struct NiUVController : public Controller
{ {
public:
NiUVDataPtr data; NiUVDataPtr data;
unsigned int uvSet; unsigned int uvSet;
@ -135,9 +130,8 @@ public:
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiKeyframeController : public Controller struct NiKeyframeController : public Controller
{ {
public:
NiKeyframeDataPtr data; NiKeyframeDataPtr data;
NiTransformInterpolatorPtr interpolator; NiTransformInterpolatorPtr interpolator;
@ -154,12 +148,11 @@ struct NiFloatInterpController : public Controller
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiAlphaController : public NiFloatInterpController { }; struct NiAlphaController : public NiFloatInterpController { };
class NiRollController : public NiFloatInterpController { }; struct NiRollController : public NiFloatInterpController { };
class NiGeomMorpherController : public Controller struct NiGeomMorpherController : public Controller
{ {
public:
NiMorphDataPtr data; NiMorphDataPtr data;
NiFloatInterpolatorList interpolators; NiFloatInterpolatorList interpolators;
@ -167,18 +160,16 @@ public:
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiVisController : public Controller struct NiVisController : public Controller
{ {
public:
NiVisDataPtr data; NiVisDataPtr data;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiFlipController : public Controller struct NiFlipController : public Controller
{ {
public:
NiFloatInterpolatorPtr mInterpolator; NiFloatInterpolatorPtr mInterpolator;
int mTexSlot; // NiTexturingProperty::TextureType int mTexSlot; // NiTexturingProperty::TextureType
float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources 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; 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 } // Namespace
#endif #endif

View file

@ -32,9 +32,8 @@ namespace Nif
{ {
// Common ancestor for several data classes // 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::Vec3f> vertices, normals, tangents, bitangents;
std::vector<osg::Vec4f> colors; std::vector<osg::Vec4f> colors;
std::vector< std::vector<osg::Vec2f> > uvlist; std::vector< std::vector<osg::Vec2f> > uvlist;
@ -44,18 +43,16 @@ public:
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiTriShapeData : public NiGeometryData struct NiTriShapeData : public NiGeometryData
{ {
public:
// Triangles, three vertex indices per triangle // Triangles, three vertex indices per triangle
std::vector<unsigned short> triangles; std::vector<unsigned short> triangles;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiTriStripsData : public NiGeometryData struct NiTriStripsData : public NiGeometryData
{ {
public:
// Triangle strips, series of vertex indices. // Triangle strips, series of vertex indices.
std::vector<std::vector<unsigned short>> strips; std::vector<std::vector<unsigned short>> strips;
@ -70,9 +67,8 @@ struct NiLinesData : public NiGeometryData
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiParticlesData : public NiGeometryData struct NiParticlesData : public NiGeometryData
{ {
public:
int numParticles{0}; int numParticles{0};
int activeCount; int activeCount;
@ -84,39 +80,34 @@ public:
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiRotatingParticlesData : public NiParticlesData struct NiRotatingParticlesData : public NiParticlesData
{ {
public:
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiPosData : public Record struct NiPosData : public Record
{ {
public:
Vector3KeyMapPtr mKeyList; Vector3KeyMapPtr mKeyList;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiUVData : public Record struct NiUVData : public Record
{ {
public:
FloatKeyMapPtr mKeyList[4]; FloatKeyMapPtr mKeyList[4];
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiFloatData : public Record struct NiFloatData : public Record
{ {
public:
FloatKeyMapPtr mKeyList; FloatKeyMapPtr mKeyList;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiPixelData : public Record struct NiPixelData : public Record
{ {
public:
enum Format enum Format
{ {
NIPXFMT_RGB8, NIPXFMT_RGB8,
@ -150,17 +141,15 @@ public:
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiColorData : public Record struct NiColorData : public Record
{ {
public:
Vector4KeyMapPtr mKeyMap; Vector4KeyMapPtr mKeyMap;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiVisData : public Record struct NiVisData : public Record
{ {
public:
struct VisData { struct VisData {
float time; float time;
bool isSet; bool isSet;
@ -170,9 +159,8 @@ public:
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiSkinInstance : public Record struct NiSkinInstance : public Record
{ {
public:
NiSkinDataPtr data; NiSkinDataPtr data;
NiSkinPartitionPtr partitions; NiSkinPartitionPtr partitions;
NodePtr root; NodePtr root;
@ -182,9 +170,8 @@ public:
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiSkinData : public Record struct NiSkinData : public Record
{ {
public:
struct VertWeight struct VertWeight
{ {
unsigned short vertex; unsigned short vertex;
@ -251,9 +238,8 @@ struct NiKeyframeData : public Record
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiPalette : public Record struct NiPalette : public Record
{ {
public:
// 32-bit RGBA colors that correspond to 8-bit indices // 32-bit RGBA colors that correspond to 8-bit indices
std::vector<unsigned int> colors; std::vector<unsigned int> colors;

View file

@ -29,15 +29,13 @@
namespace Nif namespace Nif
{ {
class NiVertWeightsExtraData : public Extra struct NiVertWeightsExtraData : public Extra
{ {
public:
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiTextKeyExtraData : public Extra struct NiTextKeyExtraData : public Extra
{ {
public:
struct TextKey struct TextKey
{ {
float time; float time;
@ -48,9 +46,8 @@ public:
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
class NiStringExtraData : public Extra struct NiStringExtraData : public Extra
{ {
public:
/* Two known meanings: /* Two known meanings:
"MRK" - marker, only visible in the editor, not rendered in-game "MRK" - marker, only visible in the editor, not rendered in-game
"NCO" - no collision "NCO" - no collision

View file

@ -129,6 +129,10 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
factory["NiPoint3Interpolator"] = {&construct <NiPoint3Interpolator> , RC_NiPoint3Interpolator }; factory["NiPoint3Interpolator"] = {&construct <NiPoint3Interpolator> , RC_NiPoint3Interpolator };
factory["NiTransformController"] = {&construct <NiKeyframeController> , RC_NiKeyframeController }; factory["NiTransformController"] = {&construct <NiKeyframeController> , RC_NiKeyframeController };
factory["NiTransformInterpolator"] = {&construct <NiTransformInterpolator> , RC_NiTransformInterpolator }; 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; return factory;
} }

View file

@ -131,9 +131,8 @@ struct NiBoundingVolume
parent node (unless it's the root), and transformation (location parent node (unless it's the root), and transformation (location
and rotation) relative to it's parent. 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. // Node flags. Interpretation depends somewhat on the type of node.
unsigned int flags; unsigned int flags;
Transformation trafo; Transformation trafo;
@ -240,43 +239,6 @@ struct NiNode : Node
}; };
struct NiGeometry : Node struct NiGeometry : Node
{
struct MaterialData
{
std::vector<std::string> materialNames;
std::vector<int> materialExtraData;
unsigned int activeMaterial{0};
bool materialNeedsUpdate{false};
void read(NIFStream *nif)
{
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0))
return;
unsigned int numMaterials = 0;
if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3))
numMaterials = nif->getBoolean(); // Has Shader
else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
numMaterials = nif->getUInt();
if (numMaterials)
{
nif->getStrings(materialNames, numMaterials);
nif->getInts(materialExtraData, numMaterials);
}
if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
activeMaterial = nif->getUInt();
if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS)
{
materialNeedsUpdate = nif->getBoolean();
if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
nif->skip(8);
}
}
};
NiSkinInstancePtr skin;
MaterialData materialData;
};
struct NiTriShape : NiGeometry
{ {
/* Possible flags: /* Possible flags:
0x40 - mesh has no vertex normals ? 0x40 - mesh has no vertex normals ?
@ -285,14 +247,50 @@ struct NiTriShape : NiGeometry
been observed so far. been observed so far.
*/ */
NiTriShapeDataPtr data; struct MaterialData
{
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 num = 0;
if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3))
num = nif->getBoolean(); // Has Shader
else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
num = nif->getUInt();
if (num)
{
nif->getStrings(names, num);
nif->getInts(extra, num);
}
if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
active = nif->getUInt();
if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS)
needsUpdate = nif->getBoolean();
}
};
NiGeometryDataPtr data;
NiSkinInstancePtr skin;
MaterialData material;
BSShaderPropertyPtr shaderprop;
NiAlphaPropertyPtr alphaprop;
void read(NIFStream *nif) override void read(NIFStream *nif) override
{ {
Node::read(nif); Node::read(nif);
data.read(nif); data.read(nif);
skin.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 void post(NIFFile *nif) override
@ -300,53 +298,28 @@ struct NiTriShape : NiGeometry
Node::post(nif); Node::post(nif);
data.post(nif); data.post(nif);
skin.post(nif); skin.post(nif);
if (!skin.empty()) shaderprop.post(nif);
alphaprop.post(nif);
if (recType != RC_NiParticles && !skin.empty())
nif->setUseSkinning(true); nif->setUseSkinning(true);
} }
}; };
struct NiTriStrips : NiGeometry struct NiTriShape : NiGeometry {};
struct BSLODTriShape : NiTriShape
{ {
NiTriStripsDataPtr data; unsigned int lod0, lod1, lod2;
void read(NIFStream *nif) override void read(NIFStream *nif) override
{ {
Node::read(nif); NiTriShape::read(nif);
data.read(nif); lod0 = nif->getUInt();
skin.read(nif); lod1 = nif->getUInt();
materialData.read(nif); lod2 = nif->getUInt();
}
void post(NIFFile *nif) override
{
Node::post(nif);
data.post(nif);
skin.post(nif);
if (!skin.empty())
nif->setUseSkinning(true);
}
};
struct NiLines : NiGeometry
{
NiLinesDataPtr data;
void read(NIFStream *nif) override
{
Node::read(nif);
data.read(nif);
skin.read(nif);
}
void post(NIFFile *nif) override
{
Node::post(nif);
data.post(nif);
skin.post(nif);
if (!skin.empty())
nif->setUseSkinning(true);
} }
}; };
struct NiTriStrips : NiGeometry {};
struct NiLines : NiGeometry {};
struct NiParticles : NiGeometry { };
struct NiCamera : Node 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. // A node used as the base to switch between child nodes, such as for LOD levels.
struct NiSwitchNode : public NiNode struct NiSwitchNode : public NiNode
{ {

View file

@ -99,6 +99,25 @@ void NiTexturingProperty::post(NIFFile *nif)
shaderTextures[i].post(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) void NiFogProperty::read(NIFStream *nif)
{ {
Property::read(nif); Property::read(nif);

View file

@ -29,11 +29,10 @@
namespace Nif namespace Nif
{ {
class Property : public Named { }; struct Property : public Named { };
class NiTexturingProperty : public Property struct NiTexturingProperty : public Property
{ {
public:
unsigned short flags{0u}; unsigned short flags{0u};
// A sub-texture // A sub-texture
@ -96,9 +95,8 @@ public:
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
class NiFogProperty : public Property struct NiFogProperty : public Property
{ {
public:
unsigned short mFlags; unsigned short mFlags;
float mFogDepth; float mFogDepth;
osg::Vec3f mColour; 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 struct NiDitherProperty : public Property
{ {
unsigned short flags; unsigned short flags;
@ -294,8 +305,8 @@ struct S_StencilProperty
void read(NIFStream *nif); void read(NIFStream *nif);
}; };
class NiAlphaProperty : public StructPropT<S_AlphaProperty> { }; struct NiAlphaProperty : public StructPropT<S_AlphaProperty> { };
class NiVertexColorProperty : public StructPropT<S_VertexColorProperty> { }; struct NiVertexColorProperty : public StructPropT<S_VertexColorProperty> { };
struct NiStencilProperty : public Property struct NiStencilProperty : public Property
{ {
S_StencilProperty data; S_StencilProperty data;

View file

@ -118,7 +118,11 @@ enum RecordType
RC_NiFloatInterpolator, RC_NiFloatInterpolator,
RC_NiPoint3Interpolator, RC_NiPoint3Interpolator,
RC_NiBoolInterpolator, RC_NiBoolInterpolator,
RC_NiTransformInterpolator RC_NiTransformInterpolator,
RC_NiColorInterpolator,
RC_BSShaderTextureSet,
RC_BSLODTriShape,
RC_BSShaderProperty
}; };
/// Base class for all records /// Base class for all records

View file

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

View file

@ -34,11 +34,10 @@ bool pathFileNameStartsWithX(const std::string& path)
void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform) 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<osg::Vec3f> &vertices = data.vertices;
const std::vector<unsigned short> &triangles = data.triangles; 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) 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<osg::Vec3f> &vertices = data.vertices;
const std::vector<std::vector<unsigned short>> &strips = data.strips; const std::vector<std::vector<unsigned short>> &strips = data.strips;
if (vertices.empty() || strips.empty())
return;
mesh.preallocateVertices(static_cast<int>(vertices.size())); mesh.preallocateVertices(static_cast<int>(vertices.size()));
int numTriangles = 0; int numTriangles = 0;
for (const std::vector<unsigned short>& strip : strips) 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) if (geometry->recType == Nif::RC_NiTriShape || geometry->recType == Nif::RC_BSLODTriShape)
fillTriangleMesh(mesh, static_cast<const Nif::NiTriShape*>(nifNode)->data.get(), transform); fillTriangleMesh(mesh, static_cast<const Nif::NiTriShapeData&>(geometry->data.get()), transform);
else if (nifNode->recType == Nif::RC_NiTriStrips) else if (geometry->recType == Nif::RC_NiTriStrips)
fillTriangleMesh(mesh, static_cast<const Nif::NiTriStrips*>(nifNode)->data.get(), transform); 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)) if (findBoundingBox(node, filename))
{ {
const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.extents);
const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.center);
std::unique_ptr<btCompoundShape> compound (new btCompoundShape); 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(); btTransform transform = btTransform::getIdentity();
transform.setOrigin(origin); transform.setOrigin(center);
compound->addChildShape(transform, boxShape.get()); compound->addChildShape(transform, boxShape.get());
boxShape.release(); boxShape.release();
@ -208,8 +205,8 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string&
switch (type) switch (type)
{ {
case Nif::NiBoundingVolume::Type::BOX_BV: case Nif::NiBoundingVolume::Type::BOX_BV:
mShape->mCollisionBoxHalfExtents = node->bounds.box.extents; mShape->mCollisionBox.extents = node->bounds.box.extents;
mShape->mCollisionBoxTranslate = node->bounds.box.center; mShape->mCollisionBox.center = node->bounds.box.center;
break; break;
default: 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! // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape!
// It must be ignored completely. // It must be ignored completely.
// (occurs in tr_ex_imp_wall_arch_04.nif) // (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); handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid);
} }
@ -341,23 +340,31 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
if ((flags & 0x800)) if ((flags & 0x800))
return; 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 (niGeometry->data->recType != Nif::RC_NiTriShapeData)
if (!shape->skin.empty()) return;
isAnimated = false;
if (shape->data.empty() || shape->data->triangles.empty()) auto data = static_cast<const Nif::NiTriShapeData*>(niGeometry->data.getPtr());
if (data->triangles.empty())
return; return;
} }
else else if (niGeometry->recType == Nif::RC_NiTriStrips)
{ {
const Nif::NiTriStrips* shape = static_cast<const Nif::NiTriStrips*>(nifNode); if (niGeometry->data->recType != Nif::RC_NiTriStripsData)
if (!shape->skin.empty()) return;
isAnimated = false;
if (shape->data.empty() || shape->data->strips.empty()) auto data = static_cast<const Nif::NiTriStripsData*>(niGeometry->data.getPtr());
if (data->strips.empty())
return; return;
} }
if (!niGeometry->skin.empty())
isAnimated = false;
if (isAnimated) if (isAnimated)
{ {
@ -366,7 +373,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
std::unique_ptr<btTriangleMesh> childMesh(new btTriangleMesh); 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)); std::unique_ptr<Resource::TriangleMeshShape> childShape(new Resource::TriangleMeshShape(childMesh.get(), true));
childMesh.release(); childMesh.release();
@ -394,7 +401,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
if (!mAvoidStaticMesh) if (!mAvoidStaticMesh)
mAvoidStaticMesh.reset(new btTriangleMesh(false)); mAvoidStaticMesh.reset(new btTriangleMesh(false));
fillTriangleMesh(*mAvoidStaticMesh, nifNode, transform); fillTriangleMesh(*mAvoidStaticMesh, niGeometry, transform);
} }
else else
{ {
@ -402,7 +409,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
mStaticMesh.reset(new btTriangleMesh(false)); mStaticMesh.reset(new btTriangleMesh(false));
// Static shape, just transform all vertices into position // Static shape, just transform all vertices into position
fillTriangleMesh(*mStaticMesh, nifNode, transform); fillTriangleMesh(*mStaticMesh, niGeometry, transform);
} }
} }

View file

@ -66,7 +66,9 @@ namespace NifOsg
std::conjunction_v< std::conjunction_v<
std::disjunction< std::disjunction<
std::is_same<ValueT, float>, 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> std::is_same<decltype(T::defaultVal), ValueT>
>, >,

View file

@ -67,6 +67,7 @@ namespace
case Nif::RC_NiTriShape: case Nif::RC_NiTriShape:
case Nif::RC_NiTriStrips: case Nif::RC_NiTriStrips:
case Nif::RC_NiLines: case Nif::RC_NiLines:
case Nif::RC_BSLODTriShape:
return true; return true;
} }
return false; 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 // 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); 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) 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_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); 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 int texUnit = 3; // FIXME
osg::StateSet* stateset = node->getOrCreateStateSet(); osg::StateSet* stateset = node->getOrCreateStateSet();
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(texUnit, texGen, 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))); 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. // 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) void handleParticleInitialState(const Nif::Node* nifNode, osgParticle::ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl)
{ {
const auto particleNode = static_cast<const Nif::NiParticles*>(nifNode); auto particleNode = static_cast<const Nif::NiParticles*>(nifNode);
if (particleNode->data.empty()) if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData)
return; return;
const Nif::NiParticlesData* particledata = particleNode->data.getPtr(); auto particledata = static_cast<const Nif::NiParticlesData*>(particleNode->data.getPtr());
osg::BoundingBox box; osg::BoundingBox box;
@ -963,6 +971,9 @@ namespace NifOsg
if (particle.lifespan <= 0) if (particle.lifespan <= 0)
continue; continue;
if (particle.vertex >= particledata->vertices.size())
continue;
ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime)); ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime));
osgParticle::Particle* created = partsys->createParticle(&particletemplate); 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, // 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. // 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); created->setVelocity(particle.velocity);
const osg::Vec3f& position = particledata->vertices.at(particle.vertex); const osg::Vec3f& position = particledata->vertices[particle.vertex];
created->setPosition(position); created->setPosition(position);
osg::Vec4f partcolor (1.f,1.f,1.f,1.f); osg::Vec4f partcolor (1.f,1.f,1.f,1.f);
if (particle.vertex < int(particledata->colors.size())) if (particle.vertex < particledata->colors.size())
partcolor = particledata->colors.at(particle.vertex); partcolor = particledata->colors[particle.vertex];
float size = partctrl->size; float size = partctrl->size;
if (particle.vertex < int(particledata->sizes.size())) if (particle.vertex < particledata->sizes.size())
size *= particledata->sizes.at(particle.vertex); size *= particledata->sizes[particle.vertex];
created->setSizeRange(osgParticle::rangef(size, size)); created->setSizeRange(osgParticle::rangef(size, size));
box.expandBy(osg::BoundingSphere(position, 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) 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; const Nif::NiGeometry* niGeometry = static_cast<const Nif::NiGeometry*>(nifNode);
if (nifNode->recType == Nif::RC_NiTriShape) 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 (niGeometryData->recType != Nif::RC_NiTriShapeData)
if (!triShape->data.empty()) return;
{ auto triangles = static_cast<const Nif::NiTriShapeData*>(niGeometryData)->triangles;
const Nif::NiTriShapeData* data = triShape->data.getPtr(); if (triangles.empty())
niGeometryData = static_cast<const Nif::NiGeometryData*>(data); return;
if (!data->triangles.empty()) geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, triangles.size(),
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(), (unsigned short*)triangles.data()));
(unsigned short*)data->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 (niGeometryData->recType != Nif::RC_NiTriStripsData)
if (!triStrips->data.empty()) 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(); if (strip.size() < 3)
niGeometryData = static_cast<const Nif::NiGeometryData*>(data); continue;
if (!data->strips.empty()) geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(),
{ (unsigned short*)strip.data()));
for (const auto& strip : data->strips) hasGeometry = true;
{
if (strip.size() >= 3)
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(),
(unsigned short*)strip.data()));
}
}
} }
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 (niGeometryData->recType != Nif::RC_NiLinesData)
if (!lines->data.empty()) return;
{ auto data = static_cast<const Nif::NiLinesData*>(niGeometryData);
const Nif::NiLinesData* data = lines->data.getPtr(); const auto& line = data->lines;
niGeometryData = static_cast<const Nif::NiGeometryData*>(data); if (line.empty())
const auto& line = data->lines; return;
if (!line.empty()) geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(),
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), (unsigned short*)line.data())); (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: // osg::Material properties are handled here for two reasons:
// - if there are no vertex colors, we need to disable colorMode. // - if there are no vertex colors, we need to disable colorMode.
@ -1229,15 +1239,18 @@ namespace NifOsg
// above the actual renderable would be tedious. // above the actual renderable would be tedious.
std::vector<const Nif::Property*> drawableProps; std::vector<const Nif::Property*> drawableProps;
collectDrawableProperties(nifNode, 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) void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<unsigned int>& boundTextures, int animflags)
{ {
assert(isTypeGeometry(nifNode->recType)); assert(isTypeGeometry(nifNode->recType));
osg::ref_ptr<osg::Drawable> drawable;
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry); osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); 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) for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
{ {
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
@ -1282,6 +1295,8 @@ namespace NifOsg
assert(isTypeGeometry(nifNode->recType)); assert(isTypeGeometry(nifNode->recType));
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry); osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags);
if (geometry->empty())
return;
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry); osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
rig->setSourceGeometry(geometry); rig->setSourceGeometry(geometry);
rig->setName(nifNode->name); rig->setName(nifNode->name);
@ -1481,6 +1496,17 @@ namespace NifOsg
return image; 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) 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()) if (!boundTextures.empty())
@ -1567,14 +1593,7 @@ namespace NifOsg
if (i == Nif::NiTexturingProperty::GlowTexture) if (i == Nif::NiTexturingProperty::GlowTexture)
{ {
osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON);
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);
} }
else if (i == Nif::NiTexturingProperty::DarkTexture) else if (i == Nif::NiTexturingProperty::DarkTexture)
{ {

View file

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

View file

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

View file

@ -20,8 +20,7 @@ BulletShape::BulletShape()
BulletShape::BulletShape(const BulletShape &copy, const osg::CopyOp &copyop) BulletShape::BulletShape(const BulletShape &copy, const osg::CopyOp &copyop)
: mCollisionShape(duplicateCollisionShape(copy.mCollisionShape)) : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape))
, mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape)) , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape))
, mCollisionBoxHalfExtents(copy.mCollisionBoxHalfExtents) , mCollisionBox(copy.mCollisionBox)
, mCollisionBoxTranslate(copy.mCollisionBoxTranslate)
, mAnimatedShapes(copy.mAnimatedShapes) , mAnimatedShapes(copy.mAnimatedShapes)
{ {
} }
@ -106,8 +105,7 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr<const BulletShape> source)
: BulletShape() : BulletShape()
, mSource(source) , mSource(source)
{ {
mCollisionBoxHalfExtents = source->mCollisionBoxHalfExtents; mCollisionBox = source->mCollisionBox;
mCollisionBoxTranslate = source->mCollisionBoxTranslate;
mAnimatedShapes = source->mAnimatedShapes; mAnimatedShapes = source->mAnimatedShapes;

View file

@ -27,10 +27,14 @@ namespace Resource
btCollisionShape* mCollisionShape; btCollisionShape* mCollisionShape;
btCollisionShape* mAvoidCollisionShape; 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. // 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. // For now, use one file <-> one resource for simplicity.
osg::Vec3f mCollisionBoxHalfExtents; CollisionBox mCollisionBox;
osg::Vec3f mCollisionBoxTranslate;
// Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape // 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). // will be a btCompoundShape (which consists of one or more child shapes).

View file

@ -21,7 +21,7 @@ namespace Resource
void RetrieveAnimationsVisitor::apply(osg::Node& node) 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(); osg::ref_ptr<SceneUtil::OsgAnimationController> callback = new SceneUtil::OsgAnimationController();
@ -40,11 +40,9 @@ namespace Resource
std::string animationName = animation->getName(); std::string animationName = animation->getName();
std::string start = animationName + std::string(": start"); std::string start = animationName + std::string(": start");
std::string stop = animationName + std::string(": stop"); 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(); 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? 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" // 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(startTime, std::move(start));
mTarget.mTextKeys.emplace(stopTime, std::move(stop)); mTarget.mTextKeys.emplace(stopTime, std::move(stop));
mTarget.mTextKeys.emplace(startTime, std::move(loopstart));
mTarget.mTextKeys.emplace(stopTime, std::move(loopstop));
SceneUtil::EmulatedAnimation emulatedAnimation; SceneUtil::EmulatedAnimation emulatedAnimation;
emulatedAnimation.mStartTime = startTime; emulatedAnimation.mStartTime = startTime;

View file

@ -31,7 +31,7 @@ namespace SceneUtil
void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt)
{ {
const osgAnimation::ChannelList& channels = mAnimation->getChannels(); 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& channelName = channel->getName();
const std::string& channelTargetName = channel->getTargetName(); const std::string& channelTargetName = channel->getTargetName();
@ -83,7 +83,7 @@ namespace SceneUtil
{ {
osgAnimation::UpdateMatrixTransform* umt = dynamic_cast<osgAnimation::UpdateMatrixTransform*>(cb); osgAnimation::UpdateMatrixTransform* umt = dynamic_cast<osgAnimation::UpdateMatrixTransform*>(cb);
if (umt) if (umt)
if (node.getName() != "root") link(umt); if (node.getName() != "bip01") link(umt);
cb = cb->getNestedCallback(); cb = cb->getNestedCallback();
} }
@ -117,7 +117,7 @@ namespace SceneUtil
//Find the correct animation based on time //Find the correct animation based on time
for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations)
{ {
if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime)
{ {
newTime = time - emulatedAnimation.mStartTime; newTime = time - emulatedAnimation.mStartTime;
animationName = emulatedAnimation.mName; animationName = emulatedAnimation.mName;
@ -125,15 +125,15 @@ namespace SceneUtil
} }
//Find the root transform track in animation //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; if (mergedAnimationTrack->getName() != animationName) continue;
const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); 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()) ) if ( osgAnimation::MatrixLinearSampler* templateSampler = dynamic_cast<osgAnimation::MatrixLinearSampler*> (channel->getSampler()) )
{ {
@ -150,7 +150,7 @@ namespace SceneUtil
void OsgAnimationController::update(float time, std::string animationName) 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); if (mergedAnimationTrack->getName() == animationName) mergedAnimationTrack->update(time);
} }
@ -162,7 +162,7 @@ namespace SceneUtil
{ {
if (mNeedToLink) if (mNeedToLink)
{ {
for (const osg::ref_ptr<Resource::Animation> mergedAnimationTrack : mMergedAnimationTracks) for (const auto& mergedAnimationTrack : mMergedAnimationTracks)
{ {
if (!mLinker.valid()) mLinker = new LinkVisitor(); if (!mLinker.valid()) mLinker = new LinkVisitor();
mLinker->setAnimation(mergedAnimationTrack); mLinker->setAnimation(mergedAnimationTrack);

View file

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

View file

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

View file

@ -267,6 +267,12 @@
<Property key="TextAlign" value="HCenter Top"/> <Property key="TextAlign" value="HCenter Top"/>
</Widget> </Widget>
</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> </Widget>
<!-- Birthsign tooltip --> <!-- Birthsign tooltip -->

View file

@ -724,6 +724,16 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C
</property> </property>
</widget> </widget>
</item> </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> <item>
<spacer name="verticalSpacer_2"> <spacer name="verticalSpacer_2">
<property name="orientation"> <property name="orientation">