mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-19 22:53:50 +00:00
Merge remote-tracking branch 'remotes/origin/master' into openxr_vr_geometryshader_feature_branch
This commit is contained in:
commit
3e82cae500
72 changed files with 1509 additions and 591 deletions
|
@ -49,6 +49,7 @@ Programmers
|
|||
Cédric Mocquillon
|
||||
Chris Boyce (slothlife)
|
||||
Chris Robinson (KittyCat)
|
||||
Coleman Smith (olcoal)
|
||||
Cory F. Cohen (cfcohen)
|
||||
Cris Mihalache (Mirceam)
|
||||
crussell187
|
||||
|
@ -122,7 +123,6 @@ Programmers
|
|||
Lordrea
|
||||
Łukasz Gołębiewski (lukago)
|
||||
Lukasz Gromanowski (lgro)
|
||||
Manuel Edelmann (vorenon)
|
||||
Marc Bouvier (CramitDeFrog)
|
||||
Marcin Hulist (Gohan)
|
||||
Mark Siewert (mark76)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
0.47.0
|
||||
------
|
||||
|
||||
Bug #832: OpenMW-CS: Handle deleted references
|
||||
Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path
|
||||
Bug #1952: Incorrect particle lighting
|
||||
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
|
||||
|
@ -67,14 +68,19 @@
|
|||
Bug #5644: Summon effects running on the player during game initialization cause crashes
|
||||
Bug #5656: Sneaking characters block hits while standing
|
||||
Bug #5661: Region sounds don't play at the right interval
|
||||
Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx
|
||||
Bug #5688: Water shader broken indoors with enable indoor shadows = false
|
||||
Bug #5695: ExplodeSpell for actors doesn't target the ground
|
||||
Bug #5703: OpenMW-CS menu system crashing on XFCE
|
||||
Bug #5731: Editor: skirts are invisible on characters
|
||||
Feature #390: 3rd person look "over the shoulder"
|
||||
Feature #1536: Show more information about level on menu
|
||||
Feature #2386: Distant Statics in the form of Object Paging
|
||||
Feature #2404: Levelled List can not be placed into a container
|
||||
Feature #2686: Timestamps in openmw.log
|
||||
Feature #4894: Consider actors as obstacles for pathfinding
|
||||
Feature #5043: Head Bobbing
|
||||
Feature #5199: Improve Scene Colors
|
||||
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
|
||||
Feature #5362: Show the soul gems' trapped soul in count dialog
|
||||
Feature #5445: Handle NiLines
|
||||
|
@ -93,6 +99,7 @@
|
|||
Feature #5649: Skyrim SE compressed BSA format support
|
||||
Feature #5672: Make stretch menu background configuration more accessible
|
||||
Feature #5692: Improve spell/magic item search to factor in magic effect names
|
||||
Feature #5730: Add graphic herbalism option to the launcher and documents
|
||||
Task #5480: Drop Qt4 support
|
||||
Task #5520: Improve cell name autocompleter implementation
|
||||
|
||||
|
|
|
@ -34,7 +34,9 @@ Bug Fixes:
|
|||
- Morrowind legacy madness: Using a key on a trapped door/container now only disarms the trap if the door/container is locked (#5370)
|
||||
|
||||
Editor Bug Fixes:
|
||||
- Deleted and moved objects within a cell are now saved properly (#832)
|
||||
- Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400)
|
||||
- Loading mods now keeps the master index (#5675)
|
||||
- Flicker and crashing on XFCE4 fixed (#5703)
|
||||
|
||||
Miscellaneous:
|
||||
|
|
|
@ -153,6 +153,7 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
|
||||
showOwnedComboBox->setCurrentIndex(showOwnedIndex);
|
||||
loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
|
||||
loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
|
||||
}
|
||||
|
||||
// Bug fixes
|
||||
|
@ -279,6 +280,7 @@ void Launcher::AdvancedPage::saveSettings()
|
|||
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
|
||||
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
|
||||
saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
|
||||
saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
|
||||
}
|
||||
|
||||
// Bug fixes
|
||||
|
|
|
@ -210,6 +210,19 @@ void CSMPrefs::State::declare()
|
|||
setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.").
|
||||
setRange(10, 10000);
|
||||
declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1);
|
||||
declareBool("scene-use-gradient", "Use Gradient Background", true);
|
||||
declareColour ("scene-day-background-colour", "Day Background Colour", QColor (110, 120, 128, 255));
|
||||
declareColour ("scene-day-gradient-colour", "Day Gradient Colour", QColor (47, 51, 51, 255)).
|
||||
setTooltip("Sets the gradient color to use in conjunction with the day background color. Ignored if "
|
||||
"the gradient option is disabled.");
|
||||
declareColour ("scene-bright-background-colour", "Scene Bright Background Colour", QColor (79, 87, 92, 255));
|
||||
declareColour ("scene-bright-gradient-colour", "Scene Bright Gradient Colour", QColor (47, 51, 51, 255)).
|
||||
setTooltip("Sets the gradient color to use in conjunction with the bright background color. Ignored if "
|
||||
"the gradient option is disabled.");
|
||||
declareColour ("scene-night-background-colour", "Scene Night Background Colour", QColor (64, 77, 79, 255));
|
||||
declareColour ("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor (47, 51, 51, 255)).
|
||||
setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if "
|
||||
"the gradient option is disabled.");
|
||||
|
||||
declareCategory ("Tooltips");
|
||||
declareBool ("scene", "Show Tooltips in 3D scenes", true);
|
||||
|
|
|
@ -593,56 +593,33 @@ namespace CSMWorld
|
|||
}
|
||||
else if (type == UniversalId::Type_Clothing)
|
||||
{
|
||||
int priority = 0;
|
||||
// TODO: reserve bodyparts for robes and skirts
|
||||
auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(record).get();
|
||||
|
||||
std::vector<ESM::PartReferenceType> parts;
|
||||
if (clothing.mData.mType == ESM::Clothing::Robe)
|
||||
{
|
||||
auto reservedList = std::vector<ESM::PartReference>();
|
||||
|
||||
ESM::PartReference pr;
|
||||
pr.mMale = "";
|
||||
pr.mFemale = "";
|
||||
|
||||
ESM::PartReferenceType parts[] = {
|
||||
parts = {
|
||||
ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg,
|
||||
ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee,
|
||||
ESM::PRT_RForearm, ESM::PRT_LForearm
|
||||
ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass
|
||||
};
|
||||
size_t parts_size = sizeof(parts)/sizeof(parts[0]);
|
||||
for(size_t p = 0;p < parts_size;++p)
|
||||
{
|
||||
pr.mPart = parts[p];
|
||||
reservedList.push_back(pr);
|
||||
}
|
||||
|
||||
priority = parts_size;
|
||||
addParts(reservedList, priority);
|
||||
}
|
||||
else if (clothing.mData.mType == ESM::Clothing::Skirt)
|
||||
{
|
||||
auto reservedList = std::vector<ESM::PartReference>();
|
||||
|
||||
ESM::PartReference pr;
|
||||
pr.mMale = "";
|
||||
pr.mFemale = "";
|
||||
|
||||
ESM::PartReferenceType parts[] = {
|
||||
ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg
|
||||
};
|
||||
size_t parts_size = sizeof(parts)/sizeof(parts[0]);
|
||||
for(size_t p = 0;p < parts_size;++p)
|
||||
{
|
||||
pr.mPart = parts[p];
|
||||
reservedList.push_back(pr);
|
||||
}
|
||||
|
||||
priority = parts_size;
|
||||
addParts(reservedList, priority);
|
||||
parts = {ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg};
|
||||
}
|
||||
|
||||
std::vector<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(reservedList, priority);
|
||||
|
||||
// Changing parts could affect what is picked for rendering
|
||||
data->addOtherDependency(itemId);
|
||||
|
|
|
@ -64,10 +64,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
|
|||
|
||||
// ignore content file number
|
||||
std::map<ESM::RefNum, std::string>::iterator iter = cache.begin();
|
||||
ref.mRefNum.mIndex = ref.mRefNum.mIndex & 0x00ffffff;
|
||||
unsigned int thisIndex = ref.mRefNum.mIndex & 0x00ffffff;
|
||||
if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24;
|
||||
|
||||
for (; iter != cache.end(); ++iter)
|
||||
{
|
||||
if (ref.mRefNum.mIndex == iter->first.mIndex)
|
||||
if (thisIndex == iter->first.mIndex)
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -151,9 +151,9 @@ void CSVRender::CellArrow::buildShape()
|
|||
osg::Vec4Array *colours = new osg::Vec4Array;
|
||||
|
||||
for (int i=0; i<6; ++i)
|
||||
colours->push_back (osg::Vec4f (1.0f, 0.0f, 0.0f, 1.0f));
|
||||
colours->push_back (osg::Vec4f (0.11, 0.6f, 0.95f, 1.0f));
|
||||
for (int i=0; i<6; ++i)
|
||||
colours->push_back (osg::Vec4f (0.8f, (i==2 || i==5) ? 0.6f : 0.4f, 0.0f, 1.0f));
|
||||
colours->push_back (osg::Vec4f (0.08f, 0.44f, 0.7f, 1.0f));
|
||||
|
||||
geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX);
|
||||
|
||||
|
|
|
@ -70,7 +70,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f)
|
|||
setLayout(layout);
|
||||
|
||||
mView->getCamera()->setGraphicsContext(window);
|
||||
mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) );
|
||||
mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
|
||||
|
||||
SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager;
|
||||
|
@ -213,6 +212,25 @@ SceneWidget::SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSyste
|
|||
|
||||
mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() );
|
||||
|
||||
// set up gradient view or configured clear color
|
||||
QColor bgColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor();
|
||||
|
||||
if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) {
|
||||
QColor gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor();
|
||||
mGradientCamera = createGradientCamera(bgColour, gradientColour);
|
||||
|
||||
mView->getCamera()->setClearMask(0);
|
||||
mView->getCamera()->addChild(mGradientCamera.get());
|
||||
}
|
||||
else {
|
||||
mView->getCamera()->setClearColor(osg::Vec4(
|
||||
bgColour.redF(),
|
||||
bgColour.greenF(),
|
||||
bgColour.blueF(),
|
||||
1.0f
|
||||
));
|
||||
}
|
||||
|
||||
// we handle lighting manually
|
||||
mView->setLightingMode(osgViewer::View::NO_LIGHT);
|
||||
|
||||
|
@ -250,6 +268,79 @@ SceneWidget::~SceneWidget()
|
|||
mResourceSystem->releaseGLObjects(SDLUtil::GraphicsWindowSDL2::findContext(*mView)->getState());
|
||||
}
|
||||
|
||||
|
||||
osg::ref_ptr<osg::Geometry> SceneWidget::createGradientRectangle(QColor bgColour, QColor gradientColour)
|
||||
{
|
||||
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
|
||||
|
||||
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
|
||||
|
||||
vertices->push_back(osg::Vec3(0.0f, 0.0f, -1.0f));
|
||||
vertices->push_back(osg::Vec3(1.0f, 0.0f, -1.0f));
|
||||
vertices->push_back(osg::Vec3(0.0f, 1.0f, -1.0f));
|
||||
vertices->push_back(osg::Vec3(1.0f, 1.0f, -1.0f));
|
||||
|
||||
geometry->setVertexArray(vertices);
|
||||
|
||||
osg::ref_ptr<osg::DrawElementsUShort> primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0);
|
||||
|
||||
// triangle 1
|
||||
primitives->push_back (0);
|
||||
primitives->push_back (1);
|
||||
primitives->push_back (2);
|
||||
|
||||
// triangle 2
|
||||
primitives->push_back (2);
|
||||
primitives->push_back (1);
|
||||
primitives->push_back (3);
|
||||
|
||||
geometry->addPrimitiveSet(primitives);
|
||||
|
||||
osg::ref_ptr <osg::Vec4ubArray> colours = new osg::Vec4ubArray;
|
||||
colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f));
|
||||
colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f));
|
||||
colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f));
|
||||
colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f));
|
||||
|
||||
geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX);
|
||||
|
||||
geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
|
||||
geometry->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
|
||||
osg::ref_ptr<osg::Camera> SceneWidget::createGradientCamera(QColor bgColour, QColor gradientColour)
|
||||
{
|
||||
osg::ref_ptr<osg::Camera> camera = new osg::Camera();
|
||||
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
|
||||
camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1.0f, 0, 1.0f));
|
||||
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
|
||||
camera->setViewMatrix(osg::Matrix::identity());
|
||||
|
||||
camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||||
camera->setAllowEventFocus(false);
|
||||
|
||||
// draw subgraph before main camera view.
|
||||
camera->setRenderOrder(osg::Camera::PRE_RENDER);
|
||||
|
||||
camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
|
||||
|
||||
osg::ref_ptr<osg::Geometry> gradientQuad = createGradientRectangle(bgColour, gradientColour);
|
||||
|
||||
camera->addChild(gradientQuad);
|
||||
return camera;
|
||||
}
|
||||
|
||||
|
||||
void SceneWidget::updateGradientCamera(QColor bgColour, QColor gradientColour)
|
||||
{
|
||||
osg::ref_ptr<osg::Geometry> gradientRect = createGradientRectangle(bgColour, gradientColour);
|
||||
// Replaces previous rectangle
|
||||
mGradientCamera->setChild(0, gradientRect.get());
|
||||
}
|
||||
|
||||
void SceneWidget::setLighting(Lighting *lighting)
|
||||
{
|
||||
if (mLighting)
|
||||
|
@ -277,12 +368,59 @@ void SceneWidget::setAmbient(const osg::Vec4f& ambient)
|
|||
|
||||
void SceneWidget::selectLightingMode (const std::string& mode)
|
||||
{
|
||||
if (mode=="day")
|
||||
setLighting (&mLightingDay);
|
||||
else if (mode=="night")
|
||||
setLighting (&mLightingNight);
|
||||
else if (mode=="bright")
|
||||
setLighting (&mLightingBright);
|
||||
QColor backgroundColour;
|
||||
QColor gradientColour;
|
||||
if (mode == "day")
|
||||
{
|
||||
backgroundColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor();
|
||||
gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor();
|
||||
setLighting(&mLightingDay);
|
||||
}
|
||||
else if (mode == "night")
|
||||
{
|
||||
backgroundColour = CSMPrefs::get()["Rendering"]["scene-night-background-colour"].toColor();
|
||||
gradientColour = CSMPrefs::get()["Rendering"]["scene-night-gradient-colour"].toColor();
|
||||
setLighting(&mLightingNight);
|
||||
}
|
||||
else if (mode == "bright")
|
||||
{
|
||||
backgroundColour = CSMPrefs::get()["Rendering"]["scene-bright-background-colour"].toColor();
|
||||
gradientColour = CSMPrefs::get()["Rendering"]["scene-bright-gradient-colour"].toColor();
|
||||
setLighting(&mLightingBright);
|
||||
}
|
||||
if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) {
|
||||
if (mGradientCamera.get() != nullptr) {
|
||||
// we can go ahead and update since this camera still exists
|
||||
updateGradientCamera(backgroundColour, gradientColour);
|
||||
|
||||
if (!mView->getCamera()->containsNode(mGradientCamera.get()))
|
||||
{
|
||||
// need to re-attach the gradient camera
|
||||
mView->getCamera()->setClearMask(0);
|
||||
mView->getCamera()->addChild(mGradientCamera.get());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// need to create the gradient camera
|
||||
mGradientCamera = createGradientCamera(backgroundColour, gradientColour);
|
||||
mView->getCamera()->setClearMask(0);
|
||||
mView->getCamera()->addChild(mGradientCamera.get());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Fall back to using the clear color for the camera
|
||||
mView->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
mView->getCamera()->setClearColor(osg::Vec4(
|
||||
backgroundColour.redF(),
|
||||
backgroundColour.greenF(),
|
||||
backgroundColour.blueF(),
|
||||
1.0f
|
||||
));
|
||||
if (mGradientCamera.get() != nullptr && mView->getCamera()->containsNode(mGradientCamera.get())) {
|
||||
// Remove the child to prevent the gradient from rendering
|
||||
mView->getCamera()->removeChild(mGradientCamera.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent)
|
||||
|
|
|
@ -100,10 +100,15 @@ namespace CSVRender
|
|||
void mouseMoveEvent (QMouseEvent *event) override;
|
||||
void wheelEvent (QWheelEvent *event) override;
|
||||
|
||||
osg::ref_ptr<osg::Geometry> createGradientRectangle(QColor bgColour, QColor gradientColour);
|
||||
osg::ref_ptr<osg::Camera> createGradientCamera(QColor bgColour, QColor gradientColour);
|
||||
void updateGradientCamera(QColor bgColour, QColor gradientColour);
|
||||
|
||||
std::shared_ptr<Resource::ResourceSystem> mResourceSystem;
|
||||
|
||||
Lighting* mLighting;
|
||||
|
||||
|
||||
osg::ref_ptr<osg::Camera> mGradientCamera;
|
||||
osg::Vec4f mDefaultAmbient;
|
||||
bool mHasDefaultAmbient;
|
||||
bool mIsExterior;
|
||||
|
|
|
@ -135,7 +135,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
|
||||
if (variables.count ("help"))
|
||||
{
|
||||
std::cout << desc << std::endl;
|
||||
getRawStdout() << desc << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
cfgMgr.readConfiguration(variables, desc, true);
|
||||
|
||||
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapePath>().mPath.string());
|
||||
std::cout << v.describe() << std::endl;
|
||||
getRawStdout() << v.describe() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -293,6 +293,9 @@ namespace MWBase
|
|||
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
|
||||
///< @return an updated Ptr
|
||||
|
||||
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0;
|
||||
///< @return an updated Ptr
|
||||
|
||||
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
||||
|
||||
virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z,
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
#include "loadingscreen.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/Version>
|
||||
|
||||
#include <MyGUI_RenderManager.h>
|
||||
#include <MyGUI_ScrollBar.h>
|
||||
|
@ -43,6 +45,8 @@ namespace MWGui
|
|||
, mNestedLoadingCount(0)
|
||||
, mProgress(0)
|
||||
, mShowWallpaper(true)
|
||||
, mOldCallback(nullptr)
|
||||
, mHasCallback(false)
|
||||
{
|
||||
mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize());
|
||||
|
||||
|
@ -136,24 +140,54 @@ namespace MWGui
|
|||
{
|
||||
public:
|
||||
CopyFramebufferToTextureCallback(osg::Texture2D* texture)
|
||||
: mTexture(texture)
|
||||
, oneshot(true)
|
||||
: mOneshot(true)
|
||||
, mTexture(texture)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () (osg::RenderInfo& renderInfo) const override
|
||||
{
|
||||
if (!oneshot)
|
||||
return;
|
||||
oneshot = false;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mOneshot = false;
|
||||
}
|
||||
mSignal.notify_all();
|
||||
|
||||
int w = renderInfo.getCurrentCamera()->getViewport()->width();
|
||||
int h = renderInfo.getCurrentCamera()->getViewport()->height();
|
||||
mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h);
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mOneshot = false;
|
||||
}
|
||||
mSignal.notify_all();
|
||||
}
|
||||
|
||||
void wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
while (mOneshot)
|
||||
mSignal.wait(lock);
|
||||
}
|
||||
|
||||
void waitUntilInvoked()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
while (mOneshot)
|
||||
mSignal.wait(lock);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
mOneshot = true;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable bool mOneshot;
|
||||
mutable std::mutex mMutex;
|
||||
mutable std::condition_variable mSignal;
|
||||
osg::ref_ptr<osg::Texture2D> mTexture;
|
||||
mutable bool oneshot;
|
||||
};
|
||||
|
||||
class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback
|
||||
|
@ -322,9 +356,20 @@ namespace MWGui
|
|||
mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture));
|
||||
}
|
||||
|
||||
// Notice that the next time this is called, the current CopyFramebufferToTextureCallback will be deleted
|
||||
// so there's no memory leak as at most one object of type CopyFramebufferToTextureCallback is allocated at a time.
|
||||
mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture));
|
||||
if (!mCopyFramebufferToTextureCallback)
|
||||
{
|
||||
mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture);
|
||||
}
|
||||
|
||||
#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
|
||||
mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback);
|
||||
#else
|
||||
// TODO: Remove once we officially end support for OSG versions pre 3.5.10
|
||||
mOldCallback = mViewer->getCamera()->getInitialDrawCallback();
|
||||
mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback);
|
||||
#endif
|
||||
mCopyFramebufferToTextureCallback->reset();
|
||||
mHasCallback = true;
|
||||
|
||||
mBackgroundImage->setBackgroundImage("");
|
||||
mBackgroundImage->setVisible(false);
|
||||
|
@ -367,6 +412,21 @@ namespace MWGui
|
|||
mViewer->renderingTraversals();
|
||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||
|
||||
if (mHasCallback)
|
||||
{
|
||||
mCopyFramebufferToTextureCallback->waitUntilInvoked();
|
||||
|
||||
// Note that we are removing the callback before the draw thread has returned from it.
|
||||
// This is OK as we are retaining the ref_ptr.
|
||||
#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
|
||||
mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback);
|
||||
#else
|
||||
// TODO: Remove once we officially end support for OSG versions pre 3.5.10
|
||||
mViewer->getCamera()->setInitialDrawCallback(mOldCallback);
|
||||
#endif
|
||||
mHasCallback = false;
|
||||
}
|
||||
|
||||
mLastRenderTime = mTimer.time_m();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <osg/Camera>
|
||||
#include <osg/Timer>
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
|
@ -28,6 +29,7 @@ namespace Resource
|
|||
namespace MWGui
|
||||
{
|
||||
class BackgroundImage;
|
||||
class CopyFramebufferToTextureCallback;
|
||||
|
||||
class LoadingScreen : public WindowBase, public Loading::Listener
|
||||
{
|
||||
|
@ -84,6 +86,9 @@ namespace MWGui
|
|||
std::vector<std::string> mSplashScreens;
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> mTexture;
|
||||
osg::ref_ptr<CopyFramebufferToTextureCallback> mCopyFramebufferToTextureCallback;
|
||||
osg::ref_ptr<osg::Camera::DrawCallback> mOldCallback;
|
||||
bool mHasCallback;
|
||||
std::unique_ptr<MyGUI::ITexture> mGuiTexture;
|
||||
|
||||
void changeWallpaper();
|
||||
|
|
|
@ -339,6 +339,15 @@ namespace MWGui
|
|||
{
|
||||
int max = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iLevelUpTotal")->mValue.getInteger();
|
||||
getWidget(levelWidget, i==0 ? "Level_str" : "LevelText");
|
||||
|
||||
std::string detail;
|
||||
for (int i = 0; i < ESM::Attribute::Length; ++i)
|
||||
{
|
||||
if (auto increase = PCstats.getLevelUpAttributeIncrease(i))
|
||||
detail += (detail.empty() ? "" : "\n") + ESM::Attribute::sAttributeNames[i] + " x" + MyGUI::utility::toString(increase);
|
||||
}
|
||||
if (!detail.empty())
|
||||
levelWidget->setUserString("Caption_LevelDetailText", detail);
|
||||
levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress()));
|
||||
levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max));
|
||||
levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/"
|
||||
|
|
|
@ -322,6 +322,11 @@ void MWMechanics::NpcStats::updateHealth()
|
|||
setHealth(floor(0.5f * (strength + endurance)));
|
||||
}
|
||||
|
||||
int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const
|
||||
{
|
||||
return mSkillIncreases[attribute];
|
||||
}
|
||||
|
||||
int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
|
||||
{
|
||||
int num = mSkillIncreases[attribute];
|
||||
|
|
|
@ -87,6 +87,8 @@ namespace MWMechanics
|
|||
|
||||
int getLevelProgress() const;
|
||||
|
||||
int getLevelUpAttributeIncrease(int attribute) const;
|
||||
|
||||
int getLevelupAttributeMultiplier(int attribute) const;
|
||||
|
||||
int getSkillIncreasesForSpecialization(int spec) const;
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace MWPhysics
|
|||
|
||||
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
|
||||
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
|
||||
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBoxTranslate), mHalfExtents(shape->mCollisionBoxHalfExtents)
|
||||
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents)
|
||||
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
||||
, mInternalCollisionMode(true)
|
||||
, mExternalCollisionMode(true)
|
||||
|
@ -76,6 +76,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
|
|||
updateScale();
|
||||
resetPosition();
|
||||
addCollisionMask(getCollisionMask());
|
||||
updateCollisionObjectPosition();
|
||||
}
|
||||
|
||||
Actor::~Actor()
|
||||
|
@ -120,6 +121,8 @@ int Actor::getCollisionMask() const
|
|||
|
||||
void Actor::updatePositionUnsafe()
|
||||
{
|
||||
if (!mWorldPositionChanged && mWorldPosition != mPtr.getRefData().getPosition().asVec3())
|
||||
mWorldPositionChanged = true;
|
||||
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
|
||||
}
|
||||
|
||||
|
@ -137,7 +140,9 @@ osg::Vec3f Actor::getWorldPosition() const
|
|||
|
||||
void Actor::setSimulationPosition(const osg::Vec3f& position)
|
||||
{
|
||||
mSimulationPosition = position;
|
||||
if (!mResetSimulation)
|
||||
mSimulationPosition = position;
|
||||
mResetSimulation = false;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getSimulationPosition() const
|
||||
|
@ -153,6 +158,7 @@ void Actor::updateCollisionObjectPositionUnsafe()
|
|||
mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
|
||||
mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation));
|
||||
mCollisionObject->setWorldTransform(mLocalTransform);
|
||||
mWorldPositionChanged = false;
|
||||
}
|
||||
|
||||
void Actor::updateCollisionObjectPosition()
|
||||
|
@ -167,18 +173,20 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
|
|||
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
|
||||
}
|
||||
|
||||
void Actor::setPosition(const osg::Vec3f& position)
|
||||
bool Actor::setPosition(const osg::Vec3f& position)
|
||||
{
|
||||
std::scoped_lock lock(mPositionMutex);
|
||||
mPreviousPosition = mPosition;
|
||||
mPosition = position;
|
||||
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
|
||||
mPreviousPosition = mPosition + mPositionOffset;
|
||||
mPosition = position + mPositionOffset;
|
||||
mPositionOffset = osg::Vec3f();
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
void Actor::adjustPosition(const osg::Vec3f& offset)
|
||||
{
|
||||
std::scoped_lock lock(mPositionMutex);
|
||||
mPosition += offset;
|
||||
mPreviousPosition += offset;
|
||||
mPositionOffset += offset;
|
||||
}
|
||||
|
||||
void Actor::resetPosition()
|
||||
|
@ -188,7 +196,9 @@ void Actor::resetPosition()
|
|||
mPreviousPosition = mWorldPosition;
|
||||
mPosition = mWorldPosition;
|
||||
mSimulationPosition = mWorldPosition;
|
||||
updateCollisionObjectPositionUnsafe();
|
||||
mStandingOnPtr = nullptr;
|
||||
mWorldPositionChanged = false;
|
||||
mResetSimulation = true;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getPosition() const
|
||||
|
|
|
@ -90,8 +90,9 @@ namespace MWPhysics
|
|||
|
||||
/**
|
||||
* Store the current position into mPreviousPosition, then move to this position.
|
||||
* Returns true if the new position is different.
|
||||
*/
|
||||
void setPosition(const osg::Vec3f& position);
|
||||
bool setPosition(const osg::Vec3f& position);
|
||||
void resetPosition();
|
||||
void adjustPosition(const osg::Vec3f& offset);
|
||||
|
||||
|
@ -177,6 +178,9 @@ namespace MWPhysics
|
|||
osg::Vec3f mSimulationPosition;
|
||||
osg::Vec3f mPosition;
|
||||
osg::Vec3f mPreviousPosition;
|
||||
osg::Vec3f mPositionOffset;
|
||||
bool mWorldPositionChanged;
|
||||
bool mResetSimulation;
|
||||
btTransform mLocalTransform;
|
||||
mutable std::mutex mPositionMutex;
|
||||
|
||||
|
|
|
@ -30,12 +30,9 @@ namespace MWPhysics
|
|||
return btScalar(1);
|
||||
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
|
||||
const MWWorld::Ptr target = targetHolder->getPtr();
|
||||
// do nothing if we hit the caster. Sometimes the launching origin is inside of caster collision shape
|
||||
if (projectileHolder->getCaster() != target)
|
||||
{
|
||||
if (projectileHolder->isValidTarget(target))
|
||||
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
|
||||
return btScalar(1);
|
||||
}
|
||||
return btScalar(1);
|
||||
}
|
||||
|
||||
btVector3 hitNormalWorld;
|
||||
|
|
|
@ -24,6 +24,10 @@ namespace MWPhysics
|
|||
{
|
||||
if (rayResult.m_collisionObject == mMe)
|
||||
return 1.f;
|
||||
|
||||
if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject())
|
||||
return 1.f;
|
||||
|
||||
if (!mTargets.empty())
|
||||
{
|
||||
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
|
||||
|
|
|
@ -100,15 +100,6 @@ namespace
|
|||
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
|
||||
{
|
||||
const float interpolationFactor = timeAccum / physicsDt;
|
||||
|
||||
// account for force change of actor's position in the main thread
|
||||
const auto correction = actorData.mActorRaw->getWorldPosition() - actorData.mOrigin;
|
||||
if (correction.length() != 0)
|
||||
{
|
||||
actorData.mActorRaw->adjustPosition(correction);
|
||||
actorData.mPosition = actorData.mActorRaw->getPosition();
|
||||
}
|
||||
|
||||
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
|
||||
}
|
||||
|
||||
|
@ -213,31 +204,31 @@ namespace MWPhysics
|
|||
thread.join();
|
||||
}
|
||||
|
||||
const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
||||
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
||||
{
|
||||
// This function run in the main thread.
|
||||
// While the mSimulationMutex is held, background physics threads can't run.
|
||||
|
||||
std::unique_lock lock(mSimulationMutex);
|
||||
|
||||
for (auto& data : actorsData)
|
||||
data.updatePosition();
|
||||
mMovedActors.clear();
|
||||
|
||||
// start by finishing previous background computation
|
||||
if (mNumThreads != 0)
|
||||
{
|
||||
for (auto& data : mActorsFrameData)
|
||||
{
|
||||
// Ignore actors that were deleted while the background thread was running
|
||||
if (!data.mActor.lock())
|
||||
continue;
|
||||
// Only return actors that are still part of the scene
|
||||
if (std::any_of(actorsData.begin(), actorsData.end(), [&data](const auto& newFrameData) { return data.mActorRaw->getPtr() == newFrameData.mActorRaw->getPtr(); }))
|
||||
{
|
||||
updateMechanics(data);
|
||||
|
||||
updateMechanics(data);
|
||||
if (mAdvanceSimulation)
|
||||
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
|
||||
|
||||
if (mMovementResults.find(data.mPtr) != mMovementResults.end())
|
||||
data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]);
|
||||
// these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values
|
||||
if (mAdvanceSimulation)
|
||||
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
|
||||
data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt));
|
||||
mMovedActors.emplace_back(data.mActorRaw->getPtr());
|
||||
}
|
||||
}
|
||||
|
||||
if (mFrameNumber == frameNumber - 1)
|
||||
|
@ -252,6 +243,8 @@ namespace MWPhysics
|
|||
}
|
||||
|
||||
// init
|
||||
for (auto& data : actorsData)
|
||||
data.updatePosition();
|
||||
mRemainingSteps = numSteps;
|
||||
mTimeAccum = timeAccum;
|
||||
mActorsFrameData = std::move(actorsData);
|
||||
|
@ -266,52 +259,28 @@ namespace MWPhysics
|
|||
|
||||
if (mNumThreads == 0)
|
||||
{
|
||||
mMovementResults.clear();
|
||||
syncComputation();
|
||||
|
||||
for (auto& data : mActorsFrameData)
|
||||
{
|
||||
if (mAdvanceSimulation)
|
||||
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
|
||||
if (mMovementResults.find(data.mPtr) != mMovementResults.end())
|
||||
data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]);
|
||||
}
|
||||
return mMovementResults;
|
||||
return mMovedActors;
|
||||
}
|
||||
|
||||
// Remove actors that were deleted while the background thread was running
|
||||
for (auto& data : mActorsFrameData)
|
||||
{
|
||||
if (!data.mActor.lock())
|
||||
mMovementResults.erase(data.mPtr);
|
||||
}
|
||||
std::swap(mMovementResults, mPreviousMovementResults);
|
||||
|
||||
// mMovementResults is shared between all workers instance
|
||||
// pre-allocate all nodes so that we don't need synchronization
|
||||
mMovementResults.clear();
|
||||
for (const auto& m : mActorsFrameData)
|
||||
mMovementResults[m.mPtr] = m.mPosition;
|
||||
|
||||
lock.unlock();
|
||||
mHasJob.notify_all();
|
||||
return mPreviousMovementResults;
|
||||
return mMovedActors;
|
||||
}
|
||||
|
||||
const PtrPositionList& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
|
||||
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
|
||||
{
|
||||
std::unique_lock lock(mSimulationMutex);
|
||||
mMovementResults.clear();
|
||||
mPreviousMovementResults.clear();
|
||||
mMovedActors.clear();
|
||||
mActorsFrameData.clear();
|
||||
|
||||
for (const auto& [_, actor] : actors)
|
||||
{
|
||||
actor->resetPosition();
|
||||
actor->setStandingOnPtr(nullptr);
|
||||
mMovementResults[actor->getPtr()] = actor->getWorldPosition();
|
||||
actor->setSimulationPosition(actor->getWorldPosition()); // resetPosition skip next simulation, now we need to "consume" it
|
||||
actor->updateCollisionObjectPosition();
|
||||
mMovedActors.emplace_back(actor->getPtr());
|
||||
}
|
||||
return mMovementResults;
|
||||
return mMovedActors;
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
|
||||
|
@ -379,18 +348,18 @@ namespace MWPhysics
|
|||
mCollisionWorld->removeCollisionObject(collisionObject);
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr)
|
||||
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate)
|
||||
{
|
||||
if (mDeferAabbUpdate)
|
||||
{
|
||||
std::unique_lock lock(mUpdateAabbMutex);
|
||||
mUpdateAabb.insert(std::move(ptr));
|
||||
}
|
||||
else
|
||||
if (!mDeferAabbUpdate || immediate)
|
||||
{
|
||||
std::unique_lock lock(mCollisionWorldMutex);
|
||||
updatePtrAabb(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_lock lock(mUpdateAabbMutex);
|
||||
mUpdateAabb.insert(std::move(ptr));
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2)
|
||||
|
@ -493,7 +462,6 @@ namespace MWPhysics
|
|||
{
|
||||
auto& actorData = mActorsFrameData[job];
|
||||
handleFall(actorData, mAdvanceSimulation);
|
||||
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -511,9 +479,7 @@ namespace MWPhysics
|
|||
{
|
||||
if(const auto actor = actorData.mActor.lock())
|
||||
{
|
||||
bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition();
|
||||
actorData.mActorRaw->setPosition(actorData.mPosition);
|
||||
if (positionChanged)
|
||||
if (actor->setPosition(actorData.mPosition))
|
||||
{
|
||||
actor->updateCollisionObjectPosition();
|
||||
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
|
||||
|
@ -550,8 +516,11 @@ namespace MWPhysics
|
|||
for (auto& actorData : mActorsFrameData)
|
||||
{
|
||||
handleFall(actorData, mAdvanceSimulation);
|
||||
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
|
||||
actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt));
|
||||
updateMechanics(actorData);
|
||||
mMovedActors.emplace_back(actorData.mActorRaw->getPtr());
|
||||
if (mAdvanceSimulation)
|
||||
actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,9 +32,9 @@ namespace MWPhysics
|
|||
/// @param timeAccum accumulated time from previous run to interpolate movements
|
||||
/// @param actorsData per actor data needed to compute new positions
|
||||
/// @return new position of each actor
|
||||
const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||
const std::vector<MWWorld::Ptr>& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||
|
||||
const PtrPositionList& resetSimulation(const ActorMap& actors);
|
||||
const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors);
|
||||
|
||||
// Thread safe wrappers
|
||||
void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const;
|
||||
|
@ -46,7 +46,7 @@ namespace MWPhysics
|
|||
void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask);
|
||||
void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask);
|
||||
void removeCollisionObject(btCollisionObject* collisionObject);
|
||||
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr);
|
||||
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
|
||||
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
|
||||
|
||||
private:
|
||||
|
@ -60,8 +60,7 @@ namespace MWPhysics
|
|||
|
||||
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
||||
std::vector<ActorFrameData> mActorsFrameData;
|
||||
PtrPositionList mMovementResults;
|
||||
PtrPositionList mPreviousMovementResults;
|
||||
std::vector<MWWorld::Ptr> mMovedActors;
|
||||
const float mPhysicsDt;
|
||||
float mTimeAccum;
|
||||
std::shared_ptr<btCollisionWorld> mCollisionWorld;
|
||||
|
|
|
@ -437,7 +437,7 @@ namespace MWPhysics
|
|||
ActorMap::iterator found = mActors.find(ptr);
|
||||
if (found == mActors.end())
|
||||
return ptr.getRefData().getPosition().asVec3();
|
||||
found->second->resetPosition();
|
||||
resetPosition(ptr);
|
||||
return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight);
|
||||
}
|
||||
|
||||
|
@ -537,6 +537,13 @@ namespace MWPhysics
|
|||
if (actor->getStandingOnPtr() == old)
|
||||
actor->setStandingOnPtr(updated);
|
||||
}
|
||||
|
||||
for (auto& [_, projectile] : mProjectiles)
|
||||
{
|
||||
if (projectile->getCaster() == old)
|
||||
projectile->setCaster(updated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr)
|
||||
|
@ -640,12 +647,23 @@ namespace MWPhysics
|
|||
}
|
||||
}
|
||||
|
||||
void PhysicsSystem::resetPosition(const MWWorld::ConstPtr &ptr)
|
||||
{
|
||||
ActorMap::iterator foundActor = mActors.find(ptr);
|
||||
if (foundActor != mActors.end())
|
||||
{
|
||||
foundActor->second->resetPosition();
|
||||
mTaskScheduler->updateSingleAabb(foundActor->second, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh)
|
||||
{
|
||||
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
|
||||
|
||||
// Try to get shape from basic model as fallback for creatures
|
||||
if (!ptr.getClass().isNpc() && shape && shape->mCollisionBoxHalfExtents.length2() == 0)
|
||||
if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.extents.length2() == 0)
|
||||
{
|
||||
const std::string fallbackModel = ptr.getClass().getModel(ptr);
|
||||
if (fallbackModel != mesh)
|
||||
|
@ -676,6 +694,8 @@ namespace MWPhysics
|
|||
if (found != mActors.end())
|
||||
{
|
||||
bool cmode = found->second->getCollisionMode();
|
||||
if (cmode)
|
||||
resetPosition(found->first);
|
||||
cmode = !cmode;
|
||||
found->second->enableCollisionMode(cmode);
|
||||
// NB: Collision body isn't disabled for vanilla TCL compatibility
|
||||
|
@ -704,7 +724,7 @@ namespace MWPhysics
|
|||
mMovementQueue.clear();
|
||||
}
|
||||
|
||||
const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
||||
const std::vector<MWWorld::Ptr>& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
||||
{
|
||||
mTimeAccum += dt;
|
||||
|
||||
|
@ -923,7 +943,6 @@ namespace MWPhysics
|
|||
void ActorFrameData::updatePosition()
|
||||
{
|
||||
mActorRaw->updatePosition();
|
||||
mOrigin = mActorRaw->getSimulationPosition();
|
||||
mPosition = mActorRaw->getPosition();
|
||||
if (mMoveToWaterSurface)
|
||||
{
|
||||
|
|
|
@ -50,8 +50,6 @@ class btVector3;
|
|||
|
||||
namespace MWPhysics
|
||||
{
|
||||
using PtrPositionList = std::map<MWWorld::Ptr, osg::Vec3f>;
|
||||
|
||||
class HeightField;
|
||||
class Object;
|
||||
class Actor;
|
||||
|
@ -99,7 +97,6 @@ namespace MWPhysics
|
|||
float mOldHeight;
|
||||
float mFallHeight;
|
||||
osg::Vec3f mMovement;
|
||||
osg::Vec3f mOrigin;
|
||||
osg::Vec3f mPosition;
|
||||
ESM::Position mRefpos;
|
||||
};
|
||||
|
@ -147,6 +144,7 @@ namespace MWPhysics
|
|||
void updateScale (const MWWorld::Ptr& ptr);
|
||||
void updateRotation (const MWWorld::Ptr& ptr);
|
||||
void updatePosition (const MWWorld::Ptr& ptr);
|
||||
void resetPosition(const MWWorld::ConstPtr &ptr);
|
||||
|
||||
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
|
||||
|
||||
|
@ -210,7 +208,7 @@ namespace MWPhysics
|
|||
void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);
|
||||
|
||||
/// Apply all queued movements, then clear the list.
|
||||
const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||
const std::vector<MWWorld::Ptr>& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||
|
||||
/// Clear the queued movements list without applying.
|
||||
void clearQueuedMovement();
|
||||
|
|
|
@ -55,7 +55,7 @@ Projectile::~Projectile()
|
|||
|
||||
void Projectile::commitPositionChange()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
std::scoped_lock lock(mMutex);
|
||||
if (mTransformUpdatePending)
|
||||
{
|
||||
mCollisionObject->setWorldTransform(mLocalTransform);
|
||||
|
@ -65,7 +65,7 @@ void Projectile::commitPositionChange()
|
|||
|
||||
void Projectile::setPosition(const osg::Vec3f &position)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
std::scoped_lock lock(mMutex);
|
||||
mLocalTransform.setOrigin(Misc::Convert::toBullet(position));
|
||||
mTransformUpdatePending = true;
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
|
|||
{
|
||||
if (!mActive.load(std::memory_order_acquire))
|
||||
return;
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
std::scoped_lock lock(mMutex);
|
||||
mHitTarget = target;
|
||||
mHitPosition = pos;
|
||||
mHitNormal = normal;
|
||||
|
@ -86,4 +86,46 @@ void Projectile::activate()
|
|||
assert(!mActive);
|
||||
mActive.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
MWWorld::Ptr Projectile::getCaster() const
|
||||
{
|
||||
std::scoped_lock lock(mMutex);
|
||||
return mCaster;
|
||||
}
|
||||
|
||||
void Projectile::setCaster(MWWorld::Ptr caster)
|
||||
{
|
||||
std::scoped_lock lock(mMutex);
|
||||
mCaster = caster;
|
||||
}
|
||||
|
||||
void Projectile::setValidTargets(const std::vector<MWWorld::Ptr>& targets)
|
||||
{
|
||||
std::scoped_lock lock(mMutex);
|
||||
mValidTargets = targets;
|
||||
}
|
||||
|
||||
bool Projectile::isValidTarget(const MWWorld::Ptr& target) const
|
||||
{
|
||||
std::scoped_lock lock(mMutex);
|
||||
if (mCaster == target)
|
||||
return false;
|
||||
|
||||
if (!mValidTargets.empty())
|
||||
{
|
||||
bool validTarget = false;
|
||||
for (const auto& targetActor : mValidTargets)
|
||||
{
|
||||
if (targetActor == target)
|
||||
{
|
||||
validTarget = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return validTarget;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -62,7 +62,8 @@ namespace MWPhysics
|
|||
return mHitTarget;
|
||||
}
|
||||
|
||||
MWWorld::Ptr getCaster() const { return mCaster; }
|
||||
MWWorld::Ptr getCaster() const;
|
||||
void setCaster(MWWorld::Ptr caster);
|
||||
|
||||
osg::Vec3f getHitPos() const
|
||||
{
|
||||
|
@ -73,6 +74,9 @@ namespace MWPhysics
|
|||
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
|
||||
void activate();
|
||||
|
||||
void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
|
||||
bool isValidTarget(const MWWorld::Ptr& target) const;
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<btCollisionShape> mShape;
|
||||
|
@ -87,7 +91,9 @@ namespace MWPhysics
|
|||
btVector3 mHitPosition;
|
||||
btVector3 mHitNormal;
|
||||
|
||||
mutable std::mutex mPositionMutex;
|
||||
std::vector<MWWorld::Ptr> mValidTargets;
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
|
||||
osg::Vec3f mPosition;
|
||||
|
||||
|
|
|
@ -783,8 +783,6 @@ namespace MWRender
|
|||
NodeMap::const_iterator found = nodeMap.find("bip01");
|
||||
if (found == nodeMap.end())
|
||||
found = nodeMap.find("root bone");
|
||||
if (found == nodeMap.end())
|
||||
found = nodeMap.find("root");
|
||||
|
||||
if (found != nodeMap.end())
|
||||
mAccumRoot = found->second;
|
||||
|
|
|
@ -32,11 +32,7 @@ namespace MWScript
|
|||
std::vector<MWWorld::Ptr> actors;
|
||||
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
|
||||
for (auto& actor : actors)
|
||||
{
|
||||
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
|
||||
actorPos += diff;
|
||||
MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z());
|
||||
}
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff);
|
||||
}
|
||||
|
||||
template<class R>
|
||||
|
@ -727,14 +723,12 @@ namespace MWScript
|
|||
return;
|
||||
|
||||
osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange;
|
||||
osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3());
|
||||
worldPos += diff;
|
||||
|
||||
// We should move actors, standing on moving object, too.
|
||||
// This approach can be used to create elevators.
|
||||
moveStandingActors(ptr, diff);
|
||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
|
||||
MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z()));
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -755,15 +749,14 @@ namespace MWScript
|
|||
Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
|
||||
runtime.pop();
|
||||
|
||||
const float *objPos = ptr.getRefData().getPosition().pos;
|
||||
osg::Vec3f diff;
|
||||
|
||||
if (axis == "x")
|
||||
diff.x() += movement;
|
||||
diff.x() = movement;
|
||||
else if (axis == "y")
|
||||
diff.y() += movement;
|
||||
diff.y() = movement;
|
||||
else if (axis == "z")
|
||||
diff.z() += movement;
|
||||
diff.z() = movement;
|
||||
else
|
||||
return;
|
||||
|
||||
|
@ -771,7 +764,7 @@ namespace MWScript
|
|||
// This approach can be used to create elevators.
|
||||
moveStandingActors(ptr, diff);
|
||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
|
||||
MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+diff.x(), objPos[1]+diff.y(), objPos[2]+diff.z()));
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -401,7 +401,7 @@ namespace MWWorld
|
|||
if (magicBoltState.mToDelete)
|
||||
continue;
|
||||
|
||||
const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
|
||||
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
|
||||
if (!projectile->isActive())
|
||||
continue;
|
||||
// If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame.
|
||||
|
@ -437,6 +437,7 @@ namespace MWWorld
|
|||
std::vector<MWWorld::Ptr> targetActors;
|
||||
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
|
||||
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
|
||||
projectile->setValidTargets(targetActors);
|
||||
|
||||
// Check for impact
|
||||
// TODO: use a proper btRigidBody / btGhostObject?
|
||||
|
@ -483,7 +484,7 @@ namespace MWWorld
|
|||
if (projectileState.mToDelete)
|
||||
continue;
|
||||
|
||||
const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
|
||||
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
|
||||
if (!projectile->isActive())
|
||||
continue;
|
||||
// gravity constant - must be way lower than the gravity affecting actors, since we're not
|
||||
|
@ -513,6 +514,7 @@ namespace MWWorld
|
|||
std::vector<MWWorld::Ptr> targetActors;
|
||||
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
|
||||
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
|
||||
projectile->setValidTargets(targetActors);
|
||||
|
||||
// Check for impact
|
||||
// TODO: use a proper btRigidBody / btGhostObject?
|
||||
|
@ -561,7 +563,7 @@ namespace MWWorld
|
|||
const auto pos = projectile->getHitPos();
|
||||
MWWorld::Ptr caster = projectileState.getCaster();
|
||||
assert(target != caster);
|
||||
if (!isValidTarget(caster, target))
|
||||
if (!projectile->isValidTarget(target))
|
||||
{
|
||||
projectile->activate();
|
||||
continue;
|
||||
|
@ -597,7 +599,7 @@ namespace MWWorld
|
|||
const auto pos = projectile->getHitPos();
|
||||
MWWorld::Ptr caster = magicBoltState.getCaster();
|
||||
assert(target != caster);
|
||||
if (!isValidTarget(caster, target))
|
||||
if (!projectile->isValidTarget(target))
|
||||
{
|
||||
projectile->activate();
|
||||
continue;
|
||||
|
@ -621,32 +623,6 @@ namespace MWWorld
|
|||
mMagicBolts.end());
|
||||
}
|
||||
|
||||
bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||
{
|
||||
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
|
||||
std::vector<MWWorld::Ptr> targetActors;
|
||||
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
|
||||
{
|
||||
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
|
||||
if (!targetActors.empty())
|
||||
{
|
||||
bool validTarget = false;
|
||||
for (MWWorld::Ptr& targetActor : targetActors)
|
||||
{
|
||||
if (targetActor == target)
|
||||
{
|
||||
validTarget = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return validTarget;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
|
||||
{
|
||||
mParent->removeChild(state.mNode);
|
||||
|
|
|
@ -132,8 +132,6 @@ namespace MWWorld
|
|||
void moveProjectiles(float dt);
|
||||
void moveMagicBolts(float dt);
|
||||
|
||||
bool isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||
|
||||
void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient,
|
||||
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = "");
|
||||
void update (State& state, float duration);
|
||||
|
|
|
@ -1293,6 +1293,18 @@ namespace MWWorld
|
|||
return moveObjectImp(ptr, x, y, z, true, moveToActive);
|
||||
}
|
||||
|
||||
MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec)
|
||||
{
|
||||
auto* actor = mPhysics->getActor(ptr);
|
||||
if (actor)
|
||||
{
|
||||
actor->adjustPosition(vec);
|
||||
return ptr;
|
||||
}
|
||||
osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec;
|
||||
return moveObject(ptr, newpos.x(), newpos.y(), newpos.z());
|
||||
}
|
||||
|
||||
void World::scaleObject (const Ptr& ptr, float scale)
|
||||
{
|
||||
if (mPhysics->getActor(ptr))
|
||||
|
@ -1386,12 +1398,7 @@ namespace MWWorld
|
|||
}
|
||||
|
||||
moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z());
|
||||
if (force) // force physics to use the new position
|
||||
{
|
||||
auto actor = mPhysics->getActor(ptr);
|
||||
if(actor)
|
||||
actor->resetPosition();
|
||||
}
|
||||
mPhysics->resetPosition(ptr);
|
||||
}
|
||||
|
||||
void World::fixPosition()
|
||||
|
@ -1538,20 +1545,30 @@ namespace MWWorld
|
|||
|
||||
mProjectileManager->update(duration);
|
||||
|
||||
const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
|
||||
const auto& results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
|
||||
mProjectileManager->processHits();
|
||||
mDiscardMovements = false;
|
||||
|
||||
for(const auto& [actor, position]: results)
|
||||
for(const auto& actor : results)
|
||||
{
|
||||
// Handle player last, in case a cell transition occurs
|
||||
if(actor != getPlayerPtr())
|
||||
{
|
||||
auto* physactor = mPhysics->getActor(actor);
|
||||
assert(physactor);
|
||||
const auto position = physactor->getSimulationPosition();
|
||||
moveObjectImp(actor, position.x(), position.y(), position.z(), false);
|
||||
}
|
||||
}
|
||||
|
||||
const auto player = results.find(getPlayerPtr());
|
||||
const auto player = std::find(results.begin(), results.end(), getPlayerPtr());
|
||||
if (player != results.end())
|
||||
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false);
|
||||
{
|
||||
auto* physactor = mPhysics->getActor(*player);
|
||||
assert(physactor);
|
||||
const auto position = physactor->getSimulationPosition();
|
||||
moveObjectImp(*player, position.x(), position.y(), position.z(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void World::updateNavigator()
|
||||
|
|
|
@ -385,6 +385,9 @@ namespace MWWorld
|
|||
MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override;
|
||||
///< @return an updated Ptr
|
||||
|
||||
MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec) override;
|
||||
///< @return an updated Ptr
|
||||
|
||||
void scaleObject (const Ptr& ptr, float scale) override;
|
||||
|
||||
/// World rotates object, uses radians
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
find_package(GTest REQUIRED)
|
||||
find_package(GMock REQUIRED)
|
||||
find_package(GTest 1.10 REQUIRED)
|
||||
find_package(GMock 1.10 REQUIRED)
|
||||
|
||||
if (GTEST_FOUND AND GMOCK_FOUND)
|
||||
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace
|
|||
struct DetourNavigatorRecastMeshObjectTest : Test
|
||||
{
|
||||
btBoxShape mBoxShape {btVector3(1, 2, 3)};
|
||||
btCompoundShape mCompoundShape {btVector3(1, 2, 3)};
|
||||
btCompoundShape mCompoundShape {true};
|
||||
btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)};
|
||||
|
||||
DetourNavigatorRecastMeshObjectTest()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <boost/filesystem/fstream.hpp>
|
||||
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
#include <components/files/escape.hpp>
|
||||
#include <components/esm/esmreader.hpp>
|
||||
#include <components/esm/esmwriter.hpp>
|
||||
#include <components/loadinglistener/loadinglistener.hpp>
|
||||
|
@ -58,10 +59,10 @@ struct ContentFileTest : public ::testing::Test
|
|||
|
||||
boost::program_options::options_description desc("Allowed options");
|
||||
desc.add_options()
|
||||
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken()->composing())
|
||||
("content", boost::program_options::value<std::vector<std::string> >()->default_value(std::vector<std::string>(), "")
|
||||
->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon")
|
||||
("data-local", boost::program_options::value<std::string>()->default_value(""));
|
||||
("data", boost::program_options::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing())
|
||||
("content", boost::program_options::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
|
||||
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
|
||||
("data-local", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), ""));
|
||||
|
||||
boost::program_options::notify(variables);
|
||||
|
||||
|
@ -69,12 +70,12 @@ struct ContentFileTest : public ::testing::Test
|
|||
|
||||
Files::PathContainer dataDirs, dataLocal;
|
||||
if (!variables["data"].empty()) {
|
||||
dataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>());
|
||||
dataDirs = Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>());
|
||||
}
|
||||
|
||||
std::string local = variables["data-local"].as<std::string>();
|
||||
Files::PathContainer::value_type local(variables["data-local"].as<Files::EscapePath>().mPath);
|
||||
if (!local.empty()) {
|
||||
dataLocal.push_back(Files::PathContainer::value_type(local));
|
||||
dataLocal.push_back(local);
|
||||
}
|
||||
|
||||
mConfigurationManager.processPaths (dataDirs);
|
||||
|
@ -85,7 +86,7 @@ struct ContentFileTest : public ::testing::Test
|
|||
|
||||
Files::Collections collections (dataDirs, true);
|
||||
|
||||
std::vector<std::string> contentFiles = variables["content"].as<std::vector<std::string> >();
|
||||
std::vector<std::string> contentFiles = variables["content"].as<Files::EscapeStringVector>().toStdStringVector();
|
||||
for (auto & contentFile : contentFiles)
|
||||
mContentFiles.push_back(collections.getPath(contentFile));
|
||||
}
|
||||
|
|
|
@ -162,8 +162,8 @@ namespace Resource
|
|||
{
|
||||
return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape)
|
||||
&& compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape)
|
||||
&& lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents
|
||||
&& lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate
|
||||
&& lhs.mCollisionBox.extents == rhs.mCollisionBox.extents
|
||||
&& lhs.mCollisionBox.center == rhs.mCollisionBox.center
|
||||
&& lhs.mAnimatedShapes == rhs.mAnimatedShapes;
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,8 @@ namespace Resource
|
|||
return stream << "Resource::BulletShape {"
|
||||
<< value.mCollisionShape << ", "
|
||||
<< value.mAvoidCollisionShape << ", "
|
||||
<< "osg::Vec3f {" << value.mCollisionBoxHalfExtents << "}" << ", "
|
||||
<< "osg::Vec3f {" << value.mCollisionBox.extents << "}" << ", "
|
||||
<< "osg::Vec3f {" << value.mCollisionBox.center << "}" << ", "
|
||||
<< value.mAnimatedShapes
|
||||
<< "}";
|
||||
}
|
||||
|
@ -258,14 +259,19 @@ namespace
|
|||
value.isBone = false;
|
||||
}
|
||||
|
||||
void init(Nif::NiTriShape& value)
|
||||
void init(Nif::NiGeometry& value)
|
||||
{
|
||||
init(static_cast<Nif::Node&>(value));
|
||||
value.recType = Nif::RC_NiTriShape;
|
||||
value.data = Nif::NiTriShapeDataPtr(nullptr);
|
||||
value.data = Nif::NiGeometryDataPtr(nullptr);
|
||||
value.skin = Nif::NiSkinInstancePtr(nullptr);
|
||||
}
|
||||
|
||||
void init(Nif::NiTriShape& value)
|
||||
{
|
||||
init(static_cast<Nif::NiGeometry&>(value));
|
||||
value.recType = Nif::RC_NiTriShape;
|
||||
}
|
||||
|
||||
void init(Nif::NiSkinInstance& value)
|
||||
{
|
||||
value.data = Nif::NiSkinDataPtr(nullptr);
|
||||
|
@ -293,22 +299,22 @@ namespace
|
|||
|
||||
struct NifFileMock : Nif::File
|
||||
{
|
||||
MOCK_CONST_METHOD1(getRecord, Nif::Record* (std::size_t));
|
||||
MOCK_CONST_METHOD0(numRecords, std::size_t ());
|
||||
MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t));
|
||||
MOCK_CONST_METHOD0(numRoots, std::size_t ());
|
||||
MOCK_CONST_METHOD1(getString, std::string (uint32_t));
|
||||
MOCK_METHOD1(setUseSkinning, void (bool));
|
||||
MOCK_CONST_METHOD0(getUseSkinning, bool ());
|
||||
MOCK_CONST_METHOD0(getFilename, std::string ());
|
||||
MOCK_CONST_METHOD0(getVersion, unsigned int ());
|
||||
MOCK_CONST_METHOD0(getUserVersion, unsigned int ());
|
||||
MOCK_CONST_METHOD0(getBethVersion, unsigned int ());
|
||||
MOCK_METHOD(Nif::Record*, getRecord, (std::size_t), (const, override));
|
||||
MOCK_METHOD(std::size_t, numRecords, (), (const, override));
|
||||
MOCK_METHOD(Nif::Record*, getRoot, (std::size_t), (const, override));
|
||||
MOCK_METHOD(std::size_t, numRoots, (), (const, override));
|
||||
MOCK_METHOD(std::string, getString, (uint32_t), (const, override));
|
||||
MOCK_METHOD(void, setUseSkinning, (bool), (override));
|
||||
MOCK_METHOD(bool, getUseSkinning, (), (const, override));
|
||||
MOCK_METHOD(std::string, getFilename, (), (const, override));
|
||||
MOCK_METHOD(unsigned int, getVersion, (), (const, override));
|
||||
MOCK_METHOD(unsigned int, getUserVersion, (), (const, override));
|
||||
MOCK_METHOD(unsigned int, getBethVersion, (), (const, override));
|
||||
};
|
||||
|
||||
struct RecordMock : Nif::Record
|
||||
{
|
||||
MOCK_METHOD1(read, void (Nif::NIFStream *nif));
|
||||
MOCK_METHOD(void, read, (Nif::NIFStream *nif), (override));
|
||||
};
|
||||
|
||||
struct TestBulletNifLoader : Test
|
||||
|
@ -360,13 +366,15 @@ namespace
|
|||
init(mNiStringExtraData2);
|
||||
init(mController);
|
||||
|
||||
mNiTriShapeData.recType = Nif::RC_NiTriShapeData;
|
||||
mNiTriShapeData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0)};
|
||||
mNiTriShapeData.triangles = {0, 1, 2};
|
||||
mNiTriShape.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData);
|
||||
mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData);
|
||||
|
||||
mNiTriShapeData2.recType = Nif::RC_NiTriShapeData;
|
||||
mNiTriShapeData2.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)};
|
||||
mNiTriShapeData2.triangles = {0, 1, 2};
|
||||
mNiTriShape2.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData2);
|
||||
mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -433,8 +441,8 @@ namespace
|
|||
const auto result = mLoader.load(mNifFile);
|
||||
|
||||
Resource::BulletShape expected;
|
||||
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
|
||||
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
|
||||
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
|
||||
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
|
||||
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
|
||||
|
@ -458,8 +466,8 @@ namespace
|
|||
const auto result = mLoader.load(mNifFile);
|
||||
|
||||
Resource::BulletShape expected;
|
||||
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
|
||||
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
|
||||
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
|
||||
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
|
||||
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
|
||||
|
@ -488,8 +496,8 @@ namespace
|
|||
const auto result = mLoader.load(mNifFile);
|
||||
|
||||
Resource::BulletShape expected;
|
||||
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
|
||||
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
|
||||
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
|
||||
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
|
||||
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
|
||||
|
@ -523,8 +531,8 @@ namespace
|
|||
const auto result = mLoader.load(mNifFile);
|
||||
|
||||
Resource::BulletShape expected;
|
||||
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
|
||||
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
|
||||
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
|
||||
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
|
||||
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
|
||||
|
@ -558,8 +566,8 @@ namespace
|
|||
const auto result = mLoader.load(mNifFile);
|
||||
|
||||
Resource::BulletShape expected;
|
||||
expected.mCollisionBoxHalfExtents = osg::Vec3f(4, 5, 6);
|
||||
expected.mCollisionBoxTranslate = osg::Vec3f(-4, -5, -6);
|
||||
expected.mCollisionBox.extents = osg::Vec3f(4, 5, 6);
|
||||
expected.mCollisionBox.center = osg::Vec3f(-4, -5, -6);
|
||||
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(4, 5, 6)));
|
||||
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
|
||||
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release());
|
||||
|
@ -581,8 +589,8 @@ namespace
|
|||
const auto result = mLoader.load(mNifFile);
|
||||
|
||||
Resource::BulletShape expected;
|
||||
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
|
||||
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
|
||||
|
||||
EXPECT_EQ(*result, expected);
|
||||
}
|
||||
|
@ -615,8 +623,8 @@ namespace
|
|||
const auto result = mLoader.load(mNifFile);
|
||||
|
||||
Resource::BulletShape expected;
|
||||
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
|
||||
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
|
||||
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
|
||||
|
||||
EXPECT_EQ(*result, expected);
|
||||
}
|
||||
|
@ -872,7 +880,7 @@ namespace
|
|||
|
||||
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape)
|
||||
{
|
||||
mNiTriShape.data = Nif::NiTriShapeDataPtr(nullptr);
|
||||
mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr);
|
||||
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
|
||||
|
||||
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
|
||||
|
@ -887,7 +895,8 @@ namespace
|
|||
|
||||
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape)
|
||||
{
|
||||
mNiTriShape.data->triangles.clear();
|
||||
auto data = static_cast<Nif::NiTriShapeData*>(mNiTriShape.data.getPtr());
|
||||
data->triangles.clear();
|
||||
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
|
||||
|
||||
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
|
||||
|
|
|
@ -157,6 +157,8 @@ void Wizard::MainWizard::setupGameSettings()
|
|||
mGameSettings.readUserFile(stream);
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// Now the rest
|
||||
QStringList paths;
|
||||
paths.append(userPath + QLatin1String("openmw.cfg"));
|
||||
|
|
|
@ -151,7 +151,13 @@ add_component_dir (fallback
|
|||
fallback validate
|
||||
)
|
||||
|
||||
if(NOT WIN32 AND NOT ANDROID)
|
||||
if(WIN32)
|
||||
add_component_dir (crashcatcher
|
||||
windows_crashcatcher
|
||||
windows_crashmonitor
|
||||
windows_crashshm
|
||||
)
|
||||
elseif(NOT ANDROID)
|
||||
add_component_dir (crashcatcher
|
||||
crashcatcher
|
||||
)
|
||||
|
|
205
components/crashcatcher/windows_crashcatcher.cpp
Normal file
205
components/crashcatcher/windows_crashcatcher.cpp
Normal 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
|
79
components/crashcatcher/windows_crashcatcher.hpp
Normal file
79
components/crashcatcher/windows_crashcatcher.hpp
Normal 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
|
188
components/crashcatcher/windows_crashmonitor.cpp
Normal file
188
components/crashcatcher/windows_crashmonitor.cpp
Normal 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
|
49
components/crashcatcher/windows_crashmonitor.hpp
Normal file
49
components/crashcatcher/windows_crashmonitor.hpp
Normal 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
|
45
components/crashcatcher/windows_crashshm.hpp
Normal file
45
components/crashcatcher/windows_crashshm.hpp
Normal 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
|
|
@ -1,10 +1,13 @@
|
|||
#include "debugging.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include <components/crashcatcher/crashcatcher.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <components/crashcatcher/windows_crashcatcher.hpp>
|
||||
# undef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
|
@ -133,11 +136,19 @@ namespace Debug
|
|||
}
|
||||
}
|
||||
|
||||
static std::unique_ptr<std::ostream> rawStdout = nullptr;
|
||||
|
||||
std::ostream& getRawStdout()
|
||||
{
|
||||
return rawStdout ? *rawStdout : std::cout;
|
||||
}
|
||||
|
||||
int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName)
|
||||
{
|
||||
#if defined _WIN32
|
||||
(void)Debug::attachParentConsole();
|
||||
#endif
|
||||
rawStdout = std::make_unique<std::ostream>(std::cout.rdbuf());
|
||||
|
||||
// Some objects used to redirect cout and cerr
|
||||
// Scope must be here, so this still works inside the catch block for logging exceptions
|
||||
|
@ -154,7 +165,6 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
|
|||
#endif
|
||||
|
||||
const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log";
|
||||
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log";
|
||||
boost::filesystem::ofstream logfile;
|
||||
|
||||
int ret = 0;
|
||||
|
@ -178,13 +188,18 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
|
|||
std::cerr.rdbuf (&cerrsb);
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp";
|
||||
Crash::CrashCatcher crashy(argc, argv, (cfgMgr.getLogPath() / crashLogName).make_preferred().string());
|
||||
#else
|
||||
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log";
|
||||
// install the crash handler as soon as possible. note that the log path
|
||||
// does not depend on config being read.
|
||||
crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string());
|
||||
|
||||
#endif
|
||||
ret = innerApplication(argc, argv);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix))
|
||||
if (!isatty(fileno(stdin)))
|
||||
|
|
|
@ -135,6 +135,9 @@ namespace Debug
|
|||
#endif
|
||||
}
|
||||
|
||||
// Can be used to print messages without timestamps
|
||||
std::ostream& getRawStdout();
|
||||
|
||||
int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -11,9 +11,8 @@
|
|||
namespace Nif
|
||||
{
|
||||
// An extra data record. All the extra data connected to an object form a linked list.
|
||||
class Extra : public Record
|
||||
struct Extra : public Record
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
ExtraPtr next; // Next extra data record in the list
|
||||
|
||||
|
@ -31,9 +30,8 @@ public:
|
|||
void post(NIFFile *nif) override { next.post(nif); }
|
||||
};
|
||||
|
||||
class Controller : public Record
|
||||
struct Controller : public Record
|
||||
{
|
||||
public:
|
||||
ControllerPtr next;
|
||||
int flags;
|
||||
float frequency, phase;
|
||||
|
@ -45,9 +43,8 @@ public:
|
|||
};
|
||||
|
||||
/// Has name, extra-data and controller
|
||||
class Named : public Record
|
||||
struct Named : public Record
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
ExtraPtr extra;
|
||||
ExtraList extralist;
|
||||
|
|
|
@ -47,6 +47,11 @@ namespace Nif
|
|||
data.post(nif);
|
||||
}
|
||||
|
||||
void BSShaderTextureSet::read(NIFStream *nif)
|
||||
{
|
||||
nif->getSizedStrings(textures, nif->getUInt());
|
||||
}
|
||||
|
||||
void NiParticleModifier::read(NIFStream *nif)
|
||||
{
|
||||
next.read(nif);
|
||||
|
|
|
@ -29,9 +29,8 @@
|
|||
namespace Nif
|
||||
{
|
||||
|
||||
class NiSourceTexture : public Named
|
||||
struct NiSourceTexture : public Named
|
||||
{
|
||||
public:
|
||||
// Is this an external (references a separate texture file) or
|
||||
// internal (data is inside the nif itself) texture?
|
||||
bool external;
|
||||
|
@ -66,6 +65,24 @@ public:
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
struct BSShaderTextureSet : public Record
|
||||
{
|
||||
enum TextureType
|
||||
{
|
||||
TextureType_Base = 0,
|
||||
TextureType_Normal = 1,
|
||||
TextureType_Glow = 2,
|
||||
TextureType_Parallax = 3,
|
||||
TextureType_Env = 4,
|
||||
TextureType_EnvMask = 5,
|
||||
TextureType_Subsurface = 6,
|
||||
TextureType_BackLighting = 7
|
||||
};
|
||||
std::vector<std::string> textures;
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
struct NiParticleModifier : public Record
|
||||
{
|
||||
NiParticleModifierPtr next;
|
||||
|
@ -75,27 +92,24 @@ struct NiParticleModifier : public Record
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiParticleGrowFade : public NiParticleModifier
|
||||
struct NiParticleGrowFade : public NiParticleModifier
|
||||
{
|
||||
public:
|
||||
float growTime;
|
||||
float fadeTime;
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiParticleColorModifier : public NiParticleModifier
|
||||
struct NiParticleColorModifier : public NiParticleModifier
|
||||
{
|
||||
public:
|
||||
NiColorDataPtr data;
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiGravity : public NiParticleModifier
|
||||
struct NiGravity : public NiParticleModifier
|
||||
{
|
||||
public:
|
||||
float mForce;
|
||||
/* 0 - Wind (fixed direction)
|
||||
* 1 - Point (fixed origin)
|
||||
|
@ -115,27 +129,24 @@ struct NiParticleCollider : public NiParticleModifier
|
|||
};
|
||||
|
||||
// NiPinaColada
|
||||
class NiPlanarCollider : public NiParticleCollider
|
||||
struct NiPlanarCollider : public NiParticleCollider
|
||||
{
|
||||
public:
|
||||
void read(NIFStream *nif) override;
|
||||
|
||||
osg::Vec3f mPlaneNormal;
|
||||
float mPlaneDistance;
|
||||
};
|
||||
|
||||
class NiSphericalCollider : public NiParticleCollider
|
||||
struct NiSphericalCollider : public NiParticleCollider
|
||||
{
|
||||
public:
|
||||
float mRadius;
|
||||
osg::Vec3f mCenter;
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiParticleRotation : public NiParticleModifier
|
||||
struct NiParticleRotation : public NiParticleModifier
|
||||
{
|
||||
public:
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
|
|
|
@ -325,4 +325,15 @@ namespace Nif
|
|||
data.post(nif);
|
||||
}
|
||||
|
||||
void NiColorInterpolator::read(NIFStream *nif)
|
||||
{
|
||||
defaultVal = nif->getVector4();
|
||||
data.read(nif);
|
||||
}
|
||||
|
||||
void NiColorInterpolator::post(NIFFile *nif)
|
||||
{
|
||||
data.post(nif);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,15 +29,14 @@
|
|||
namespace Nif
|
||||
{
|
||||
|
||||
class NiParticleSystemController : public Controller
|
||||
struct NiParticleSystemController : public Controller
|
||||
{
|
||||
public:
|
||||
struct Particle {
|
||||
osg::Vec3f velocity;
|
||||
float lifetime;
|
||||
float lifespan;
|
||||
float timestamp;
|
||||
int vertex;
|
||||
unsigned short vertex;
|
||||
};
|
||||
|
||||
float velocity;
|
||||
|
@ -80,9 +79,8 @@ public:
|
|||
};
|
||||
using NiBSPArrayController = NiParticleSystemController;
|
||||
|
||||
class NiMaterialColorController : public Controller
|
||||
struct NiMaterialColorController : public Controller
|
||||
{
|
||||
public:
|
||||
NiPoint3InterpolatorPtr interpolator;
|
||||
NiPosDataPtr data;
|
||||
unsigned int targetColor;
|
||||
|
@ -91,9 +89,8 @@ public:
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiPathController : public Controller
|
||||
struct NiPathController : public Controller
|
||||
{
|
||||
public:
|
||||
NiPosDataPtr posData;
|
||||
NiFloatDataPtr floatData;
|
||||
|
||||
|
@ -115,9 +112,8 @@ public:
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiLookAtController : public Controller
|
||||
struct NiLookAtController : public Controller
|
||||
{
|
||||
public:
|
||||
NodePtr target;
|
||||
unsigned short lookAtFlags{0};
|
||||
|
||||
|
@ -125,9 +121,8 @@ public:
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiUVController : public Controller
|
||||
struct NiUVController : public Controller
|
||||
{
|
||||
public:
|
||||
NiUVDataPtr data;
|
||||
unsigned int uvSet;
|
||||
|
||||
|
@ -135,9 +130,8 @@ public:
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiKeyframeController : public Controller
|
||||
struct NiKeyframeController : public Controller
|
||||
{
|
||||
public:
|
||||
NiKeyframeDataPtr data;
|
||||
NiTransformInterpolatorPtr interpolator;
|
||||
|
||||
|
@ -154,12 +148,11 @@ struct NiFloatInterpController : public Controller
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiAlphaController : public NiFloatInterpController { };
|
||||
class NiRollController : public NiFloatInterpController { };
|
||||
struct NiAlphaController : public NiFloatInterpController { };
|
||||
struct NiRollController : public NiFloatInterpController { };
|
||||
|
||||
class NiGeomMorpherController : public Controller
|
||||
struct NiGeomMorpherController : public Controller
|
||||
{
|
||||
public:
|
||||
NiMorphDataPtr data;
|
||||
NiFloatInterpolatorList interpolators;
|
||||
|
||||
|
@ -167,18 +160,16 @@ public:
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiVisController : public Controller
|
||||
struct NiVisController : public Controller
|
||||
{
|
||||
public:
|
||||
NiVisDataPtr data;
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiFlipController : public Controller
|
||||
struct NiFlipController : public Controller
|
||||
{
|
||||
public:
|
||||
NiFloatInterpolatorPtr mInterpolator;
|
||||
int mTexSlot; // NiTexturingProperty::TextureType
|
||||
float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources
|
||||
|
@ -230,5 +221,13 @@ struct NiTransformInterpolator : public Interpolator
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
struct NiColorInterpolator : public Interpolator
|
||||
{
|
||||
osg::Vec4f defaultVal;
|
||||
NiColorDataPtr data;
|
||||
void read(NIFStream *nif) override;
|
||||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
} // Namespace
|
||||
#endif
|
||||
|
|
|
@ -32,9 +32,8 @@ namespace Nif
|
|||
{
|
||||
|
||||
// Common ancestor for several data classes
|
||||
class NiGeometryData : public Record
|
||||
struct NiGeometryData : public Record
|
||||
{
|
||||
public:
|
||||
std::vector<osg::Vec3f> vertices, normals, tangents, bitangents;
|
||||
std::vector<osg::Vec4f> colors;
|
||||
std::vector< std::vector<osg::Vec2f> > uvlist;
|
||||
|
@ -44,18 +43,16 @@ public:
|
|||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiTriShapeData : public NiGeometryData
|
||||
struct NiTriShapeData : public NiGeometryData
|
||||
{
|
||||
public:
|
||||
// Triangles, three vertex indices per triangle
|
||||
std::vector<unsigned short> triangles;
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiTriStripsData : public NiGeometryData
|
||||
struct NiTriStripsData : public NiGeometryData
|
||||
{
|
||||
public:
|
||||
// Triangle strips, series of vertex indices.
|
||||
std::vector<std::vector<unsigned short>> strips;
|
||||
|
||||
|
@ -70,9 +67,8 @@ struct NiLinesData : public NiGeometryData
|
|||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiParticlesData : public NiGeometryData
|
||||
struct NiParticlesData : public NiGeometryData
|
||||
{
|
||||
public:
|
||||
int numParticles{0};
|
||||
|
||||
int activeCount;
|
||||
|
@ -84,39 +80,34 @@ public:
|
|||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiRotatingParticlesData : public NiParticlesData
|
||||
struct NiRotatingParticlesData : public NiParticlesData
|
||||
{
|
||||
public:
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiPosData : public Record
|
||||
struct NiPosData : public Record
|
||||
{
|
||||
public:
|
||||
Vector3KeyMapPtr mKeyList;
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiUVData : public Record
|
||||
struct NiUVData : public Record
|
||||
{
|
||||
public:
|
||||
FloatKeyMapPtr mKeyList[4];
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiFloatData : public Record
|
||||
struct NiFloatData : public Record
|
||||
{
|
||||
public:
|
||||
FloatKeyMapPtr mKeyList;
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiPixelData : public Record
|
||||
struct NiPixelData : public Record
|
||||
{
|
||||
public:
|
||||
enum Format
|
||||
{
|
||||
NIPXFMT_RGB8,
|
||||
|
@ -150,17 +141,15 @@ public:
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiColorData : public Record
|
||||
struct NiColorData : public Record
|
||||
{
|
||||
public:
|
||||
Vector4KeyMapPtr mKeyMap;
|
||||
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiVisData : public Record
|
||||
struct NiVisData : public Record
|
||||
{
|
||||
public:
|
||||
struct VisData {
|
||||
float time;
|
||||
bool isSet;
|
||||
|
@ -170,9 +159,8 @@ public:
|
|||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiSkinInstance : public Record
|
||||
struct NiSkinInstance : public Record
|
||||
{
|
||||
public:
|
||||
NiSkinDataPtr data;
|
||||
NiSkinPartitionPtr partitions;
|
||||
NodePtr root;
|
||||
|
@ -182,9 +170,8 @@ public:
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiSkinData : public Record
|
||||
struct NiSkinData : public Record
|
||||
{
|
||||
public:
|
||||
struct VertWeight
|
||||
{
|
||||
unsigned short vertex;
|
||||
|
@ -251,9 +238,8 @@ struct NiKeyframeData : public Record
|
|||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiPalette : public Record
|
||||
struct NiPalette : public Record
|
||||
{
|
||||
public:
|
||||
// 32-bit RGBA colors that correspond to 8-bit indices
|
||||
std::vector<unsigned int> colors;
|
||||
|
||||
|
|
|
@ -29,15 +29,13 @@
|
|||
namespace Nif
|
||||
{
|
||||
|
||||
class NiVertWeightsExtraData : public Extra
|
||||
struct NiVertWeightsExtraData : public Extra
|
||||
{
|
||||
public:
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiTextKeyExtraData : public Extra
|
||||
struct NiTextKeyExtraData : public Extra
|
||||
{
|
||||
public:
|
||||
struct TextKey
|
||||
{
|
||||
float time;
|
||||
|
@ -48,9 +46,8 @@ public:
|
|||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
class NiStringExtraData : public Extra
|
||||
struct NiStringExtraData : public Extra
|
||||
{
|
||||
public:
|
||||
/* Two known meanings:
|
||||
"MRK" - marker, only visible in the editor, not rendered in-game
|
||||
"NCO" - no collision
|
||||
|
|
|
@ -129,6 +129,10 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
|
|||
factory["NiPoint3Interpolator"] = {&construct <NiPoint3Interpolator> , RC_NiPoint3Interpolator };
|
||||
factory["NiTransformController"] = {&construct <NiKeyframeController> , RC_NiKeyframeController };
|
||||
factory["NiTransformInterpolator"] = {&construct <NiTransformInterpolator> , RC_NiTransformInterpolator };
|
||||
factory["NiColorInterpolator"] = {&construct <NiColorInterpolator> , RC_NiColorInterpolator };
|
||||
factory["BSShaderTextureSet"] = {&construct <BSShaderTextureSet> , RC_BSShaderTextureSet };
|
||||
factory["BSLODTriShape"] = {&construct <BSLODTriShape> , RC_BSLODTriShape };
|
||||
factory["BSShaderProperty"] = {&construct <BSShaderProperty> , RC_BSShaderProperty };
|
||||
return factory;
|
||||
}
|
||||
|
||||
|
|
|
@ -131,9 +131,8 @@ struct NiBoundingVolume
|
|||
parent node (unless it's the root), and transformation (location
|
||||
and rotation) relative to it's parent.
|
||||
*/
|
||||
class Node : public Named
|
||||
struct Node : public Named
|
||||
{
|
||||
public:
|
||||
// Node flags. Interpretation depends somewhat on the type of node.
|
||||
unsigned int flags;
|
||||
Transformation trafo;
|
||||
|
@ -240,43 +239,6 @@ struct NiNode : 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:
|
||||
0x40 - mesh has no vertex normals ?
|
||||
|
@ -285,14 +247,50 @@ struct NiTriShape : NiGeometry
|
|||
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
|
||||
{
|
||||
Node::read(nif);
|
||||
data.read(nif);
|
||||
skin.read(nif);
|
||||
materialData.read(nif);
|
||||
material.read(nif);
|
||||
if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
|
||||
{
|
||||
shaderprop.read(nif);
|
||||
alphaprop.read(nif);
|
||||
}
|
||||
}
|
||||
|
||||
void post(NIFFile *nif) override
|
||||
|
@ -300,53 +298,28 @@ struct NiTriShape : NiGeometry
|
|||
Node::post(nif);
|
||||
data.post(nif);
|
||||
skin.post(nif);
|
||||
if (!skin.empty())
|
||||
shaderprop.post(nif);
|
||||
alphaprop.post(nif);
|
||||
if (recType != RC_NiParticles && !skin.empty())
|
||||
nif->setUseSkinning(true);
|
||||
}
|
||||
};
|
||||
|
||||
struct NiTriStrips : NiGeometry
|
||||
struct NiTriShape : NiGeometry {};
|
||||
struct BSLODTriShape : NiTriShape
|
||||
{
|
||||
NiTriStripsDataPtr data;
|
||||
|
||||
unsigned int lod0, lod1, lod2;
|
||||
void read(NIFStream *nif) override
|
||||
{
|
||||
Node::read(nif);
|
||||
data.read(nif);
|
||||
skin.read(nif);
|
||||
materialData.read(nif);
|
||||
}
|
||||
|
||||
void post(NIFFile *nif) override
|
||||
{
|
||||
Node::post(nif);
|
||||
data.post(nif);
|
||||
skin.post(nif);
|
||||
if (!skin.empty())
|
||||
nif->setUseSkinning(true);
|
||||
}
|
||||
};
|
||||
|
||||
struct NiLines : NiGeometry
|
||||
{
|
||||
NiLinesDataPtr data;
|
||||
|
||||
void read(NIFStream *nif) override
|
||||
{
|
||||
Node::read(nif);
|
||||
data.read(nif);
|
||||
skin.read(nif);
|
||||
}
|
||||
|
||||
void post(NIFFile *nif) override
|
||||
{
|
||||
Node::post(nif);
|
||||
data.post(nif);
|
||||
skin.post(nif);
|
||||
if (!skin.empty())
|
||||
nif->setUseSkinning(true);
|
||||
NiTriShape::read(nif);
|
||||
lod0 = nif->getUInt();
|
||||
lod1 = nif->getUInt();
|
||||
lod2 = nif->getUInt();
|
||||
}
|
||||
};
|
||||
struct NiTriStrips : NiGeometry {};
|
||||
struct NiLines : NiGeometry {};
|
||||
struct NiParticles : NiGeometry { };
|
||||
|
||||
struct NiCamera : Node
|
||||
{
|
||||
|
@ -401,25 +374,6 @@ struct NiCamera : Node
|
|||
}
|
||||
};
|
||||
|
||||
struct NiParticles : NiGeometry
|
||||
{
|
||||
NiParticlesDataPtr data;
|
||||
void read(NIFStream *nif) override
|
||||
{
|
||||
Node::read(nif);
|
||||
data.read(nif);
|
||||
skin.read(nif);
|
||||
materialData.read(nif);
|
||||
}
|
||||
|
||||
void post(NIFFile *nif) override
|
||||
{
|
||||
Node::post(nif);
|
||||
data.post(nif);
|
||||
skin.post(nif);
|
||||
}
|
||||
};
|
||||
|
||||
// A node used as the base to switch between child nodes, such as for LOD levels.
|
||||
struct NiSwitchNode : public NiNode
|
||||
{
|
||||
|
|
|
@ -99,6 +99,25 @@ void NiTexturingProperty::post(NIFFile *nif)
|
|||
shaderTextures[i].post(nif);
|
||||
}
|
||||
|
||||
void BSShaderProperty::read(NIFStream *nif)
|
||||
{
|
||||
NiShadeProperty::read(nif);
|
||||
if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3)
|
||||
{
|
||||
type = nif->getUInt();
|
||||
flags1 = nif->getUInt();
|
||||
flags2 = nif->getUInt();
|
||||
envMapIntensity = nif->getFloat();
|
||||
}
|
||||
}
|
||||
|
||||
void BSShaderLightingProperty::read(NIFStream *nif)
|
||||
{
|
||||
BSShaderProperty::read(nif);
|
||||
if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3)
|
||||
clamp = nif->getUInt();
|
||||
}
|
||||
|
||||
void NiFogProperty::read(NIFStream *nif)
|
||||
{
|
||||
Property::read(nif);
|
||||
|
|
|
@ -29,11 +29,10 @@
|
|||
namespace Nif
|
||||
{
|
||||
|
||||
class Property : public Named { };
|
||||
struct Property : public Named { };
|
||||
|
||||
class NiTexturingProperty : public Property
|
||||
struct NiTexturingProperty : public Property
|
||||
{
|
||||
public:
|
||||
unsigned short flags{0u};
|
||||
|
||||
// A sub-texture
|
||||
|
@ -96,9 +95,8 @@ public:
|
|||
void post(NIFFile *nif) override;
|
||||
};
|
||||
|
||||
class NiFogProperty : public Property
|
||||
struct NiFogProperty : public Property
|
||||
{
|
||||
public:
|
||||
unsigned short mFlags;
|
||||
float mFogDepth;
|
||||
osg::Vec3f mColour;
|
||||
|
@ -118,6 +116,19 @@ struct NiShadeProperty : public Property
|
|||
}
|
||||
};
|
||||
|
||||
struct BSShaderProperty : public NiShadeProperty
|
||||
{
|
||||
unsigned int type{0u}, flags1{0u}, flags2{0u};
|
||||
float envMapIntensity{0.f};
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
struct BSShaderLightingProperty : public BSShaderProperty
|
||||
{
|
||||
unsigned int clamp{0u};
|
||||
void read(NIFStream *nif) override;
|
||||
};
|
||||
|
||||
struct NiDitherProperty : public Property
|
||||
{
|
||||
unsigned short flags;
|
||||
|
@ -294,8 +305,8 @@ struct S_StencilProperty
|
|||
void read(NIFStream *nif);
|
||||
};
|
||||
|
||||
class NiAlphaProperty : public StructPropT<S_AlphaProperty> { };
|
||||
class NiVertexColorProperty : public StructPropT<S_VertexColorProperty> { };
|
||||
struct NiAlphaProperty : public StructPropT<S_AlphaProperty> { };
|
||||
struct NiVertexColorProperty : public StructPropT<S_VertexColorProperty> { };
|
||||
struct NiStencilProperty : public Property
|
||||
{
|
||||
S_StencilProperty data;
|
||||
|
|
|
@ -118,7 +118,11 @@ enum RecordType
|
|||
RC_NiFloatInterpolator,
|
||||
RC_NiPoint3Interpolator,
|
||||
RC_NiBoolInterpolator,
|
||||
RC_NiTransformInterpolator
|
||||
RC_NiTransformInterpolator,
|
||||
RC_NiColorInterpolator,
|
||||
RC_BSShaderTextureSet,
|
||||
RC_BSLODTriShape,
|
||||
RC_BSShaderProperty
|
||||
};
|
||||
|
||||
/// Base class for all records
|
||||
|
|
|
@ -120,33 +120,34 @@ public:
|
|||
};
|
||||
|
||||
|
||||
class Node;
|
||||
class Extra;
|
||||
class Property;
|
||||
class NiUVData;
|
||||
class NiPosData;
|
||||
class NiVisData;
|
||||
class Controller;
|
||||
class Named;
|
||||
class NiSkinData;
|
||||
class NiFloatData;
|
||||
struct Node;
|
||||
struct Extra;
|
||||
struct Property;
|
||||
struct NiUVData;
|
||||
struct NiPosData;
|
||||
struct NiVisData;
|
||||
struct Controller;
|
||||
struct Named;
|
||||
struct NiSkinData;
|
||||
struct NiFloatData;
|
||||
struct NiMorphData;
|
||||
class NiPixelData;
|
||||
class NiColorData;
|
||||
struct NiPixelData;
|
||||
struct NiColorData;
|
||||
struct NiKeyframeData;
|
||||
class NiTriShapeData;
|
||||
class NiTriStripsData;
|
||||
class NiSkinInstance;
|
||||
class NiSourceTexture;
|
||||
class NiParticlesData;
|
||||
class NiPalette;
|
||||
struct NiTriStripsData;
|
||||
struct NiSkinInstance;
|
||||
struct NiSourceTexture;
|
||||
struct NiPalette;
|
||||
struct NiParticleModifier;
|
||||
struct NiLinesData;
|
||||
struct NiBoolData;
|
||||
struct NiSkinPartition;
|
||||
struct NiFloatInterpolator;
|
||||
struct NiPoint3Interpolator;
|
||||
struct NiTransformInterpolator;
|
||||
struct BSShaderTextureSet;
|
||||
struct NiGeometryData;
|
||||
struct BSShaderProperty;
|
||||
struct NiAlphaProperty;
|
||||
|
||||
using NodePtr = RecordPtrT<Node>;
|
||||
using ExtraPtr = RecordPtrT<Extra>;
|
||||
|
@ -161,12 +162,8 @@ using NiPixelDataPtr = RecordPtrT<NiPixelData>;
|
|||
using NiFloatDataPtr = RecordPtrT<NiFloatData>;
|
||||
using NiColorDataPtr = RecordPtrT<NiColorData>;
|
||||
using NiKeyframeDataPtr = RecordPtrT<NiKeyframeData>;
|
||||
using NiTriShapeDataPtr = RecordPtrT<NiTriShapeData>;
|
||||
using NiTriStripsDataPtr = RecordPtrT<NiTriStripsData>;
|
||||
using NiLinesDataPtr = RecordPtrT<NiLinesData>;
|
||||
using NiSkinInstancePtr = RecordPtrT<NiSkinInstance>;
|
||||
using NiSourceTexturePtr = RecordPtrT<NiSourceTexture>;
|
||||
using NiParticlesDataPtr = RecordPtrT<NiParticlesData>;
|
||||
using NiPalettePtr = RecordPtrT<NiPalette>;
|
||||
using NiParticleModifierPtr = RecordPtrT<NiParticleModifier>;
|
||||
using NiBoolDataPtr = RecordPtrT<NiBoolData>;
|
||||
|
@ -174,12 +171,17 @@ using NiSkinPartitionPtr = RecordPtrT<NiSkinPartition>;
|
|||
using NiFloatInterpolatorPtr = RecordPtrT<NiFloatInterpolator>;
|
||||
using NiPoint3InterpolatorPtr = RecordPtrT<NiPoint3Interpolator>;
|
||||
using NiTransformInterpolatorPtr = RecordPtrT<NiTransformInterpolator>;
|
||||
using BSShaderTextureSetPtr = RecordPtrT<BSShaderTextureSet>;
|
||||
using NiGeometryDataPtr = RecordPtrT<NiGeometryData>;
|
||||
using BSShaderPropertyPtr = RecordPtrT<BSShaderProperty>;
|
||||
using NiAlphaPropertyPtr = RecordPtrT<NiAlphaProperty>;
|
||||
|
||||
using NodeList = RecordListT<Node>;
|
||||
using PropertyList = RecordListT<Property>;
|
||||
using ExtraList = RecordListT<Extra>;
|
||||
using NiSourceTextureList = RecordListT<NiSourceTexture>;
|
||||
using NiFloatInterpolatorList = RecordListT<NiFloatInterpolator>;
|
||||
using NiTriStripsDataList = RecordListT<NiTriStripsData>;
|
||||
|
||||
} // Namespace
|
||||
#endif
|
||||
|
|
|
@ -34,11 +34,10 @@ bool pathFileNameStartsWithX(const std::string& path)
|
|||
|
||||
void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform)
|
||||
{
|
||||
mesh.preallocateVertices(static_cast<int>(data.vertices.size()));
|
||||
mesh.preallocateIndices(static_cast<int>(data.triangles.size()));
|
||||
|
||||
const std::vector<osg::Vec3f> &vertices = data.vertices;
|
||||
const std::vector<unsigned short> &triangles = data.triangles;
|
||||
mesh.preallocateVertices(static_cast<int>(vertices.size()));
|
||||
mesh.preallocateIndices(static_cast<int>(triangles.size()));
|
||||
|
||||
for (std::size_t i = 0; i < triangles.size(); i += 3)
|
||||
{
|
||||
|
@ -54,8 +53,6 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co
|
|||
{
|
||||
const std::vector<osg::Vec3f> &vertices = data.vertices;
|
||||
const std::vector<std::vector<unsigned short>> &strips = data.strips;
|
||||
if (vertices.empty() || strips.empty())
|
||||
return;
|
||||
mesh.preallocateVertices(static_cast<int>(vertices.size()));
|
||||
int numTriangles = 0;
|
||||
for (const std::vector<unsigned short>& strip : strips)
|
||||
|
@ -102,12 +99,12 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co
|
|||
}
|
||||
}
|
||||
|
||||
void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* nifNode, const osg::Matrixf &transform = osg::Matrixf())
|
||||
void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiGeometry* geometry, const osg::Matrixf &transform = osg::Matrixf())
|
||||
{
|
||||
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||
fillTriangleMesh(mesh, static_cast<const Nif::NiTriShape*>(nifNode)->data.get(), transform);
|
||||
else if (nifNode->recType == Nif::RC_NiTriStrips)
|
||||
fillTriangleMesh(mesh, static_cast<const Nif::NiTriStrips*>(nifNode)->data.get(), transform);
|
||||
if (geometry->recType == Nif::RC_NiTriShape || geometry->recType == Nif::RC_BSLODTriShape)
|
||||
fillTriangleMesh(mesh, static_cast<const Nif::NiTriShapeData&>(geometry->data.get()), transform);
|
||||
else if (geometry->recType == Nif::RC_NiTriStrips)
|
||||
fillTriangleMesh(mesh, static_cast<const Nif::NiTriStripsData&>(geometry->data.get()), transform);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -145,12 +142,12 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
|
|||
{
|
||||
if (findBoundingBox(node, filename))
|
||||
{
|
||||
const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents);
|
||||
const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate);
|
||||
const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.extents);
|
||||
const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.center);
|
||||
std::unique_ptr<btCompoundShape> compound (new btCompoundShape);
|
||||
std::unique_ptr<btBoxShape> boxShape(new btBoxShape(halfExtents));
|
||||
std::unique_ptr<btBoxShape> boxShape(new btBoxShape(extents));
|
||||
btTransform transform = btTransform::getIdentity();
|
||||
transform.setOrigin(origin);
|
||||
transform.setOrigin(center);
|
||||
compound->addChildShape(transform, boxShape.get());
|
||||
boxShape.release();
|
||||
|
||||
|
@ -208,8 +205,8 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string&
|
|||
switch (type)
|
||||
{
|
||||
case Nif::NiBoundingVolume::Type::BOX_BV:
|
||||
mShape->mCollisionBoxHalfExtents = node->bounds.box.extents;
|
||||
mShape->mCollisionBoxTranslate = node->bounds.box.center;
|
||||
mShape->mCollisionBox.extents = node->bounds.box.extents;
|
||||
mShape->mCollisionBox.center = node->bounds.box.center;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
|
@ -312,7 +309,9 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n
|
|||
// NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape!
|
||||
// It must be ignored completely.
|
||||
// (occurs in tr_ex_imp_wall_arch_04.nif)
|
||||
if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape || node->recType == Nif::RC_NiTriStrips))
|
||||
if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape
|
||||
|| node->recType == Nif::RC_NiTriStrips
|
||||
|| node->recType == Nif::RC_BSLODTriShape))
|
||||
{
|
||||
handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid);
|
||||
}
|
||||
|
@ -341,23 +340,31 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
|
|||
if ((flags & 0x800))
|
||||
return;
|
||||
|
||||
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||
auto niGeometry = static_cast<const Nif::NiGeometry*>(nifNode);
|
||||
if (niGeometry->data.empty() || niGeometry->data->vertices.empty())
|
||||
return;
|
||||
|
||||
if (niGeometry->recType == Nif::RC_NiTriShape || niGeometry->recType == Nif::RC_BSLODTriShape)
|
||||
{
|
||||
const Nif::NiTriShape* shape = static_cast<const Nif::NiTriShape*>(nifNode);
|
||||
if (!shape->skin.empty())
|
||||
isAnimated = false;
|
||||
if (shape->data.empty() || shape->data->triangles.empty())
|
||||
if (niGeometry->data->recType != Nif::RC_NiTriShapeData)
|
||||
return;
|
||||
|
||||
auto data = static_cast<const Nif::NiTriShapeData*>(niGeometry->data.getPtr());
|
||||
if (data->triangles.empty())
|
||||
return;
|
||||
}
|
||||
else
|
||||
else if (niGeometry->recType == Nif::RC_NiTriStrips)
|
||||
{
|
||||
const Nif::NiTriStrips* shape = static_cast<const Nif::NiTriStrips*>(nifNode);
|
||||
if (!shape->skin.empty())
|
||||
isAnimated = false;
|
||||
if (shape->data.empty() || shape->data->strips.empty())
|
||||
if (niGeometry->data->recType != Nif::RC_NiTriStripsData)
|
||||
return;
|
||||
|
||||
auto data = static_cast<const Nif::NiTriStripsData*>(niGeometry->data.getPtr());
|
||||
if (data->strips.empty())
|
||||
return;
|
||||
}
|
||||
|
||||
if (!niGeometry->skin.empty())
|
||||
isAnimated = false;
|
||||
|
||||
if (isAnimated)
|
||||
{
|
||||
|
@ -366,7 +373,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
|
|||
|
||||
std::unique_ptr<btTriangleMesh> childMesh(new btTriangleMesh);
|
||||
|
||||
fillTriangleMesh(*childMesh, nifNode);
|
||||
fillTriangleMesh(*childMesh, niGeometry);
|
||||
|
||||
std::unique_ptr<Resource::TriangleMeshShape> childShape(new Resource::TriangleMeshShape(childMesh.get(), true));
|
||||
childMesh.release();
|
||||
|
@ -394,7 +401,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
|
|||
if (!mAvoidStaticMesh)
|
||||
mAvoidStaticMesh.reset(new btTriangleMesh(false));
|
||||
|
||||
fillTriangleMesh(*mAvoidStaticMesh, nifNode, transform);
|
||||
fillTriangleMesh(*mAvoidStaticMesh, niGeometry, transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -402,7 +409,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
|
|||
mStaticMesh.reset(new btTriangleMesh(false));
|
||||
|
||||
// Static shape, just transform all vertices into position
|
||||
fillTriangleMesh(*mStaticMesh, nifNode, transform);
|
||||
fillTriangleMesh(*mStaticMesh, niGeometry, transform);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,9 @@ namespace NifOsg
|
|||
std::conjunction_v<
|
||||
std::disjunction<
|
||||
std::is_same<ValueT, float>,
|
||||
std::is_same<ValueT, osg::Vec3f>
|
||||
std::is_same<ValueT, osg::Vec3f>,
|
||||
std::is_same<ValueT, bool>,
|
||||
std::is_same<ValueT, osg::Vec4f>
|
||||
>,
|
||||
std::is_same<decltype(T::defaultVal), ValueT>
|
||||
>,
|
||||
|
|
|
@ -67,6 +67,7 @@ namespace
|
|||
case Nif::RC_NiTriShape:
|
||||
case Nif::RC_NiTriStrips:
|
||||
case Nif::RC_NiLines:
|
||||
case Nif::RC_BSLODTriShape:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -95,6 +96,15 @@ namespace
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto geometry = dynamic_cast<const Nif::NiGeometry*>(nifNode);
|
||||
if (geometry)
|
||||
{
|
||||
if (!geometry->shaderprop.empty())
|
||||
out.emplace_back(geometry->shaderprop.getPtr());
|
||||
if (!geometry->alphaprop.empty())
|
||||
out.emplace_back(geometry->alphaprop.getPtr());
|
||||
}
|
||||
}
|
||||
|
||||
// NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale
|
||||
|
@ -365,6 +375,11 @@ namespace NifOsg
|
|||
handleProperty(props[i].getPtr(), applyTo, composite, imageManager, boundTextures, animflags);
|
||||
}
|
||||
}
|
||||
|
||||
auto geometry = dynamic_cast<const Nif::NiGeometry*>(nifNode);
|
||||
// NiGeometry's NiAlphaProperty doesn't get handled here because it's a drawable property
|
||||
if (geometry && !geometry->shaderprop.empty())
|
||||
handleProperty(geometry->shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags);
|
||||
}
|
||||
|
||||
void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags)
|
||||
|
@ -466,19 +481,12 @@ namespace NifOsg
|
|||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
|
||||
osg::ref_ptr<osg::TexEnvCombine> texEnv = new osg::TexEnvCombine;
|
||||
texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE);
|
||||
texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS);
|
||||
texEnv->setCombine_RGB(osg::TexEnvCombine::ADD);
|
||||
texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
|
||||
texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE);
|
||||
|
||||
int texUnit = 3; // FIXME
|
||||
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
|
||||
stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON);
|
||||
stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON);
|
||||
stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON);
|
||||
|
||||
stateset->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1)));
|
||||
}
|
||||
|
@ -946,11 +954,11 @@ namespace NifOsg
|
|||
// Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and colors.
|
||||
void handleParticleInitialState(const Nif::Node* nifNode, osgParticle::ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl)
|
||||
{
|
||||
const auto particleNode = static_cast<const Nif::NiParticles*>(nifNode);
|
||||
if (particleNode->data.empty())
|
||||
auto particleNode = static_cast<const Nif::NiParticles*>(nifNode);
|
||||
if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData)
|
||||
return;
|
||||
|
||||
const Nif::NiParticlesData* particledata = particleNode->data.getPtr();
|
||||
auto particledata = static_cast<const Nif::NiParticlesData*>(particleNode->data.getPtr());
|
||||
|
||||
osg::BoundingBox box;
|
||||
|
||||
|
@ -963,6 +971,9 @@ namespace NifOsg
|
|||
if (particle.lifespan <= 0)
|
||||
continue;
|
||||
|
||||
if (particle.vertex >= particledata->vertices.size())
|
||||
continue;
|
||||
|
||||
ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime));
|
||||
|
||||
osgParticle::Particle* created = partsys->createParticle(&particletemplate);
|
||||
|
@ -971,16 +982,16 @@ namespace NifOsg
|
|||
// Note this position and velocity is not correct for a particle system with absolute reference frame,
|
||||
// which can not be done in this loader since we are not attached to the scene yet. Will be fixed up post-load in the SceneManager.
|
||||
created->setVelocity(particle.velocity);
|
||||
const osg::Vec3f& position = particledata->vertices.at(particle.vertex);
|
||||
const osg::Vec3f& position = particledata->vertices[particle.vertex];
|
||||
created->setPosition(position);
|
||||
|
||||
osg::Vec4f partcolor (1.f,1.f,1.f,1.f);
|
||||
if (particle.vertex < int(particledata->colors.size()))
|
||||
partcolor = particledata->colors.at(particle.vertex);
|
||||
if (particle.vertex < particledata->colors.size())
|
||||
partcolor = particledata->colors[particle.vertex];
|
||||
|
||||
float size = partctrl->size;
|
||||
if (particle.vertex < int(particledata->sizes.size()))
|
||||
size *= particledata->sizes.at(particle.vertex);
|
||||
if (particle.vertex < particledata->sizes.size())
|
||||
size *= particledata->sizes[particle.vertex];
|
||||
|
||||
created->setSizeRange(osgParticle::rangef(size, size));
|
||||
box.expandBy(osg::BoundingSphere(position, size));
|
||||
|
@ -1177,51 +1188,50 @@ namespace NifOsg
|
|||
|
||||
void handleNiGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<unsigned int>& boundTextures, int animflags)
|
||||
{
|
||||
const Nif::NiGeometryData* niGeometryData = nullptr;
|
||||
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||
const Nif::NiGeometry* niGeometry = static_cast<const Nif::NiGeometry*>(nifNode);
|
||||
if (niGeometry->data.empty())
|
||||
return;
|
||||
const Nif::NiGeometryData* niGeometryData = niGeometry->data.getPtr();
|
||||
|
||||
if (niGeometry->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_BSLODTriShape)
|
||||
{
|
||||
const Nif::NiTriShape* triShape = static_cast<const Nif::NiTriShape*>(nifNode);
|
||||
if (!triShape->data.empty())
|
||||
{
|
||||
const Nif::NiTriShapeData* data = triShape->data.getPtr();
|
||||
niGeometryData = static_cast<const Nif::NiGeometryData*>(data);
|
||||
if (!data->triangles.empty())
|
||||
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(),
|
||||
(unsigned short*)data->triangles.data()));
|
||||
}
|
||||
if (niGeometryData->recType != Nif::RC_NiTriShapeData)
|
||||
return;
|
||||
auto triangles = static_cast<const Nif::NiTriShapeData*>(niGeometryData)->triangles;
|
||||
if (triangles.empty())
|
||||
return;
|
||||
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, triangles.size(),
|
||||
(unsigned short*)triangles.data()));
|
||||
}
|
||||
else if (nifNode->recType == Nif::RC_NiTriStrips)
|
||||
else if (niGeometry->recType == Nif::RC_NiTriStrips)
|
||||
{
|
||||
const Nif::NiTriStrips* triStrips = static_cast<const Nif::NiTriStrips*>(nifNode);
|
||||
if (!triStrips->data.empty())
|
||||
if (niGeometryData->recType != Nif::RC_NiTriStripsData)
|
||||
return;
|
||||
auto data = static_cast<const Nif::NiTriStripsData*>(niGeometryData);
|
||||
bool hasGeometry = false;
|
||||
for (const auto& strip : data->strips)
|
||||
{
|
||||
const Nif::NiTriStripsData* data = triStrips->data.getPtr();
|
||||
niGeometryData = static_cast<const Nif::NiGeometryData*>(data);
|
||||
if (!data->strips.empty())
|
||||
{
|
||||
for (const auto& strip : data->strips)
|
||||
{
|
||||
if (strip.size() >= 3)
|
||||
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(),
|
||||
(unsigned short*)strip.data()));
|
||||
}
|
||||
}
|
||||
if (strip.size() < 3)
|
||||
continue;
|
||||
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(),
|
||||
(unsigned short*)strip.data()));
|
||||
hasGeometry = true;
|
||||
}
|
||||
if (!hasGeometry)
|
||||
return;
|
||||
}
|
||||
else if (nifNode->recType == Nif::RC_NiLines)
|
||||
else if (niGeometry->recType == Nif::RC_NiLines)
|
||||
{
|
||||
const Nif::NiLines* lines = static_cast<const Nif::NiLines*>(nifNode);
|
||||
if (!lines->data.empty())
|
||||
{
|
||||
const Nif::NiLinesData* data = lines->data.getPtr();
|
||||
niGeometryData = static_cast<const Nif::NiGeometryData*>(data);
|
||||
const auto& line = data->lines;
|
||||
if (!line.empty())
|
||||
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), (unsigned short*)line.data()));
|
||||
}
|
||||
if (niGeometryData->recType != Nif::RC_NiLinesData)
|
||||
return;
|
||||
auto data = static_cast<const Nif::NiLinesData*>(niGeometryData);
|
||||
const auto& line = data->lines;
|
||||
if (line.empty())
|
||||
return;
|
||||
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(),
|
||||
(unsigned short*)line.data()));
|
||||
}
|
||||
if (niGeometryData)
|
||||
handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name);
|
||||
handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name);
|
||||
|
||||
// osg::Material properties are handled here for two reasons:
|
||||
// - if there are no vertex colors, we need to disable colorMode.
|
||||
|
@ -1229,15 +1239,18 @@ namespace NifOsg
|
|||
// above the actual renderable would be tedious.
|
||||
std::vector<const Nif::Property*> drawableProps;
|
||||
collectDrawableProperties(nifNode, drawableProps);
|
||||
applyDrawableProperties(parentNode, drawableProps, composite, niGeometryData && !niGeometryData->colors.empty(), animflags);
|
||||
applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->colors.empty(), animflags);
|
||||
}
|
||||
|
||||
void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<unsigned int>& boundTextures, int animflags)
|
||||
{
|
||||
assert(isTypeGeometry(nifNode->recType));
|
||||
osg::ref_ptr<osg::Drawable> drawable;
|
||||
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
|
||||
handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags);
|
||||
// If the record had no valid geometry data in it, early-out
|
||||
if (geom->empty())
|
||||
return;
|
||||
osg::ref_ptr<osg::Drawable> drawable;
|
||||
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
|
||||
{
|
||||
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
||||
|
@ -1282,6 +1295,8 @@ namespace NifOsg
|
|||
assert(isTypeGeometry(nifNode->recType));
|
||||
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
||||
handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags);
|
||||
if (geometry->empty())
|
||||
return;
|
||||
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
|
||||
rig->setSourceGeometry(geometry);
|
||||
rig->setName(nifNode->name);
|
||||
|
@ -1481,6 +1496,17 @@ namespace NifOsg
|
|||
return image;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::TexEnvCombine> createEmissiveTexEnv()
|
||||
{
|
||||
osg::ref_ptr<osg::TexEnvCombine> texEnv(new osg::TexEnvCombine);
|
||||
texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE);
|
||||
texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS);
|
||||
texEnv->setCombine_RGB(osg::TexEnvCombine::ADD);
|
||||
texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
|
||||
texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE);
|
||||
return texEnv;
|
||||
}
|
||||
|
||||
void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector<unsigned int>& boundTextures, int animflags)
|
||||
{
|
||||
if (!boundTextures.empty())
|
||||
|
@ -1567,14 +1593,7 @@ namespace NifOsg
|
|||
|
||||
if (i == Nif::NiTexturingProperty::GlowTexture)
|
||||
{
|
||||
osg::TexEnvCombine* texEnv = new osg::TexEnvCombine;
|
||||
texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE);
|
||||
texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS);
|
||||
texEnv->setCombine_RGB(osg::TexEnvCombine::ADD);
|
||||
texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
|
||||
texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE);
|
||||
|
||||
stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON);
|
||||
stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON);
|
||||
}
|
||||
else if (i == Nif::NiTexturingProperty::DarkTexture)
|
||||
{
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
|
||||
namespace Nif
|
||||
{
|
||||
class NiGravity;
|
||||
class NiPlanarCollider;
|
||||
class NiSphericalCollider;
|
||||
class NiColorData;
|
||||
struct NiGravity;
|
||||
struct NiPlanarCollider;
|
||||
struct NiSphericalCollider;
|
||||
struct NiColorData;
|
||||
}
|
||||
|
||||
namespace NifOsg
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Resource
|
|||
mStartTime(0.0f)
|
||||
{
|
||||
const osgAnimation::ChannelList& channels = anim.getChannels();
|
||||
for (const osg::ref_ptr<osgAnimation::Channel> channel: channels)
|
||||
for (const auto& channel: channels)
|
||||
addChannel(channel.get()->clone());
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ namespace Resource
|
|||
|
||||
bool Animation::update (double time)
|
||||
{
|
||||
for (const osg::ref_ptr<osgAnimation::Channel> channel: mChannels)
|
||||
for (const auto& channel: mChannels)
|
||||
{
|
||||
channel->update(time, 1.0f, 0);
|
||||
}
|
||||
|
|
|
@ -20,8 +20,7 @@ BulletShape::BulletShape()
|
|||
BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op)
|
||||
: mCollisionShape(duplicateCollisionShape(copy.mCollisionShape))
|
||||
, mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape))
|
||||
, mCollisionBoxHalfExtents(copy.mCollisionBoxHalfExtents)
|
||||
, mCollisionBoxTranslate(copy.mCollisionBoxTranslate)
|
||||
, mCollisionBox(copy.mCollisionBox)
|
||||
, mAnimatedShapes(copy.mAnimatedShapes)
|
||||
{
|
||||
}
|
||||
|
@ -106,8 +105,7 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr<const BulletShape> source)
|
|||
: BulletShape()
|
||||
, mSource(source)
|
||||
{
|
||||
mCollisionBoxHalfExtents = source->mCollisionBoxHalfExtents;
|
||||
mCollisionBoxTranslate = source->mCollisionBoxTranslate;
|
||||
mCollisionBox = source->mCollisionBox;
|
||||
|
||||
mAnimatedShapes = source->mAnimatedShapes;
|
||||
|
||||
|
|
|
@ -27,10 +27,14 @@ namespace Resource
|
|||
btCollisionShape* mCollisionShape;
|
||||
btCollisionShape* mAvoidCollisionShape;
|
||||
|
||||
struct CollisionBox
|
||||
{
|
||||
osg::Vec3f extents;
|
||||
osg::Vec3f center;
|
||||
};
|
||||
// Used for actors. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures.
|
||||
// For now, use one file <-> one resource for simplicity.
|
||||
osg::Vec3f mCollisionBoxHalfExtents;
|
||||
osg::Vec3f mCollisionBoxTranslate;
|
||||
CollisionBox mCollisionBox;
|
||||
|
||||
// Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape
|
||||
// will be a btCompoundShape (which consists of one or more child shapes).
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Resource
|
|||
|
||||
void RetrieveAnimationsVisitor::apply(osg::Node& node)
|
||||
{
|
||||
if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("root"))
|
||||
if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("bip01"))
|
||||
{
|
||||
osg::ref_ptr<SceneUtil::OsgAnimationController> callback = new SceneUtil::OsgAnimationController();
|
||||
|
||||
|
@ -40,11 +40,9 @@ namespace Resource
|
|||
std::string animationName = animation->getName();
|
||||
std::string start = animationName + std::string(": start");
|
||||
std::string stop = animationName + std::string(": stop");
|
||||
std::string loopstart = animationName + std::string(": loop start");
|
||||
std::string loopstop = animationName + std::string(": loop stop");
|
||||
|
||||
const osgAnimation::ChannelList& channels = animation->getChannels();
|
||||
for (const osg::ref_ptr<osgAnimation::Channel> channel: channels)
|
||||
for (const auto& channel: channels)
|
||||
{
|
||||
mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed?
|
||||
}
|
||||
|
@ -60,8 +58,6 @@ namespace Resource
|
|||
// Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow"
|
||||
mTarget.mTextKeys.emplace(startTime, std::move(start));
|
||||
mTarget.mTextKeys.emplace(stopTime, std::move(stop));
|
||||
mTarget.mTextKeys.emplace(startTime, std::move(loopstart));
|
||||
mTarget.mTextKeys.emplace(stopTime, std::move(loopstop));
|
||||
|
||||
SceneUtil::EmulatedAnimation emulatedAnimation;
|
||||
emulatedAnimation.mStartTime = startTime;
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace SceneUtil
|
|||
void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt)
|
||||
{
|
||||
const osgAnimation::ChannelList& channels = mAnimation->getChannels();
|
||||
for (const osg::ref_ptr<osgAnimation::Channel> channel: channels)
|
||||
for (const auto& channel: channels)
|
||||
{
|
||||
const std::string& channelName = channel->getName();
|
||||
const std::string& channelTargetName = channel->getTargetName();
|
||||
|
@ -83,7 +83,7 @@ namespace SceneUtil
|
|||
{
|
||||
osgAnimation::UpdateMatrixTransform* umt = dynamic_cast<osgAnimation::UpdateMatrixTransform*>(cb);
|
||||
if (umt)
|
||||
if (node.getName() != "root") link(umt);
|
||||
if (node.getName() != "bip01") link(umt);
|
||||
cb = cb->getNestedCallback();
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ namespace SceneUtil
|
|||
//Find the correct animation based on time
|
||||
for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations)
|
||||
{
|
||||
if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime)
|
||||
if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime)
|
||||
{
|
||||
newTime = time - emulatedAnimation.mStartTime;
|
||||
animationName = emulatedAnimation.mName;
|
||||
|
@ -125,15 +125,15 @@ namespace SceneUtil
|
|||
}
|
||||
|
||||
//Find the root transform track in animation
|
||||
for (const osg::ref_ptr<Resource::Animation> mergedAnimationTrack : mMergedAnimationTracks)
|
||||
for (const auto& mergedAnimationTrack : mMergedAnimationTracks)
|
||||
{
|
||||
if (mergedAnimationTrack->getName() != animationName) continue;
|
||||
|
||||
const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels();
|
||||
|
||||
for (const osg::ref_ptr<osgAnimation::Channel> channel: channels)
|
||||
for (const auto& channel: channels)
|
||||
{
|
||||
if (channel->getTargetName() != "root" || channel->getName() != "transform") continue;
|
||||
if (channel->getTargetName() != "bip01" || channel->getName() != "transform") continue;
|
||||
|
||||
if ( osgAnimation::MatrixLinearSampler* templateSampler = dynamic_cast<osgAnimation::MatrixLinearSampler*> (channel->getSampler()) )
|
||||
{
|
||||
|
@ -150,7 +150,7 @@ namespace SceneUtil
|
|||
|
||||
void OsgAnimationController::update(float time, std::string animationName)
|
||||
{
|
||||
for (const osg::ref_ptr<Resource::Animation> mergedAnimationTrack : mMergedAnimationTracks)
|
||||
for (const auto& mergedAnimationTrack : mMergedAnimationTracks)
|
||||
{
|
||||
if (mergedAnimationTrack->getName() == animationName) mergedAnimationTrack->update(time);
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ namespace SceneUtil
|
|||
{
|
||||
if (mNeedToLink)
|
||||
{
|
||||
for (const osg::ref_ptr<Resource::Animation> mergedAnimationTrack : mMergedAnimationTracks)
|
||||
for (const auto& mergedAnimationTrack : mMergedAnimationTracks)
|
||||
{
|
||||
if (!mLinker.valid()) mLinker = new LinkVisitor();
|
||||
mLinker->setAnimation(mergedAnimationTrack);
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Gui
|
|||
|
||||
MyGUI::IntSize AutoSizedTextBox::getRequestedSize()
|
||||
{
|
||||
return getTextSize();
|
||||
return getCaption().empty() ? MyGUI::IntSize{0, 0} : getTextSize();
|
||||
}
|
||||
|
||||
void AutoSizedTextBox::setCaption(const MyGUI::UString& _value)
|
||||
|
|
|
@ -428,3 +428,14 @@ even if the fighting NPC is knocked out.
|
|||
This setting allows the player to steal items from fighting NPCs that were knocked out if enabled.
|
||||
|
||||
This setting can be controlled in Advanced tab of the launcher.
|
||||
|
||||
graphic herbalism
|
||||
-----------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
:Default: True
|
||||
|
||||
Some mods add harvestable container models. When this setting is enabled, activating a container using a harvestable model will visually harvest from it instead of opening the menu.
|
||||
|
||||
When this setting is turned off or when activating a regular container, the menu will open as usual.
|
||||
|
|
|
@ -267,6 +267,12 @@
|
|||
<Property key="TextAlign" value="HCenter Top"/>
|
||||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<Widget type="AutoSizedTextBox" skin="SandText" position="0 0 0 0" align="Left Top" name="LevelDetailText">
|
||||
<Property key="AutoResize" value="true"/>
|
||||
<Property key="MultiLine" value="true"/>
|
||||
<Property key="Shrink" value="true"/>
|
||||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<!-- Birthsign tooltip -->
|
||||
|
|
|
@ -724,6 +724,16 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="graphicHerbalismCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable graphic herbalism</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
|
|
Loading…
Reference in a new issue