Add OpenMW commits up to 1 Apr 2019

# Conflicts:
#	.travis.yml
#	CMakeLists.txt
#	apps/openmw/engine.cpp
#	apps/openmw/mwdialogue/dialoguemanagerimp.cpp
#	apps/openmw/mwgui/jailscreen.cpp
#	apps/openmw/mwgui/trainingwindow.cpp
#	apps/openmw/mwgui/travelwindow.cpp
#	apps/openmw/mwgui/waitdialog.cpp
pull/541/head
David Cernat 5 years ago
commit f671c0bddc

@ -3,6 +3,9 @@
Bug #2969: Scripted items can stack
Bug #2987: Editor: some chance and AI data fields can overflow
Bug #3006: 'else if' operator breaks script compilation
Bug #3109: SetPos/Position handles actors differently
Bug #3282: Unintended behaviour when assigning F3 and Windows keys
Bug #3623: Fix HiDPI on Windows
Bug #3733: Normal maps are inverted on mirrored UVs
Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable
@ -18,6 +21,7 @@
Bug #4720: Inventory avatar has shield with two-handed weapon during [un]equipping animation
Bug #4723: ResetActors command works incorrectly
Bug #4745: Editor: Interior cell lighting field values are not displayed as colors
Bug #4736: LandTexture records overrides do not work
Bug #4746: Non-solid player can't run or sneak
Bug #4747: Bones are not read from X.NIF file for NPC animation
Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state
@ -32,15 +36,26 @@
Bug #4813: Creatures with known file but no "Sound Gen Creature" assigned use default sounds
Bug #4815: "Finished" journal entry with lower index doesn't close journal, SetJournalIndex closes journal
Bug #4820: Spell absorption is broken
Bug #4823: Jail progress bar works incorrectly
Bug #4827: NiUVController is handled incorrectly
Bug #4828: Potion looping effects VFX are not shown for NPCs
Bug #4837: CTD when a mesh with NiLODNode root node with particles is loaded
Bug #4841: Russian localization ignores implicit keywords
Bug #4860: Actors outside of processing range visible for one frame after spawning
Bug #4867: Arbitrary text after local variable declarations breaks script compilation
Bug #4876: AI ratings handling inconsistencies
Bug #4877: Startup script executes only on a new game start
Bug #4888: Global variable stray explicit reference calls break script compilation
Bug #4896: Title screen music doesn't loop
Bug #4911: Editor: QOpenGLContext::swapBuffers() warning with Qt5
Bug #4916: Specular power (shininess) material parameter is ignored when shaders are used.
Bug #4922: Werewolves can not attack if the transformation happens during attack
Bug #4938: Strings from subrecords with actually empty headers can't be empty
Bug #4942: Hand-to-Hand attack type is chosen randomly when "always use best attack" is turned off
Feature #2229: Improve pathfinding AI
Feature #3442: Default values for fallbacks from ini file
Feature #3610: Option to invert X axis
Feature #3893: Implicit target for "set" function in console
Feature #3980: In-game option to disable controller
Feature #4209: Editor: Faction rank sub-table
Feature #4673: Weapon sheathing
@ -52,6 +67,7 @@
Feature #4887: Add openmw command option to set initial random seed
Feature #4890: Make Distant Terrain configurable
Task #4686: Upgrade media decoder to a more current FFmpeg API
Task #4695: Optimize Distant Terrain memory consumption
0.45.0
------

@ -1,11 +1,9 @@
#!/bin/sh -e
brew update
brew unlink cmake || true
brew install https://gist.githubusercontent.com/nikolaykasyanov/f36da224bdef42025e480f99fa21a82d/raw/7dd8b5ed2750198757f81c6bc6456e03541999bd/cmake.rb
brew switch cmake 3.12.4
brew outdated pkgconfig || brew upgrade pkgconfig
brew install qt
brew install ccache
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-110f3d3.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

@ -4,12 +4,15 @@ export CXX=clang++
export CC=clang
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
QT_PATH=`brew --prefix qt`
QT_PATH=$(brew --prefix qt)
CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache
mkdir build
cd build
cmake \
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \
-D CMAKE_C_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \
-D CMAKE_CXX_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \
-D CMAKE_OSX_SYSROOT="macosx10.14" \
-D CMAKE_BUILD_TYPE=Release \

@ -267,11 +267,12 @@ endif()
IF(BUILD_OPENMW OR BUILD_OPENCS)
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
include_directories(SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS})
set(USED_OSG_PLUGINS
osgdb_bmp
osgdb_dds
osgdb_freetype
osgdb_jpeg
osgdb_osg
osgdb_png
@ -858,8 +859,8 @@ endif()
# Apple bundling
if (OPENMW_OSX_DEPLOYMENT AND APPLE AND DESIRED_QT_VERSION MATCHES 5)
if (${CMAKE_MAJOR_VERSION} STREQUAL "3" AND ${CMAKE_MINOR_VERSION} STREQUAL "13")
message(FATAL_ERROR "macOS packaging is broken in CMake 3.13.*, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use an older version like 3.12.4")
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.4)
message(FATAL_ERROR "macOS packaging is broken in early CMake 3.13 releases, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use at least 3.13.4 or an older version like 3.12.4")
endif ()
get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE)

@ -739,7 +739,7 @@ void Record<ESM::Faction>::print()
std::cout << " Faction Reaction: "
<< mData.mData.mRankData[i].mFactReaction << std::endl;
}
for (const std::pair<std::string, int> &reaction : mData.mReactions)
for (const auto &reaction : mData.mReactions)
std::cout << " Reaction: " << reaction.second << " = " << reaction.first << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}

@ -102,14 +102,6 @@ bool parseOptions (int argc, char** argv, std::vector<std::string>& files)
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).
options(desc).positional(p).run();
bpo::store(valid_opts, variables);
}
catch(std::exception &e)
{
std::cout << "ERROR parsing arguments: " << e.what() << "\n\n"
<< desc << std::endl;
return false;
}
bpo::notify(variables);
if (variables.count ("help"))
{
@ -121,6 +113,13 @@ bool parseOptions (int argc, char** argv, std::vector<std::string>& files)
files = variables["input-file"].as< std::vector<std::string> >();
return true;
}
}
catch(std::exception &e)
{
std::cout << "ERROR parsing arguments: " << e.what() << "\n\n"
<< desc << std::endl;
return false;
}
std::cout << "No input files or directories specified!" << std::endl;
std::cout << desc << std::endl;

@ -195,7 +195,7 @@ namespace CSMPrefs
// Only activate the best match; in exact conflicts, this will favor the first shortcut added.
if (!potentials.empty())
{
std::sort(potentials.begin(), potentials.end(), ShortcutEventHandler::sort);
std::stable_sort(potentials.begin(), potentials.end(), ShortcutEventHandler::sort);
Shortcut* shortcut = potentials.front().second;
if (shortcut->getModifierStatus() && shortcut->getSecondaryMode() == Shortcut::SM_Replace)
@ -325,7 +325,7 @@ namespace CSMPrefs
if (left.first == Matches_WithMod && right.first == Matches_NoMod)
return true;
else
return left.second->getPosition() >= right.second->getPosition();
return left.second->getPosition() > right.second->getPosition();
}
void ShortcutEventHandler::widgetDestroyed()

@ -1006,7 +1006,7 @@ void CSMWorld::Data::loadFallbackEntries()
std::make_pair("PrisonMarker", "marker_prison.nif")
};
for (const std::pair<std::string, std::string> &marker : staticMarkers)
for (const auto &marker : staticMarkers)
{
if (mReferenceables.searchId (marker.first)==-1)
{
@ -1020,7 +1020,7 @@ void CSMWorld::Data::loadFallbackEntries()
}
}
for (const std::pair<std::string, std::string> &marker : doorMarkers)
for (const auto &marker : doorMarkers)
{
if (mReferenceables.searchId (marker.first)==-1)
{

@ -2,6 +2,7 @@
#include <memory>
#include <sstream>
#include <string>
#include <QMouseEvent>
#include <QApplication>
@ -615,7 +616,39 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint)
}
else if (hint[0]=='r')
{
/// \todo implement 'r' type hints
// syntax r:ref#number (e.g. r:ref#100)
char ignore;
std::istringstream stream (hint.c_str());
if (stream >> ignore) // ignore r
{
char ignore1; // : or ;
std::string refCode; // ref#number (e.g. ref#100)
while (stream >> ignore1 >> refCode) {}
//Find out cell coordinate
CSMWorld::IdTable& references = dynamic_cast<CSMWorld::IdTable&> (
*mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_References));
int cellColumn = references.findColumnIndex(CSMWorld::Columns::ColumnId_Cell);
QVariant cell = references.data(references.getModelIndex(refCode, cellColumn)).value<QVariant>();
QString cellqs = cell.toString();
std::istringstream streamCellCoord (cellqs.toStdString().c_str());
if (streamCellCoord >> ignore) //ignore #
{
// Current coordinate
int x, y;
// Loop through all the coordinates to add them to selection
while (streamCellCoord >> x >> y)
selection.add (CSMWorld::CellCoordinates (x, y));
// Mark that camera needs setup
mCamPositionSet=false;
}
}
}
setCellSelection (selection);

@ -18,10 +18,10 @@ namespace CSVRender
private:
const CSMWorld::Data& mData;
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) override;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override;
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override;
};
}

@ -44,6 +44,8 @@
End of tes3mp addition
*/
#include <components/detournavigator/navigator.hpp>
#include "mwinput/inputmanagerimp.hpp"
#include "mwgui/windowmanagerimp.hpp"
@ -289,6 +291,8 @@ bool OMW::Engine::frame(float frametime)
stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems());
stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads());
mEnvironment.getWorld()->getNavigator()->reportStats(frameNumber, *stats);
}
}
@ -313,6 +317,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mActivationDistanceOverride(-1)
, mGrab(true)
, mExportFonts(false)
, mRandomSeed(0)
, mScriptContext (0)
, mFSStrict (false)
, mScriptBlacklistUse (true)
@ -667,7 +672,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
// Create the world
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
mFileCollections, mContentFiles, mEncoder, mFallbackMap,
mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
mActivationDistanceOverride, mCellName, mResDir.string(), mCfgMgr.getUserDataPath().string()));
mEnvironment.getWorld()->setupPlayer();
input->setPlayer(&mEnvironment.getWorld()->getPlayer());
@ -783,6 +788,9 @@ void OMW::Engine::go()
*/
Log(Debug::Info) << "OSG version: " << osgGetVersion();
SDL_version sdlVersion;
SDL_GetVersion(&sdlVersion);
Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch;
Misc::Rng::init(mRandomSeed);
@ -855,22 +863,21 @@ void OMW::Engine::go()
{
// start in main menu
mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
try
{
// Is there an ini setting for this filename or something?
mEnvironment.getSoundManager()->streamMusic("Special/morrowind title.mp3");
mEnvironment.getSoundManager()->playTitleMusic();
std::string logo = mFallbackMap["Movies_Morrowind_Logo"];
if (!logo.empty())
mEnvironment.getWindowManager()->playVideo(logo, true);
}
catch (...) {}
}
else
{
mEnvironment.getStateManager()->newGame (!mNewGame);
}
if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running)
{
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
}
// Start the main rendering loop
osg::Timer frameTimer;
double simulationTime = 0.0;

@ -90,9 +90,9 @@ namespace MWBase
virtual void setPlayerClass (const ESM::Class& class_) = 0;
///< Set player class to custom class.
virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep) = 0;
virtual void restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) = 0;
virtual void rest(bool sleep) = 0;
virtual void rest(double hours, bool sleep) = 0;
///< If the player is sleeping or waiting, this should be called every hour.
/// @param sleep is the player sleeping or waiting?

@ -89,6 +89,9 @@ namespace MWBase
///< Start playing music from the selected folder
/// \param name of the folder that contains the playlist
virtual void playTitleMusic() = 0;
///< Start playing title music
virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename) = 0;
///< Make an actor say some text.
/// \param filename name of a sound file in "Sound/" in the data directory.

@ -406,7 +406,7 @@ namespace MWBase
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0;
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0;
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0;
///< @return an updated Ptr in case the Ptr's cell changes
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
@ -760,7 +760,7 @@ namespace MWBase
virtual bool isPlayerInJail() const = 0;
virtual void rest() = 0;
virtual void rest(double hours) = 0;
virtual void setPlayerTraveling(bool traveling) = 0;
virtual bool isPlayerTraveling() const = 0;

@ -131,9 +131,6 @@ namespace MWDialogue
End of tes3mp addition
*/
if (tok->isImplicitKeyword() && mTranslationDataStorage.hasTranslation())
continue;
if (mActorKnownTopics.count( topicId ))
mKnownTopics.insert( topicId );
}

@ -19,7 +19,6 @@ namespace MWDialogue
Token(const std::string & text, Type type) : mText(text), mType(type) {}
bool isExplicitLink() { return mType == ExplicitLink; }
bool isImplicitKeyword() { return mType == ImplicitKeyword; }
std::string mText;
Type mType;

@ -11,10 +11,12 @@
#include "../mwscript/extensions.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
namespace MWGui
{
@ -173,6 +175,12 @@ namespace MWGui
print("> " + command + "\n");
Compiler::Locals locals;
if (!mPtr.isEmpty())
{
std::string script = mPtr.getClass().getScript(mPtr);
if (!script.empty())
locals = MWBase::Environment::get().getScriptManager()->getLocals(script);
}
Compiler::Output output (locals);
if (compile (command + "\n", output))

@ -122,8 +122,7 @@ namespace MWGui
End of tes3mp addition
*/
for (int i=0; i<mDays*24; ++i)
MWBase::Environment::get().getMechanicsManager()->rest(true);
MWBase::Environment::get().getMechanicsManager()->rest(mDays * 24, true);
/*
Start of tes3mp change (major)

@ -304,11 +304,10 @@ namespace MWGui
MyGUI::IntSize requested = button->getRequestedSize();
button->setImageCoord(MyGUI::IntCoord(0, 0, requested.width, requested.height));
// Trim off some of the excessive padding
// TODO: perhaps do this within ImageButton?
int trim = 8;
button->setImageCoord(MyGUI::IntCoord(0, trim, requested.width, requested.height-trim));
int height = requested.height-trim*2;
int height = requested.height-16;
button->setImageTile(MyGUI::IntSize(requested.width, height));
button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, height);
curH += height;

@ -244,8 +244,7 @@ namespace MWGui
map->setNeedMouseFocus(false);
fog->setNeedMouseFocus(false);
mMapWidgets.push_back(map);
mFogWidgets.push_back(fog);
mMaps.emplace_back(map, fog);
}
}
}
@ -265,36 +264,37 @@ namespace MWGui
void LocalMapBase::applyFogOfWar()
{
TextureVector fogTextures;
for (int mx=0; mx<mNumCells; ++mx)
{
for (int my=0; my<mNumCells; ++my)
{
int x = mCurX + (mx - mCellDistance);
int y = mCurY + (-1*(my - mCellDistance));
MyGUI::ImageBox* fog = mFogWidgets[my + mNumCells*mx];
MapEntry& entry = mMaps[my + mNumCells*mx];
MyGUI::ImageBox* fog = entry.mFogWidget;
if (!mFogOfWarToggled || !mFogOfWarEnabled)
{
fog->setImageTexture("");
entry.mFogTexture.reset();
continue;
}
osg::ref_ptr<osg::Texture2D> tex = mLocalMapRender->getFogOfWarTexture(x, y);
if (tex)
{
std::shared_ptr<MyGUI::ITexture> myguitex (new osgMyGUI::OSGTexture(tex));
fog->setRenderItemTexture(myguitex.get());
entry.mFogTexture.reset(new osgMyGUI::OSGTexture(tex));
fog->setRenderItemTexture(entry.mFogTexture.get());
fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f));
fogTextures.push_back(myguitex);
}
else
{
fog->setImageTexture("black");
entry.mFogTexture.reset();
}
}
}
// Move the textures we just set into mFogTextures, and move the previous textures into fogTextures, for deletion when this function ends.
// Note, above we need to ensure that all widgets are getting a new texture set, lest we delete textures that are still in use.
mFogTextures.swap(fogTextures);
redraw();
}
@ -428,7 +428,6 @@ namespace MWGui
applyFogOfWar();
// Update the map textures
TextureVector textures;
for (int mx=0; mx<mNumCells; ++mx)
{
for (int my=0; my<mNumCells; ++my)
@ -436,21 +435,23 @@ namespace MWGui
int mapX = x + (mx - mCellDistance);
int mapY = y + (-1*(my - mCellDistance));
MyGUI::ImageBox* box = mMapWidgets[my + mNumCells*mx];
MapEntry& entry = mMaps[my + mNumCells*mx];
MyGUI::ImageBox* box = entry.mMapWidget;
osg::ref_ptr<osg::Texture2D> texture = mLocalMapRender->getMapTexture(mapX, mapY);
if (texture)
{
std::shared_ptr<MyGUI::ITexture> guiTex (new osgMyGUI::OSGTexture(texture));
textures.push_back(guiTex);
box->setRenderItemTexture(guiTex.get());
entry.mMapTexture.reset(new osgMyGUI::OSGTexture(texture));
box->setRenderItemTexture(entry.mMapTexture.get());
box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f));
}
else
{
box->setRenderItemTexture(nullptr);
entry.mMapTexture.reset();
}
}
}
mMapTextures.swap(textures);
// Delay the door markers update until scripts have been given a chance to run.
// If we don't do this, door markers that should be disabled will still appear on the map.
@ -896,22 +897,13 @@ namespace MWGui
void MapWindow::cellExplored(int x, int y)
{
mQueuedToExplore.push_back(std::make_pair(x,y));
mGlobalMapRender->cleanupCameras();
mGlobalMapRender->exploreCell(x, y, mLocalMapRender->getMapTexture(x, y));
}
void MapWindow::onFrame(float dt)
{
LocalMapBase::onFrame(dt);
mGlobalMapRender->cleanupCameras();
for (CellId& cellId : mQueuedToExplore)
{
mGlobalMapRender->exploreCell(cellId.first, cellId.second, mLocalMapRender->getMapTexture(cellId.first, cellId.second));
}
mQueuedToExplore.clear();
NoDrop::onFrame(dt);
}
@ -1128,8 +1120,8 @@ namespace MWGui
NoDrop::setAlpha(alpha);
// can't allow showing map with partial transparency, as the fog of war will also go transparent
// and reveal parts of the map you shouldn't be able to see
for (MyGUI::ImageBox* widget : mMapWidgets)
widget->setVisible(alpha == 1);
for (MapEntry& entry : mMaps)
entry.mMapWidget->setVisible(alpha == 1);
}
void MapWindow::customMarkerCreated(MyGUI::Widget *marker)

@ -148,12 +148,17 @@ namespace MWGui
// Stores markers that were placed by a player. May be shared between multiple map views.
CustomMarkerCollection& mCustomMarkers;
std::vector<MyGUI::ImageBox*> mMapWidgets;
std::vector<MyGUI::ImageBox*> mFogWidgets;
struct MapEntry
{
MapEntry(MyGUI::ImageBox* mapWidget, MyGUI::ImageBox* fogWidget)
: mMapWidget(mapWidget), mFogWidget(fogWidget) {}
typedef std::vector<std::shared_ptr<MyGUI::ITexture> > TextureVector;
TextureVector mMapTextures;
TextureVector mFogTextures;
MyGUI::ImageBox* mMapWidget;
MyGUI::ImageBox* mFogWidget;
std::shared_ptr<MyGUI::ITexture> mMapTexture;
std::shared_ptr<MyGUI::ITexture> mFogTexture;
};
std::vector<MapEntry> mMaps;
// Keep track of created marker widgets, just to easily remove them later.
std::vector<MyGUI::Widget*> mDoorMarkerWidgets;
@ -333,10 +338,6 @@ namespace MWGui
typedef std::pair<int, int> CellId;
std::set<CellId> mMarkers;
// Cells that should be explored in the next frame (i.e. their map revealed on the global map)
// We can't do this immediately, because the map update is not immediate either (see mNeedMapUpdate in scene.cpp)
std::vector<CellId> mQueuedToExplore;
MyGUI::Button* mEventBoxGlobal;
MyGUI::Button* mEventBoxLocal;

@ -141,7 +141,8 @@ namespace MWGui
MyGUI::ScrollBar* scroll = current->castType<MyGUI::ScrollBar>();
std::string valueStr;
if (getSettingValueType(current) == "Float")
std::string valueType = getSettingValueType(current);
if (valueType == "Float" || valueType == "Integer")
{
// TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget
float min,max;
@ -438,14 +439,18 @@ namespace MWGui
if (getSettingType(scroller) == "Slider")
{
std::string valueStr;
if (getSettingValueType(scroller) == "Float")
std::string valueType = getSettingValueType(scroller);
if (valueType == "Float" || valueType == "Integer")
{
float value = pos / float(scroller->getScrollRange()-1);
float min,max;
getSettingMinMax(scroller, min, max);
value = min + (max-min) * value;
if (valueType == "Float")
Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value);
else
Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), (int)value);
valueStr = MyGUI::utility::toString(int(value));
}
else

@ -79,14 +79,11 @@ namespace MWGui
static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fMagicStartIconBlink")->mValue.getFloat();
std::vector<MagicEffectInfo>& effectInfos = effectInfoPair.second;
bool addNewLine = true;
bool addNewLine = false;
for (const MagicEffectInfo& effectInfo : effectInfos)
{
if (addNewLine)
{
sourcesDescription += "\n";
addNewLine = false;
}
// if at least one of the effect sources is permanent, the effect will never wear off
if (effectInfo.mPermanent)
@ -161,6 +158,8 @@ namespace MWGui
sourcesDescription += MWGui::ToolTips::toString(duration) + "s";
}
}
addNewLine = true;
}
if (remainingDuration > 0.f)

@ -169,8 +169,7 @@ namespace MWGui
npcStats.setGoldPool(npcStats.getGoldPool() + price);
// advance time
MWBase::Environment::get().getMechanicsManager()->rest(false);
MWBase::Environment::get().getMechanicsManager()->rest(false);
MWBase::Environment::get().getMechanicsManager()->rest(2, false);
/*
Start of tes3mp change (major)

@ -174,10 +174,7 @@ namespace MWGui
ESM::Position playerPos = player.getRefData().getPosition();
float d = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length();
int hours = static_cast<int>(d /MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fTravelTimeMult")->mValue.getFloat());
for(int i = 0;i < hours;i++)
{
MWBase::Environment::get().getMechanicsManager ()->rest (true);
}
MWBase::Environment::get().getMechanicsManager()->rest(hours, true);
/*
Start of tes3mp change (major)

@ -271,7 +271,7 @@ namespace MWGui
void WaitDialog::onWaitingProgressChanged(int cur, int total)
{
mProgressBar.setProgress(cur, total);
MWBase::Environment::get().getMechanicsManager()->rest(mSleeping);
MWBase::Environment::get().getMechanicsManager()->rest(1, mSleeping);
/*
Start of tes3mp change (major)

@ -1070,9 +1070,6 @@ namespace MWGui
updateMap();
if (!mMap->isVisible())
mMap->onFrame(frameDuration);
mHud->onFrame(frameDuration);
mDebugWindow->onFrame(frameDuration);

@ -208,8 +208,6 @@ namespace MWInput
void InputManager::handleGuiArrowKey(int action)
{
// Temporary shut-down of this function until deemed necessary.
return;
if (SDL_IsTextInputActive())
return;
@ -414,7 +412,8 @@ namespace MWInput
case A_MoveRight:
case A_MoveForward:
case A_MoveBackward:
handleGuiArrowKey(action);
// Temporary shut-down of this function until deemed necessary.
//handleGuiArrowKey(action);
break;
case A_Journal:
toggleJournal ();
@ -1905,6 +1904,17 @@ namespace MWInput
MWBase::Environment::get().getWindowManager ()->notifyInputActionBound ();
return;
}
// Disallow binding reserved keys
if (key == SDL_SCANCODE_F3 || key == SDL_SCANCODE_F4 || key == SDL_SCANCODE_F10 || key == SDL_SCANCODE_F11)
return;
#ifndef __APPLE__
// Disallow binding Windows/Meta keys
if (key == SDL_SCANCODE_LGUI || key == SDL_SCANCODE_RGUI)
return;
#endif
if(!mDetectingKeyboard)
return;

@ -147,23 +147,45 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
health = 0.1f * endurance;
magicka = 0;
if (!stunted)
{
float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat ();
magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
}
}
}
namespace MWMechanics
{
class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
{
public:
float mRemainingTime;
GetStuntedMagickaDuration(const MWWorld::Ptr& actor)
: mRemainingTime(0.f){}
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1)
{
if (mRemainingTime == -1) return;
if (key.mId == ESM::MagicEffect::StuntedMagicka)
{
if (totalTime == -1)
{
mRemainingTime = -1;
return;
}
if (remainingTime > mRemainingTime)
mRemainingTime = remainingTime;
}
}
};
class SoulTrap : public MWMechanics::EffectSourceVisitor
{
MWWorld::Ptr mCreature;
@ -611,7 +633,7 @@ namespace MWMechanics
creatureStats.setMagicka(magicka);
}
void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, bool sleep)
void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep)
{
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
if (stats.isDead())
@ -625,13 +647,37 @@ namespace MWMechanics
getRestorationPerHourOfSleep(ptr, health, magicka);
DynamicStat<float> stat = stats.getHealth();
stat.setCurrent(stat.getCurrent() + health);
stat.setCurrent(stat.getCurrent() + health * hours);
stats.setHealth(stat);
double restoreHours = hours;
bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
if (stunted)
{
// Stunted Magicka effect should be taken into account.
GetStuntedMagickaDuration visitor(ptr);
stats.getActiveSpells().visitEffectSources(visitor);
stats.getSpells().visitEffectSources(visitor);
if (ptr.getClass().hasInventoryStore(ptr))
ptr.getClass().getInventoryStore(ptr).visitEffectSources(visitor);
// Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours.
if (visitor.mRemainingTime > 0)
{
double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
restoreHours = std::max(0.0, hours - visitor.mRemainingTime * timeScale / 3600.f);
}
else if (visitor.mRemainingTime == -1)
restoreHours = 0;
}
if (restoreHours > 0)
{
stat = stats.getMagicka();
stat.setCurrent(stat.getCurrent() + magicka);
stat.setCurrent(stat.getCurrent() + magicka * restoreHours);
stats.setMagicka(stat);
}
}
// Current fatigue can be above base value due to a fortify effect.
// In that case stop here and don't try to restore.
@ -653,7 +699,7 @@ namespace MWMechanics
float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance);
x *= fEndFatigueMult * endurance;
fatigue.setCurrent (fatigue.getCurrent() + 3600 * x);
fatigue.setCurrent (fatigue.getCurrent() + 3600 * x * hours);
stats.setFatigue (fatigue);
}
@ -1925,9 +1971,9 @@ namespace MWMechanics
}
}
void Actors::rest(bool sleep)
void Actors::rest(double hours, bool sleep)
{
float duration = 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor();
float duration = hours * 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor();
const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
@ -1937,7 +1983,7 @@ namespace MWMechanics
continue;
if (!sleep || iter->first == player)
restoreDynamicStats(iter->first, sleep);
restoreDynamicStats(iter->first, hours, sleep);
if ((!iter->first.getRefData().getBaseNode()) ||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange)
@ -2049,11 +2095,12 @@ namespace MWMechanics
getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour);
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
float healthHours = healthPerHour > 0
? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour
: 1.0f;
float magickaHours = magickaPerHour > 0
float magickaHours = magickaPerHour > 0 && !stunted
? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour
: 1.0f;

@ -121,13 +121,13 @@ namespace MWMechanics
void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor,
MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance);
void rest(bool sleep);
///< Update actors while the player is waiting or sleeping. This should be called every hour.
void rest(double hours, bool sleep);
///< Update actors while the player is waiting or sleeping.
void updateSneaking(CharacterController* ctrl, float duration);
///< Update the sneaking indicator state according to the given player character controller.
void restoreDynamicStats(const MWWorld::Ptr& actor, bool sleep);
void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep);
int getHoursToRest(const MWWorld::Ptr& ptr) const;
///< Calculate how many hours the given actor needs to rest in order to be fully healed

@ -13,6 +13,8 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwphysics/collisiontype.hpp"
#include "pathgrid.hpp"
#include "creaturestats.hpp"
#include "steering.hpp"
@ -45,6 +47,16 @@ namespace MWMechanics
std::string("idle9"),
};
namespace
{
inline int getCountBeforeReset(const MWWorld::ConstPtr& actor)
{
if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor))
return 1;
return COUNT_BEFORE_RESET;
}
}
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)),
@ -298,7 +310,8 @@ namespace MWMechanics
const auto currentPosition = actor.getRefData().getPosition().asVec3();
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
do {
// Determine a random location within radius of original position
const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance;
@ -309,13 +322,22 @@ namespace MWMechanics
mDestination = osg::Vec3f(destinationX, destinationY, destinationZ);
// Check if land creature will walk onto water or if water creature will swim onto land
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) ||
(isWaterCreature && !destinationThroughGround(currentPosition, mDestination)))
if (!isWaterCreature && destinationIsAtWater(actor, mDestination))
continue;
if ((isWaterCreature || isFlyingCreature) && destinationThroughGround(currentPosition, mDestination))
continue;
if (isWaterCreature || isFlyingCreature)
{
mPathFinder.buildStraightPath(mDestination);
}
else
{
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor);
mPathFinder.buildPath(actor, currentPosition, mDestination, actor.getCell(),
getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor));
mPathFinder.addPointToPath(mDestination);
mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents,
getNavigatorFlags(actor));
}
if (mPathFinder.isPathConstructed())
{
@ -323,8 +345,8 @@ namespace MWMechanics
mHasDestination = true;
mUsePathgrid = false;
}
return;
}
break;
} while (--attempts);
}
@ -342,8 +364,10 @@ namespace MWMechanics
* Returns true if the start to end point travels through a collision point (land).
*/
bool AiWander::destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination) {
const int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door;
return MWBase::Environment::get().getWorld()->castRay(startPoint.x(), startPoint.y(), startPoint.z(),
destination.x(), destination.y(), destination.z());
destination.x(), destination.y(), destination.z(),
mask);
}
void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) {
@ -479,7 +503,7 @@ namespace MWMechanics
}
// if stuck for sufficiently long, act like current location was the destination
if (storage.mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
if (storage.mStuckCount >= getCountBeforeReset(actor)) // something has gone wrong, reset
{
mObstacleCheck.clear();
stopWalking(actor, storage);

@ -1719,23 +1719,25 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
/*
End of tes3mp change (major)
*/
{
if (isWeapon)
{
if (Settings::Manager::getBool("best attack", "Game"))
{
if (isWeapon)
{
MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
mAttackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase);
}
else
setAttackTypeBasedOnMovement();
}
else
{
// There is no "best attack" for Hand-to-Hand
setAttackTypeRandomly(mAttackType);
}
}
else
{
setAttackTypeBasedOnMovement();
}
}
// else if (mPtr != getPlayer()) use mAttackType set by AiCombat
startKey = mAttackType+" start";
stopKey = mAttackType+" min attack";
@ -2697,6 +2699,13 @@ void CharacterController::forceStateUpdate()
return;
clearAnimQueue();
// Make sure we canceled the current attack or spellcasting,
// because we disabled attack animations anyway.
mCastingManualSpell = false;
mAttackingOrSpell = false;
if (mUpperBodyState != UpperCharState_Nothing)
mUpperBodyState = UpperCharState_WeapEquiped;
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
if(mDeathState != CharState_None)

@ -303,7 +303,9 @@ namespace MWMechanics
void MechanicsManager::advanceTime (float duration)
{
// Uses ingame time, but scaled to real time
duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor();
const float timeScaleFactor = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
if (timeScaleFactor != 0.0f)
duration /= timeScaleFactor;
MWWorld::Ptr player = getPlayer();
player.getClass().getInventoryStore(player).rechargeItems(duration);
}
@ -491,17 +493,17 @@ namespace MWMechanics
return mActors.isSneaking(ptr);
}
void MechanicsManager::rest(bool sleep)
void MechanicsManager::rest(double hours, bool sleep)
{
if (sleep)
MWBase::Environment::get().getWorld()->rest();
MWBase::Environment::get().getWorld()->rest(hours);
mActors.rest(sleep);
mActors.rest(hours, sleep);
}
void MechanicsManager::restoreDynamicStats(MWWorld::Ptr actor, bool sleep)
void MechanicsManager::restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep)
{
mActors.restoreDynamicStats(actor, sleep);
mActors.restoreDynamicStats(actor, hours, sleep);
}
int MechanicsManager::getHoursToRest() const

@ -95,9 +95,9 @@ namespace MWMechanics
virtual void setPlayerClass (const ESM::Class& class_) override;
///< Set player class to custom class.
virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep) override;
virtual void restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) override;
virtual void rest(bool sleep) override;
virtual void rest(double hours, bool sleep) override;
///< If the player is sleeping or waiting, this should be called every hour.
/// @param sleep is the player sleeping or waiting?

@ -2,6 +2,8 @@
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
@ -121,17 +123,19 @@ namespace MWMechanics
* u = how long to move sideways
*
*/
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance)
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration)
{
const MWWorld::Class& cls = actor.getClass();
ESM::Position pos = actor.getRefData().getPosition();
const ESM::Position pos = actor.getRefData().getPosition();
if (mDistSameSpot == -1)
mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor) * scaleMinimumDistance;
float distSameSpot = mDistSameSpot * duration;
{
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor) + 1.2 * std::max(halfExtents.x(), halfExtents.y());
}
bool samePosition = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2() < distSameSpot * distSameSpot;
const float distSameSpot = mDistSameSpot * duration;
const float squaredMovedDistance = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2();
const bool samePosition = squaredMovedDistance < distSameSpot * distSameSpot;
// update position
mPrevX = pos.pos[0];

@ -30,7 +30,7 @@ namespace MWMechanics
bool isEvading() const;
// Updates internal state, call each frame for moving actor
void update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f);
void update(const MWWorld::Ptr& actor, float duration);
// change direction to try to fix "stuck" actor
void takeEvasiveAction(MWMechanics::Movement& actorMovement) const;

@ -269,6 +269,13 @@ namespace MWMechanics
mPath.pop_front();
}
void PathFinder::buildStraightPath(const osg::Vec3f& endPoint)
{
mPath.clear();
mPath.push_back(endPoint);
mConstructed = true;
}
void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
{
@ -280,6 +287,16 @@ namespace MWMechanics
mConstructed = true;
}
void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags)
{
mPath.clear();
buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath));
mConstructed = true;
}
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags)

@ -72,9 +72,14 @@ namespace MWMechanics
mCell = nullptr;
}
void buildStraightPath(const osg::Vec3f& endPoint);
void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags);
void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags);

@ -170,7 +170,7 @@ namespace MWMechanics
float rating = rateEffects(enchantment->mEffects, actor, enemy);
rating *= 2; // prefer rechargable magic items over spells
rating *= 1.25f; // prefer rechargable magic items over spells
return rating;
}

@ -127,7 +127,9 @@ namespace MWMechanics
value = ref->mBase->mData.mCombat;
}
rating *= getHitChance(actor, enemy, value) / 100.f;
// Take hit chance in account, but do not allow rating become negative.
float chance = getHitChance(actor, enemy, value) / 100.f;
rating *= std::min(1.f, std::max(0.01f, chance));
if (weapon->mData.mType < ESM::Weapon::Arrow)
rating *= weapon->mData.mSpeed;

@ -242,6 +242,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
{
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
if (mScabbard)
resetControllers(mScabbard->getNode());
}

@ -1798,17 +1798,13 @@ namespace MWRender
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha));
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
stateset->addUniform(new osg::Uniform("colorMode", 0), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
mObjectRoot->setStateSet(stateset);
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
}
else
{
mObjectRoot->setStateSet(nullptr);
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
setRenderBin();

@ -329,7 +329,7 @@ namespace MWRender
camera->setViewMatrix(osg::Matrix::identity());
camera->setProjectionMatrix(osg::Matrix::identity());
camera->setProjectionResizePolicy(osg::Camera::FIXED);
camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setRenderOrder(osg::Camera::PRE_RENDER, 1); // Make sure the global map is rendered after the local map
y = mHeight - y - height; // convert top-left origin to bottom-left
camera->setViewport(x, y, width, height);

@ -12,16 +12,15 @@ namespace MWRender
{
LandManager::LandManager(int loadFlags)
: ResourceManager(nullptr)
: GenericResourceManager<std::pair<int, int> >(nullptr)
, mLoadFlags(loadFlags)
{
mCache = new CacheType;
}
osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y)
{
std::string idstr = std::to_string(x) + " " + std::to_string(y);
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(idstr);
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(std::make_pair(x,y));
if (obj)
return static_cast<ESMTerrain::LandObject*>(obj.get());
else
@ -30,7 +29,7 @@ osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y)
if (!land)
return nullptr;
osg::ref_ptr<ESMTerrain::LandObject> landObj (new ESMTerrain::LandObject(land, mLoadFlags));
mCache->addEntryToObjectCache(idstr, landObj.get());
mCache->addEntryToObjectCache(std::make_pair(x,y), landObj.get());
return landObj;
}
}

@ -3,6 +3,7 @@
#include <osg/Object>
#include <components/resource/objectcache.hpp>
#include <components/resource/resourcemanager.hpp>
#include <components/esmterrain/storage.hpp>
@ -14,7 +15,7 @@ namespace ESM
namespace MWRender
{
class LandManager : public Resource::ResourceManager
class LandManager : public Resource::GenericResourceManager<std::pair<int, int> >
{
public:
LandManager(int loadFlags);

@ -613,7 +613,8 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient
if (!segment.mFogOfWarImage || !segment.mMapTexture)
continue;
unsigned char* data = segment.mFogOfWarImage->data();
uint32_t* data = (uint32_t*)segment.mFogOfWarImage->data();
bool changed = false;
for (int texV = 0; texV<sFogOfWarResolution; ++texV)
{
for (int texU = 0; texU<sFogOfWarResolution; ++texU)
@ -625,17 +626,25 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient
uint8_t alpha = (clr >> 24);
alpha = std::min( alpha, (uint8_t) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) );
*(uint32_t*)data = (uint32_t) (alpha << 24);
uint32_t val = (uint32_t) (alpha << 24);
if ( *data != val)
{
*data = val;
changed = true;
}
data += 4;
++data;
}
}
if (changed)
{
segment.mHasFogState = true;
segment.mFogOfWarImage->dirty();
}
}
}
}
LocalMap::MapSegment::MapSegment()
: mHasFogState(false)

@ -54,10 +54,8 @@ std::string getVampireHead(const std::string& race, bool female)
if (sVampireMapping.find(thisCombination) == sVampireMapping.end())
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
for(MWWorld::Store<ESM::BodyPart>::iterator it = partStore.begin(); it != partStore.end(); ++it)
for (const ESM::BodyPart& bodypart : store.get<ESM::BodyPart>())
{
const ESM::BodyPart& bodypart = *it;
if (!bodypart.mData.mVampire)
continue;
if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)
@ -68,12 +66,11 @@ std::string getVampireHead(const std::string& race, bool female)
continue;
if (!Misc::StringUtils::ciEqual(bodypart.mRace, race))
continue;
sVampireMapping[thisCombination] = &*it;
sVampireMapping[thisCombination] = &bodypart;
}
}
if (sVampireMapping.find(thisCombination) == sVampireMapping.end())
sVampireMapping[thisCombination] = nullptr;
sVampireMapping.emplace(thisCombination, nullptr);
const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination];
if (!bodyPart)
@ -238,6 +235,18 @@ void HeadAnimationTime::setBlinkStop(float value)
// ----------------------------------------------------
NpcAnimation::NpcType NpcAnimation::getNpcType()
{
const MWWorld::Class &cls = mPtr.getClass();
NpcAnimation::NpcType curType = Type_Normal;
if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
curType = Type_Vampire;
if (cls.getNpcStats(mPtr).isWerewolf())
curType = Type_Werewolf;
return curType;
}
static NpcAnimation::PartBoneMap createPartListMap()
{
NpcAnimation::PartBoneMap result;
@ -283,7 +292,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> par
mViewMode(viewMode),
mShowWeapons(false),
mShowCarriedLeft(true),
mNpcType(Type_Normal),
mNpcType(getNpcType()),
mFirstPersonFieldOfView(firstPersonFieldOfView),
mSoundsDisabled(disableSounds),
mAccurateAiming(false),
@ -431,41 +440,39 @@ void NpcAnimation::updateNpcBase()
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Race *race = store.get<ESM::Race>().find(mNpc->mRace);
bool isWerewolf = (mNpcType == Type_Werewolf);
bool isVampire = (mNpcType == Type_Vampire);
NpcType curType = getNpcType();
bool isWerewolf = (curType == Type_Werewolf);
bool isVampire = (curType == Type_Vampire);
bool isFemale = !mNpc->isMale();
if (isWerewolf)
{
mHeadModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHead")->mModel;
mHairModel = "meshes\\" + store.get<ESM::BodyPart>().find("WerewolfHair")->mModel;
}
else
{
mHeadModel = "";
const std::string& vampireHead = getVampireHead(mNpc->mRace, isFemale);
if (isVampire && !vampireHead.empty())
mHeadModel = vampireHead;
else if (!mNpc->mHead.empty())
mHeadModel.clear();
mHairModel.clear();
std::string headName = isWerewolf ? "WerewolfHead" : mNpc->mHead;
std::string hairName = isWerewolf ? "WerewolfHair" : mNpc->mHair;
if (!headName.empty())
{
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(mNpc->mHead);
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(headName);
if (bp)
mHeadModel = "meshes\\" + bp->mModel;
else
Log(Debug::Warning) << "Warning: Failed to load body part '" << mNpc->mHead << "'";
Log(Debug::Warning) << "Warning: Failed to load body part '" << headName << "'";
}
mHairModel = "";
if (!mNpc->mHair.empty())
if (!hairName.empty())
{
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(mNpc->mHair);
const ESM::BodyPart* bp = store.get<ESM::BodyPart>().search(hairName);
if (bp)
mHairModel = "meshes\\" + bp->mModel;
else
Log(Debug::Warning) << "Warning: Failed to load body part '" << mNpc->mHair << "'";
}
Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'";
}
const std::string& vampireHead = getVampireHead(mNpc->mRace, isFemale);
if (!isWerewolf && isVampire && !vampireHead.empty())
mHeadModel = vampireHead;
bool is1stPerson = mViewMode == VM_FirstPerson;
bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
@ -473,7 +480,7 @@ void NpcAnimation::updateNpcBase()
defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS());
std::string smodel = defaultSkeleton;
if (!is1stPerson && !isWerewolf & !mNpc->mModel.empty())
if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty())
smodel = Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS());
setObjectRoot(smodel, true, true, false);
@ -517,14 +524,7 @@ void NpcAnimation::updateParts()
if (!mObjectRoot.get())
return;
const MWWorld::Class &cls = mPtr.getClass();
NpcType curType = Type_Normal;
if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
curType = Type_Vampire;
if (cls.getNpcStats(mPtr).isWerewolf())
curType = Type_Werewolf;
NpcType curType = getNpcType();
if (curType != mNpcType)
{
mNpcType = curType;
@ -632,7 +632,7 @@ void NpcAnimation::updateParts()
showWeapons(mShowWeapons);
showCarriedLeft(mShowCarriedLeft);
bool isWerewolf = (mNpcType == Type_Werewolf);
bool isWerewolf = (getNpcType() == Type_Werewolf);
std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace));
const std::vector<const ESM::BodyPart*> &parts = getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf);
@ -649,9 +649,6 @@ void NpcAnimation::updateParts()
if (wasArrowAttached)
attachArrow();
if (mAlpha != 1.f)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
@ -730,10 +727,7 @@ void NpcAnimation::removePartGroup(int group)
bool NpcAnimation::isFirstPersonPart(const ESM::BodyPart* bodypart)
{
return (bodypart->mId.size() >= 3)
&& bodypart->mId[bodypart->mId.size()-3] == '1'
&& bodypart->mId[bodypart->mId.size()-2] == 's'
&& bodypart->mId[bodypart->mId.size()-1] == 't';
return bodypart->mId.size() >= 3 && bodypart->mId.substr(bodypart->mId.size()-3, 3) == "1st";
}
bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart)
@ -793,16 +787,16 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
osg::Object* obj = node->getUserDataContainer()->getUserObject(i);
if (NifOsg::TextKeyMapHolder* keys = dynamic_cast<NifOsg::TextKeyMapHolder*>(obj))
{
for (NifOsg::TextKeyMap::const_iterator it = keys->mTextKeys.begin(); it != keys->mTextKeys.end(); ++it)
for (const auto &key : keys->mTextKeys)
{
if (Misc::StringUtils::ciEqual(it->second, "talk: start"))
mHeadAnimationTime->setTalkStart(it->first);
if (Misc::StringUtils::ciEqual(it->second, "talk: stop"))
mHeadAnimationTime->setTalkStop(it->first);
if (Misc::StringUtils::ciEqual(it->second, "blink: start"))
mHeadAnimationTime->setBlinkStart(it->first);
if (Misc::StringUtils::ciEqual(it->second, "blink: stop"))
mHeadAnimationTime->setBlinkStop(it->first);
if (Misc::StringUtils::ciEqual(key.second, "talk: start"))
mHeadAnimationTime->setTalkStart(key.first);
if (Misc::StringUtils::ciEqual(key.second, "talk: stop"))
mHeadAnimationTime->setTalkStop(key.first);
if (Misc::StringUtils::ciEqual(key.second, "blink: start"))
mHeadAnimationTime->setBlinkStart(key.first);
if (Misc::StringUtils::ciEqual(key.second, "blink: stop"))
mHeadAnimationTime->setBlinkStop(key.first);
}
break;
@ -828,16 +822,15 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
const char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : "";
std::vector<ESM::PartReference>::const_iterator part(parts.begin());
for(;part != parts.end();++part)
for(const ESM::PartReference& part : parts)
{
const ESM::BodyPart *bodypart = 0;
if(!mNpc->isMale() && !part->mFemale.empty())
const ESM::BodyPart *bodypart = nullptr;
if(!mNpc->isMale() && !part.mFemale.empty())
{
bodypart = partStore.search(part->mFemale+ext);
bodypart = partStore.search(part.mFemale+ext);
if(!bodypart && mViewMode == VM_FirstPerson)
{
bodypart = partStore.search(part->mFemale);
bodypart = partStore.search(part.mFemale);
if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand ||
bodypart->mData.mPart == ESM::BodyPart::MP_Wrist ||
bodypart->mData.mPart == ESM::BodyPart::MP_Forearm ||
@ -845,14 +838,14 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::
bodypart = nullptr;
}
else if (!bodypart)
Log(Debug::Warning) << "Warning: Failed to find body part '" << part->mFemale << "'";
Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mFemale << "'";
}
if(!bodypart && !part->mMale.empty())
if(!bodypart && !part.mMale.empty())
{
bodypart = partStore.search(part->mMale+ext);
bodypart = partStore.search(part.mMale+ext);
if(!bodypart && mViewMode == VM_FirstPerson)
{
bodypart = partStore.search(part->mMale);
bodypart = partStore.search(part.mMale);
if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand ||
bodypart->mData.mPart == ESM::BodyPart::MP_Wrist ||
bodypart->mData.mPart == ESM::BodyPart::MP_Forearm ||
@ -860,13 +853,13 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector<ESM::
bodypart = nullptr;
}
else if (!bodypart)
Log(Debug::Warning) << "Warning: Failed to find body part '" << part->mMale << "'";
Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mMale << "'";
}
if(bodypart)
addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor);
addOrReplaceIndividualPart((ESM::PartReferenceType)part.mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor);
else
reserveIndividualPart((ESM::PartReferenceType)part->mPart, group, priority);
reserveIndividualPart((ESM::PartReferenceType)part.mPart, group, priority);
}
}
@ -885,7 +878,7 @@ void NpcAnimation::addControllers()
osg::MatrixTransform* node = found->second.get();
mFirstPersonNeckController = new NeckController(mObjectRoot.get());
node->addUpdateCallback(mFirstPersonNeckController);
mActiveControllers.insert(std::make_pair(node, mFirstPersonNeckController));
mActiveControllers.emplace(node, mFirstPersonNeckController);
}
}
else if (mViewMode == VM_Normal)
@ -918,9 +911,6 @@ void NpcAnimation::showWeapons(bool showWeapon)
attachArrow();
}
}
// Note: we will need to recreate shaders later if we use weapon sheathing anyway, so there is no point to update them here
if (mAlpha != 1.f && !mWeaponSheathing)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
else
{
@ -932,10 +922,6 @@ void NpcAnimation::showWeapons(bool showWeapon)
updateHolsteredWeapon(!mShowWeapons);
updateQuiver();
// Recreate shaders for invisible actors, otherwise sheath nodes will be visible
if (mAlpha != 1.f && mWeaponSheathing)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
void NpcAnimation::showCarriedLeft(bool show)
@ -953,8 +939,6 @@ void NpcAnimation::showCarriedLeft(bool show)
if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield])
addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get<ESM::Light>()->mBase);
}
if (mAlpha != 1.f)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
else
removeIndividualPart(ESM::PRT_Shield);
@ -1087,40 +1071,39 @@ const std::vector<const ESM::BodyPart *>& NpcAnimation::getBodyParts(const std::
std::vector<const ESM::BodyPart*>& parts = sRaceMapping[std::make_pair(race, flags)];
typedef std::multimap<ESM::BodyPart::MeshPart,ESM::PartReferenceType> BodyPartMapType;
static BodyPartMapType sBodyPartMap;
if(sBodyPartMap.empty())
{
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg));
sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail));
}
static const BodyPartMapType sBodyPartMap =
{
{ESM::BodyPart::MP_Neck, ESM::PRT_Neck},
{ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass},
{ESM::BodyPart::MP_Groin, ESM::PRT_Groin},
{ESM::BodyPart::MP_Hand, ESM::PRT_RHand},
{ESM::BodyPart::MP_Hand, ESM::PRT_LHand},
{ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist},
{ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist},
{ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm},
{ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm},
{ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm},
{ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm},
{ESM::BodyPart::MP_Foot, ESM::PRT_RFoot},
{ESM::BodyPart::MP_Foot, ESM::PRT_LFoot},
{ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle},
{ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle},
{ESM::BodyPart::MP_Knee, ESM::PRT_RKnee},
{ESM::BodyPart::MP_Knee, ESM::PRT_LKnee},
{ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg},
{ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg},
{ESM::BodyPart::MP_Tail, ESM::PRT_Tail}
};
parts.resize(ESM::PRT_Count, nullptr);
if (werewolf)
return parts;
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
for(MWWorld::Store<ESM::BodyPart>::iterator it = partStore.begin(); it != partStore.end(); ++it)
for(const ESM::BodyPart& bodypart : store.get<ESM::BodyPart>())
{
if(werewolf)
break;
const ESM::BodyPart& bodypart = *it;
if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable)
continue;
if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)

@ -74,6 +74,8 @@ private:
void updateNpcBase();
NpcType getNpcType();
PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename,
const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr);

@ -314,6 +314,7 @@ namespace MWRender
mTerrain->setDefaultViewer(mViewer->getCamera());
mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells"));
mTerrain->setWorkQueue(mWorkQueue.get());
mCamera.reset(new Camera(mViewer->getCamera()));
@ -371,8 +372,10 @@ namespace MWRender
mNearClip = Settings::Manager::getFloat("near clip", "Camera");
mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera");
mFieldOfView = Settings::Manager::getFloat("field of view", "Camera");
mFirstPersonFieldOfView = Settings::Manager::getFloat("first person field of view", "Camera");
float fov = Settings::Manager::getFloat("field of view", "Camera");
mFieldOfView = std::min(std::max(1.f, fov), 179.f);
float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera");
mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f);
mStateUpdater->setFogEnd(mViewDistance);
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
@ -1198,6 +1201,12 @@ namespace MWRender
mUniformNear->set(mNearClip);
mUniformFar->set(mViewDistance);
// Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear.
// Limit FOV here just for sure, otherwise viewing distance can be too high.
fov = std::min(mFieldOfView, 140.f);
float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f);
mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f));
}
void RenderingManager::updateTextureFiltering()
@ -1446,8 +1455,8 @@ namespace MWRender
{
try
{
const auto locked = it->second.lockConst();
mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(),
const auto locked = it->second->lockConst();
mNavMesh->update(locked->getImpl(), mNavMeshNumber, locked->getGeneration(),
locked->getNavMeshRevision(), mNavigator.getSettings());
}
catch (const std::exception& e)

@ -22,6 +22,15 @@ namespace MWRender
mResourceSystem->removeResourceManager(mLandManager.get());
}
bool TerrainStorage::hasData(int cellX, int cellY)
{
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
const ESM::Land* land = esmStore.get<ESM::Land>().search(cellX, cellY);
return land != nullptr;
}
void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY)
{
minX = 0, minY = 0, maxX = 0, maxY = 0;

@ -20,11 +20,13 @@ namespace MWRender
TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false);
~TerrainStorage();
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) override;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override;
virtual bool hasData(int cellX, int cellY) override;
/// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override;
LandManager* getLandManager() const;

@ -285,11 +285,11 @@ namespace MWScript
MWWorld::Ptr updated = ptr;
if(axis == "x")
{
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az);
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az,true);
}
else if(axis == "y")
{
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az);
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az,true);
}
else if(axis == "z")
{
@ -304,7 +304,7 @@ namespace MWScript
pos = terrainHeight;
}
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos);
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos,true);
}
else
throw std::runtime_error ("invalid axis: " + axis);
@ -447,7 +447,7 @@ namespace MWScript
}
else
{
ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z);
ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true);
}
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr);

@ -96,25 +96,25 @@ bool FFmpeg_Decoder::getAVAudioData()
return false;
do {
if(mPacket.size == 0 && !getNextPacket())
return false;
/* Decode some data, and check for errors */
int ret = 0;
ret = avcodec_receive_frame(mCodecCtx, mFrame);
if (ret == 0)
got_frame = true;
int ret = avcodec_receive_frame(mCodecCtx, mFrame);
if (ret == AVERROR(EAGAIN))
ret = 0;
if (ret == 0)
{
if (mPacket.size == 0 && !getNextPacket())
return false;
ret = avcodec_send_packet(mCodecCtx, &mPacket);
if (ret < 0 && ret != AVERROR(EAGAIN))
av_packet_unref(&mPacket);
if (ret == 0)
continue;
}
if (ret != 0)
return false;
av_packet_unref(&mPacket);
if (!got_frame || mFrame->nb_samples == 0)
if (mFrame->nb_samples == 0)
continue;
got_frame = true;
if(mSwr)
{
@ -138,7 +138,7 @@ bool FFmpeg_Decoder::getAVAudioData()
else
mFrameData = &mFrame->data[0];
} while(!got_frame || mFrame->nb_samples == 0);
} while(!got_frame);
mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate;
return true;

@ -471,6 +471,36 @@ namespace MWSound
startRandomTitle();
}
void SoundManager::playTitleMusic()
{
if (mCurrentPlaylist == "Title")
return;
if (mMusicFiles.find("Title") == mMusicFiles.end())
{
std::vector<std::string> filelist;
const std::map<std::string, VFS::File*>& index = mVFS->getIndex();
// Is there an ini setting for this filename or something?
std::string filename = "music/special/morrowind title.mp3";
auto found = index.find(filename);
if (found != index.end())
{
filelist.emplace_back(found->first);
mMusicFiles["Title"] = filelist;
}
else
{
Log(Debug::Warning) << "Title music not found";
return;
}
}
if (mMusicFiles["Title"].empty())
return;
mCurrentPlaylist = "Title";
startRandomTitle();
}
void SoundManager::say(const MWWorld::ConstPtr &ptr, const std::string &filename)
{
@ -1122,10 +1152,10 @@ namespace MWSound
if(!mOutput->isInitialized())
return;
updateSounds(duration);
if (MWBase::Environment::get().getStateManager()->getState()!=
MWBase::StateManager::State_NoGame)
{
updateSounds(duration);
updateRegionSound(duration);
updateWaterSound(duration);
}

@ -168,6 +168,9 @@ namespace MWSound
///< Start playing music from the selected folder
/// \param name of the folder that contains the playlist
virtual void playTitleMusic();
///< Start playing title music
virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename);
///< Make an actor say some text.
/// \param filename name of a sound file in "Sound/" in the data directory.

@ -381,7 +381,7 @@ namespace MWWorld
{
for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size() && !mAbort; ++i)
{
mWorld->preload(mTerrainViews[i], mPreloadPositions[i]);
mWorld->preload(mTerrainViews[i], mPreloadPositions[i], mAbort);
mTerrainViews[i]->reset(0);
}
}

@ -150,16 +150,16 @@ MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name)
return &result->second;
}
void MWWorld::Cells::rest ()
void MWWorld::Cells::rest (double hours)
{
for (auto &interior : mInteriors)
{
interior.second.rest();
interior.second.rest(hours);
}
for (auto &exterior : mExteriors)
{
exterior.second.rest();
exterior.second.rest(hours);
}
}

@ -61,7 +61,7 @@ namespace MWWorld
/// @note name must be lower case
Ptr getPtr (const std::string& name);
void rest ();
void rest (double hours);
/// Get all Ptrs referencing \a name in exterior cells
/// @note Due to the current implementation of getPtr this only supports one Ptr per cell.

@ -1152,7 +1152,7 @@ namespace MWWorld
}
}
void CellStore::rest()
void CellStore::rest(double hours)
{
if (mState == State_Loaded)
{
@ -1161,7 +1161,7 @@ namespace MWWorld
Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{
MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, true);
MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true);
}
}
for (CellRefList<ESM::NPC>::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it)
@ -1169,7 +1169,7 @@ namespace MWWorld
Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{
MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, true);
MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true);
}
}
}

@ -183,7 +183,7 @@ namespace MWWorld
/// @return updated MWWorld::Ptr with the new CellStore pointer set.
MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo);
void rest();
void rest(double hours);
/// Make a copy of the given object and insert it into this cell.
/// @note If you get a linker error here, this means the given type can not be inserted into a cell.

@ -360,7 +360,7 @@ namespace MWWorld
{
if (const auto object = mPhysics->getObject(ptr))
navigator->removeObject(DetourNavigator::ObjectId(object));
else if (const auto actor = mPhysics->getActor(ptr))
else if (mPhysics->getActor(ptr))
{
navigator->removeAgent(world->getPathfindingHalfExtents(ptr));
mRendering.removeActorPath(ptr);
@ -892,7 +892,7 @@ namespace MWWorld
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
navigator->update(player.getRefData().getPosition().asVec3());
}
else if (const auto actor = mPhysics->getActor(ptr))
else if (mPhysics->getActor(ptr))
{
navigator->removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr));
}

@ -387,6 +387,21 @@ namespace MWWorld
assert(plugin < mStatic.size());
// Replace texture for records with given ID and index from all plugins.
for (unsigned int i=0; i<mStatic.size(); i++)
{
ESM::LandTexture* tex = const_cast<ESM::LandTexture*>(search(lt.mIndex, i));
if (tex)
{
const std::string texId = Misc::StringUtils::lowerCase(tex->mId);
const std::string ltId = Misc::StringUtils::lowerCase(lt.mId);
if (texId == ltId)
{
tex->mTexture = lt.mTexture;
}
}
}
LandTextureList &ltexl = mStatic[plugin];
if(lt.mIndex + 1 > (int)ltexl.size())
ltexl.resize(lt.mIndex+1);

@ -178,12 +178,12 @@ namespace MWWorld
const Files::Collections& fileCollections,
const std::vector<std::string>& contentFiles,
ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap,
int activationDistanceOverride, const std::string& startCell, const std::string& startupScript,
int activationDistanceOverride, const std::string& startCell,
const std::string& resourcePath, const std::string& userDataPath)
: mResourceSystem(resourceSystem), mFallback(fallbackMap), mLocalScripts (mStore),
mSky (true), mCells (mStore, mEsm),
mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mUserDataPath(userDataPath),
mActivationDistanceOverride (activationDistanceOverride), mStartupScript(startupScript),
mActivationDistanceOverride (activationDistanceOverride),
mStartCell (startCell), mDistanceToFacedObject(-1), mTeleportEnabled(true),
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
@ -337,9 +337,6 @@ namespace MWWorld
if (!mPhysics->toggleCollisionMode())
mPhysics->toggleCollisionMode();
if (!mStartupScript.empty())
MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript);
MWBase::Environment::get().getWindowManager()->updatePlayer();
}
@ -503,7 +500,7 @@ namespace MWWorld
gmst["iWereWolfBounty"] = ESM::Variant(1000);
gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f);
for (const std::pair<std::string, ESM::Variant> &params : gmst)
for (const auto &params : gmst)
{
if (!mStore.get<ESM::GameSetting>().search(params.first))
{
@ -533,7 +530,7 @@ namespace MWWorld
globals["crimegoldturnin"] = ESM::Variant(0);
globals["pchasturnin"] = ESM::Variant(0);
for (const std::pair<std::string, ESM::Variant> &params : globals)
for (const auto &params : globals)
{
if (!mStore.get<ESM::Global>().search(params.first))
{
@ -552,7 +549,7 @@ namespace MWWorld
statics["templemarker"] = "marker_temple.nif";
statics["travelmarker"] = "marker_travel.nif";
for (const std::pair<std::string, std::string> &params : statics)
for (const auto &params : statics)
{
if (!mStore.get<ESM::Static>().search(params.first))
{
@ -566,7 +563,7 @@ namespace MWWorld
std::map<std::string, std::string> doors;
doors["prisonmarker"] = "marker_prison.nif";
for (const std::pair<std::string, std::string> &params : doors)
for (const auto &params : doors)
{
if (!mStore.get<ESM::Door>().search(params.first))
{
@ -1512,23 +1509,24 @@ namespace MWWorld
return newPtr;
}
MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics)
MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive)
{
CellStore *cell = ptr.getCell();
if (cell->isExterior()) {
int cellX, cellY;
positionToIndex(x, y, cellX, cellY);
cell = getExterior(cellX, cellY);
}
CellStore* cell = ptr.getCell();
CellStore* newCell = getExterior(cellX, cellY);
bool isCellActive = getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell);
if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor()))
cell = newCell;
return moveObject(ptr, cell, x, y, z, movePhysics);
}
MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z)
MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive)
{
return moveObjectImp(ptr, x, y, z);
return moveObjectImp(ptr, x, y, z, true, moveToActive);
}
void World::scaleObject (const Ptr& ptr, float scale)
@ -3698,9 +3696,9 @@ namespace MWWorld
return closestMarker;
}
void World::rest()
void World::rest(double hours)
{
mCells.rest();
mCells.rest(hours);
}
void World::teleportToClosestMarker (const MWWorld::Ptr& ptr,

@ -120,8 +120,6 @@ namespace MWWorld
int mActivationDistanceOverride;
std::string mStartupScript;
std::map<MWWorld::Ptr, int> mDoorStates;
///< only holds doors that are currently moving. 1 = opening, 2 = closing
@ -132,7 +130,7 @@ namespace MWWorld
void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust);
Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true);
Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false);
///< @return an updated Ptr in case the Ptr's cell changes
Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos);
@ -211,7 +209,7 @@ namespace MWWorld
const Files::Collections& fileCollections,
const std::vector<std::string>& contentFiles,
ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap,
int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath);
int activationDistanceOverride, const std::string& startCell, const std::string& resourcePath, const std::string& userDataPath);
virtual ~World();
@ -499,7 +497,7 @@ namespace MWWorld
void undeleteObject (const Ptr& ptr) override;
MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z) override;
MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive=false) override;
///< @return an updated Ptr in case the Ptr's cell changes
MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override;
@ -737,7 +735,7 @@ namespace MWWorld
RestPermitted canRest() const override;
///< check if the player is allowed to rest
void rest() override;
void rest(double hours) override;
/// \todo Probably shouldn't be here
MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override;

@ -80,14 +80,6 @@ namespace
EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), NavigatorException);
}
TEST_F(DetourNavigatorNavigatorTest, find_path_for_removed_agent_should_return_empty)
{
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->removeAgent(mAgentHalfExtents);
mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut);
EXPECT_EQ(mPath, std::deque<osg::Vec3f>());
}
TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent)
{
mNavigator->addAgent(mAgentHalfExtents);

@ -125,7 +125,7 @@ namespace Compiler
if (loop.size()!=loop2.size())
throw std::logic_error (
"internal compiler error: failed to generate a while loop");
"Internal compiler error: failed to generate a while loop");
std::copy (loop2.begin(), loop2.end(), std::back_inserter (mCode));
@ -179,6 +179,14 @@ namespace Compiler
scanner.scan (mLineParser);
return true;
}
else if (mState==IfElseJunkState)
{
getErrorHandler().warning ("Extra text after else", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
mState = IfElseBodyState;
return true;
}
return Parser::parseName (name, loc, scanner);
}
@ -207,8 +215,7 @@ namespace Compiler
return true;
}
}
else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState ||
mState==IfElseJunkState)
else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState)
{
if (parseIfBody (keyword, loc, scanner))
return true;
@ -218,6 +225,14 @@ namespace Compiler
if ( parseWhileBody (keyword, loc, scanner))
return true;
}
else if (mState==IfElseJunkState)
{
getErrorHandler().warning ("Extra text after else", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
mState = IfElseBodyState;
return true;
}
return Parser::parseKeyword (keyword, loc, scanner);
}
@ -250,8 +265,9 @@ namespace Compiler
default: ;
}
}
else if (code==Scanner::S_open && mState==IfElseJunkState)
else if (mState==IfElseJunkState)
{
getErrorHandler().warning ("Extra text after else", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
mState = IfElseBodyState;

@ -22,20 +22,20 @@ bool Compiler::DeclarationParser::parseName (const std::string& name, const Toke
char type = mLocals.getType (name2);
if (type!=' ')
{
/// \todo add option to make re-declared local variables an error
getErrorHandler().warning ("ignoring local variable re-declaration",
loc);
mState = State_End;
return true;
}
getErrorHandler().warning ("Local variable re-declaration", loc);
else
mLocals.declare (mType, name2);
mState = State_End;
return true;
}
else if (mState==State_End)
{
getErrorHandler().warning ("Extra text after local variable declaration", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
return false;
}
return Parser::parseName (name, loc, scanner);
}
@ -61,17 +61,31 @@ bool Compiler::DeclarationParser::parseKeyword (int keyword, const TokenLoc& loc
else if (mState==State_Name)
{
// allow keywords to be used as local variable names. MW script compiler, you suck!
/// \todo option to disable this atrocity.
return parseName (loc.mLiteral, loc, scanner);
}
else if (mState==State_End)
{
getErrorHandler().warning ("Extra text after local variable declaration", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
return false;
}
return Parser::parseKeyword (keyword, loc, scanner);
}
bool Compiler::DeclarationParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
{
if (code==Scanner::S_newline && mState==State_End)
if (mState==State_End)
{
if (code!=Scanner::S_newline)
{
getErrorHandler().warning ("Extra text after local variable declaration", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
}
return false;
}
return Parser::parseSpecial (code, loc, scanner);
}

@ -11,7 +11,7 @@ namespace Compiler
{
public:
virtual const char *what() const throw() { return "compile error";}
virtual const char *what() const throw() { return "Compile error";}
///< Return error message
};
@ -21,7 +21,7 @@ namespace Compiler
{
public:
virtual const char *what() const throw() { return "can't read file"; }
virtual const char *what() const throw() { return "Can't read file"; }
///< Return error message
};
@ -31,7 +31,7 @@ namespace Compiler
{
public:
virtual const char *what() const throw() { return "end of file"; }
virtual const char *what() const throw() { return "End of file"; }
///< Return error message
};
}

@ -99,7 +99,7 @@ namespace Compiler
else if (t1=='f' || t2=='f')
mOperands.push_back ('f');
else
throw std::logic_error ("failed to determine result operand type");
throw std::logic_error ("Failed to determine result operand type");
}
void ExprParser::pop()
@ -158,7 +158,7 @@ namespace Compiler
default:
throw std::logic_error ("unknown operator");
throw std::logic_error ("Unknown operator");
}
}
@ -287,7 +287,7 @@ namespace Compiler
else
{
mExplicit.clear();
getErrorHandler().warning ("Ignoring stray explicit reference", loc);
getErrorHandler().warning ("Stray explicit reference", loc);
}
}
@ -430,7 +430,7 @@ namespace Compiler
{
if (!hasExplicit)
{
getErrorHandler().warning ("ignoring stray explicit reference", loc);
getErrorHandler().warning ("Stray explicit reference", loc);
mExplicit.clear();
}
@ -735,13 +735,13 @@ namespace Compiler
{
if (mOperands.empty() && mOperators.empty())
{
getErrorHandler().error ("missing expression", mTokenLoc);
getErrorHandler().error ("Missing expression", mTokenLoc);
return 'l';
}
if (mNextOperand || mOperands.empty())
{
getErrorHandler().error ("syntax error in expression", mTokenLoc);
getErrorHandler().error ("Syntax error in expression", mTokenLoc);
return 'l';
}
@ -799,7 +799,7 @@ namespace Compiler
++optionalCount;
}
else
getErrorHandler().warning ("ignoring extra argument",
getErrorHandler().warning ("Extra argument",
stringParser.getTokenLoc());
}
else if (*iter=='X')
@ -813,7 +813,7 @@ namespace Compiler
if (parser.isEmpty())
break;
else
getErrorHandler().warning("ignoring extra argument", parser.getTokenLoc());
getErrorHandler().warning("Extra argument", parser.getTokenLoc());
}
else if (*iter=='z')
{
@ -825,7 +825,7 @@ namespace Compiler
if (discardParser.isEmpty())
break;
else
getErrorHandler().warning("ignoring extra argument", discardParser.getTokenLoc());
getErrorHandler().warning("Extra argument", discardParser.getTokenLoc());
}
else if (*iter=='j')
{

@ -356,7 +356,7 @@ namespace Compiler
opcodePlaySound3DExplicit);
extensions.registerInstruction ("playsound3dvp", "cff", opcodePlaySound3DVP,
opcodePlaySound3DVPExplicit);
extensions.registerInstruction ("playloopsound3d", "c", opcodePlayLoopSound3D,
extensions.registerInstruction ("playloopsound3d", "cXX", opcodePlayLoopSound3D,
opcodePlayLoopSound3DExplicit);
extensions.registerInstruction ("playloopsound3dvp", "cff", opcodePlayLoopSound3DVP,
opcodePlayLoopSound3DVPExplicit);

@ -98,7 +98,7 @@ namespace Compiler
if (mState == BeginState)
{
if (code != Scanner::S_newline)
reportWarning ("Ignoring stray special character before begin statement", loc);
reportWarning ("Stray special character before begin statement", loc);
return true;
}

@ -29,7 +29,7 @@ bool Compiler::JunkParser::parseName (const std::string& name, const TokenLoc& l
bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner)
{
if (keyword==mIgnoreKeyword)
reportWarning ("ignoring found junk", loc);
reportWarning ("Ignoring found junk", loc);
else
scanner.putbackKeyword (keyword, loc);
@ -39,7 +39,7 @@ bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scann
bool Compiler::JunkParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner)
{
if (code==Scanner::S_member)
reportWarning ("ignoring found junk", loc);
reportWarning ("Ignoring found junk", loc);
else
scanner.putbackSpecial (code, loc);

@ -48,7 +48,7 @@ namespace Compiler
default:
throw std::runtime_error ("unknown expression result type");
throw std::runtime_error ("Unknown expression result type");
}
}
@ -88,7 +88,7 @@ namespace Compiler
{
if (mState==PotentialEndState)
{
getErrorHandler().warning ("ignoring stray string argument", loc);
getErrorHandler().warning ("Stray string argument", loc);
mState = EndState;
return true;
}
@ -132,7 +132,7 @@ namespace Compiler
return true;
}
getErrorHandler().error ("unknown variable", loc);
getErrorHandler().error ("Unknown variable", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
return false;
@ -233,7 +233,7 @@ namespace Compiler
if (mState==SetPotentialMemberVarState && keyword==Scanner::K_to)
{
getErrorHandler().warning ("unknown variable, ignoring set instruction", loc);
getErrorHandler().warning ("Unknown variable", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
return false;
@ -286,7 +286,7 @@ namespace Compiler
{
if (!hasExplicit && mState==ExplicitState)
{
getErrorHandler().warning ("ignoring stray explicit reference", loc);
getErrorHandler().warning ("Stray explicit reference", loc);
mExplicit.clear();
}
@ -344,7 +344,7 @@ namespace Compiler
{
if (!hasExplicit && !mExplicit.empty())
{
getErrorHandler().warning ("ignoring stray explicit reference", loc);
getErrorHandler().warning ("Stray explicit reference", loc);
mExplicit.clear();
}
@ -360,7 +360,7 @@ namespace Compiler
if (mState==ExplicitState)
{
// drop stray explicit reference
getErrorHandler().warning ("ignoring stray explicit reference", loc);
getErrorHandler().warning ("Stray explicit reference", loc);
mState = BeginState;
mExplicit.clear();
}
@ -375,8 +375,7 @@ namespace Compiler
{
if (!getContext().canDeclareLocals())
{
getErrorHandler().error (
"local variables can't be declared in this context", loc);
getErrorHandler().error("Local variables cannot be declared in this context", loc);
SkipParser skip (getErrorHandler(), getContext());
scanner.scan (skip);
return true;
@ -412,19 +411,19 @@ namespace Compiler
case Scanner::K_else:
getErrorHandler().warning ("ignoring stray else", loc);
getErrorHandler().warning ("Stray else", loc);
mState = EndState;
return true;
case Scanner::K_endif:
getErrorHandler().warning ("ignoring stray endif", loc);
getErrorHandler().warning ("Stray endif", loc);
mState = EndState;
return true;
case Scanner::K_begin:
getErrorHandler().warning ("ignoring stray begin", loc);
getErrorHandler().warning ("Stray begin", loc);
mState = EndState;
return true;
}
@ -491,7 +490,7 @@ namespace Compiler
{
if (mState==EndState && code==Scanner::S_open)
{
getErrorHandler().warning ("ignoring stray '[' or '(' at the end of the line",
getErrorHandler().warning ("Stray '[' or '(' at the end of the line",
loc);
return true;
}
@ -508,7 +507,7 @@ namespace Compiler
if (code==Scanner::S_ref && mState==SetPotentialMemberVarState)
{
getErrorHandler().warning ("Ignoring stray explicit reference", loc);
getErrorHandler().warning ("Stray explicit reference", loc);
mState = SetState;
return true;
}

@ -18,7 +18,7 @@ namespace Compiler
case 'f': return mFloats;
}
throw std::logic_error ("unknown variable type");
throw std::logic_error ("Unknown variable type");
}
int Locals::searchIndex (char type, const std::string& name) const
@ -48,7 +48,7 @@ namespace Compiler
case 'f': return mFloats;
}
throw std::logic_error ("unknown variable type");
throw std::logic_error ("Unknown variable type");
}
char Locals::getType (const std::string& name) const

@ -158,7 +158,7 @@ namespace Compiler
TokenLoc loc (mLoc);
mLoc.mLiteral.clear();
mErrorHandler.error ("syntax error", loc);
mErrorHandler.error ("Syntax error", loc);
throw SourceException();
}
@ -521,7 +521,7 @@ namespace Compiler
else if (c == '<' || c == '>') // Treat <> and << as <
{
special = S_cmpLT;
mErrorHandler.warning (std::string("invalid operator <") + c + ", treating it as <", mLoc);
mErrorHandler.warning ("Invalid operator, treating it as <", mLoc);
}
else
{
@ -549,7 +549,7 @@ namespace Compiler
else if (c == '<' || c == '>') // Treat >< and >> as >
{
special = S_cmpGT;
mErrorHandler.warning (std::string("invalid operator >") + c + ", treating it as >", mLoc);
mErrorHandler.warning ("Invalid operator, treating it as >", mLoc);
}
else
{

@ -5,6 +5,8 @@
#include <components/debug/debuglog.hpp>
#include <osg/Stats>
namespace
{
using DetourNavigator::ChangeType;
@ -102,6 +104,20 @@ namespace DetourNavigator
mDone.wait(lock, [&] { return mJobs.empty(); });
}
void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
std::size_t jobs = 0;
{
const std::lock_guard<std::mutex> lock(mMutex);
jobs = mJobs.size();
}
stats.setAttribute(frameNumber, "NavMesh UpdateJobs", jobs);
mNavMeshTilesCache.reportStats(frameNumber, stats);
}
void AsyncNavMeshUpdater::process() throw()
{
log("start process jobs");
@ -129,12 +145,17 @@ namespace DetourNavigator
const auto firstStart = setFirstStart(start);
const auto navMeshCacheItem = job.mNavMeshCacheItem.lock();
if (!navMeshCacheItem)
return true;
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile);
const auto playerTile = *mPlayerTile.lockConst();
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile,
offMeshConnections, mSettings, job.mNavMeshCacheItem, mNavMeshTilesCache);
offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache);
const auto finish = std::chrono::steady_clock::now();
@ -143,7 +164,7 @@ namespace DetourNavigator
using FloatMs = std::chrono::duration<float, std::milli>;
{
const auto locked = job.mNavMeshCacheItem.lockConst();
const auto locked = navMeshCacheItem->lockConst();
log("cache updated for agent=", job.mAgentHalfExtents, " status=", status,
" generation=", locked->getGeneration(),
" revision=", locked->getNavMeshRevision(),
@ -157,9 +178,7 @@ namespace DetourNavigator
boost::optional<AsyncNavMeshUpdater::Job> AsyncNavMeshUpdater::getNextJob()
{
std::unique_lock<std::mutex> lock(mMutex);
if (mJobs.empty())
mHasJob.wait_for(lock, std::chrono::milliseconds(10));
if (mJobs.empty())
if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), [&] { return !mJobs.empty(); }))
{
mFirstStart.lock()->reset();
mDone.notify_all();
@ -194,7 +213,8 @@ namespace DetourNavigator
writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x())
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision);
if (mSettings.get().mEnableWriteNavMeshToFile)
writeToFile(job.mNavMeshCacheItem.lockConst()->getValue(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
if (const auto shared = job.mNavMeshCacheItem.lock())
writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
}
std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value)

@ -44,11 +44,13 @@ namespace DetourNavigator
void wait();
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
private:
struct Job
{
osg::Vec3f mAgentHalfExtents;
SharedNavMeshCacheItem mNavMeshCacheItem;
std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
TilePosition mChangedTile;
unsigned mTryNumber;
ChangeType mChangeType;
@ -72,7 +74,7 @@ namespace DetourNavigator
std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager;
std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager;
std::atomic_bool mShouldStop;
std::mutex mMutex;
mutable std::mutex mMutex;
std::condition_variable mHasJob;
std::condition_variable mDone;
Jobs mJobs;

@ -25,8 +25,6 @@ namespace
{
using namespace DetourNavigator;
static const int doNotTransferOwnership = 0;
void initPolyMeshDetail(rcPolyMeshDetail& value)
{
value.meshes = nullptr;
@ -441,56 +439,7 @@ namespace
return NavMeshData(navMeshData, navMeshDataSize);
}
class UpdateNavMeshStatusBuilder
{
public:
UpdateNavMeshStatusBuilder() = default;
UpdateNavMeshStatusBuilder removed(bool value)
{
if (value)
set(UpdateNavMeshStatus::removed);
else
unset(UpdateNavMeshStatus::removed);
return *this;
}
UpdateNavMeshStatusBuilder added(bool value)
{
if (value)
set(UpdateNavMeshStatus::added);
else
unset(UpdateNavMeshStatus::added);
return *this;
}
UpdateNavMeshStatusBuilder failed(bool value)
{
if (value)
set(UpdateNavMeshStatus::failed);
else
unset(UpdateNavMeshStatus::failed);
return *this;
}
UpdateNavMeshStatus getResult() const
{
return mResult;
}
private:
UpdateNavMeshStatus mResult = UpdateNavMeshStatus::ignored;
void set(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) | static_cast<unsigned>(value));
}
void unset(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) & ~static_cast<unsigned>(value));
}
};
template <class T>
unsigned long getMinValuableBitsNumber(const T value)
@ -500,49 +449,6 @@ namespace
++power;
return power;
}
dtStatus addTile(dtNavMesh& navMesh, const NavMeshData& navMeshData)
{
const dtTileRef lastRef = 0;
dtTileRef* const result = nullptr;
return navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize,
doNotTransferOwnership, lastRef, result);
}
dtStatus addTile(dtNavMesh& navMesh, const NavMeshTilesCache::Value& cachedNavMeshData)
{
const dtTileRef lastRef = 0;
dtTileRef* const result = nullptr;
return navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize,
doNotTransferOwnership, lastRef, result);
}
template <class T>
UpdateNavMeshStatus replaceTile(const SharedNavMeshCacheItem& navMeshCacheItem,
const TilePosition& changedTile, T&& navMeshData)
{
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const int layer = 0;
const auto tileRef = navMesh.getTileRefAt(changedTile.x(), changedTile.y(), layer);
unsigned char** const data = nullptr;
int* const dataSize = nullptr;
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, data, dataSize));
const auto addStatus = addTile(navMesh, navMeshData);
if (dtStatusSucceed(addStatus))
{
locked->setUsedTile(changedTile, std::forward<T>(navMeshData));
return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult();
}
else
{
if (removed)
locked->removeUsedTile(changedTile);
log("failed to add tile with status=", WriteDtStatus {addStatus});
return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult();
}
}
}
namespace DetourNavigator
@ -591,26 +497,13 @@ namespace DetourNavigator
" playerTile=", playerTile,
" changedTileDistance=", getDistance(changedTile, playerTile));
const auto params = *navMeshCacheItem.lockConst()->getValue().getParams();
const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams();
const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]);
const auto x = changedTile.x();
const auto y = changedTile.y();
const auto removeTile = [&] {
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
if (removed)
locked->removeUsedTile(changedTile);
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
};
if (!recastMesh)
{
log("ignore add tile: recastMesh is null");
return removeTile();
return navMeshCacheItem->lock()->removeTile(changedTile);
}
auto recastMeshBounds = recastMesh->getBounds();
@ -625,13 +518,13 @@ namespace DetourNavigator
if (isEmpty(recastMeshBounds))
{
log("ignore add tile: recastMesh is empty");
return removeTile();
return navMeshCacheItem->lock()->removeTile(changedTile);
}
if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles)))
{
log("ignore add tile: too far from player");
return removeTile();
return navMeshCacheItem->lock()->removeTile(changedTile);
}
auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections);
@ -648,7 +541,7 @@ namespace DetourNavigator
if (!navMeshData.mValue)
{
log("ignore add tile: NavMeshData is null");
return removeTile();
return navMeshCacheItem->lock()->removeTile(changedTile);
}
try
@ -665,10 +558,10 @@ namespace DetourNavigator
if (!cachedNavMeshData)
{
log("cache overflow");
return replaceTile(navMeshCacheItem, changedTile, std::move(navMeshData));
return navMeshCacheItem->lock()->updateTile(changedTile, std::move(navMeshData));
}
}
return replaceTile(navMeshCacheItem, changedTile, std::move(cachedNavMeshData));
return navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData));
}
}

@ -20,21 +20,6 @@ namespace DetourNavigator
class RecastMesh;
struct Settings;
enum class UpdateNavMeshStatus : unsigned
{
ignored = 0,
removed = 1 << 0,
added = 1 << 1,
replaced = removed | added,
failed = 1 << 2,
lost = removed | failed,
};
inline bool isSuccess(UpdateNavMeshStatus value)
{
return (static_cast<unsigned>(value) & static_cast<unsigned>(UpdateNavMeshStatus::failed)) == 0;
}
inline float getLength(const osg::Vec2i& value)
{
return std::sqrt(float(osg::square(value.x()) + osg::square(value.y())));

@ -174,7 +174,7 @@ namespace DetourNavigator
if (!navMesh)
return out;
const auto settings = getSettings();
return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(settings, agentHalfExtents),
return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents),
toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start),
toNavMeshCoordinates(settings, end), includeFlags, settings, out);
}
@ -191,7 +191,9 @@ namespace DetourNavigator
*/
virtual std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const = 0;
virtual Settings getSettings() const = 0;
virtual const Settings& getSettings() const = 0;
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
};
}

@ -21,10 +21,10 @@ namespace DetourNavigator
void NavigatorImpl::removeAgent(const osg::Vec3f& agentHalfExtents)
{
const auto it = mAgents.find(agentHalfExtents);
if (it == mAgents.end() || --it->second)
if (it == mAgents.end())
return;
mAgents.erase(it);
mNavMeshManager.reset(agentHalfExtents);
if (it->second > 0)
--it->second;
}
bool NavigatorImpl::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform)
@ -113,6 +113,7 @@ namespace DetourNavigator
void NavigatorImpl::update(const osg::Vec3f& playerPosition)
{
removeUnusedNavMeshes();
for (const auto& v : mAgents)
mNavMeshManager.update(playerPosition, v.first);
}
@ -132,11 +133,16 @@ namespace DetourNavigator
return mNavMeshManager.getNavMeshes();
}
Settings NavigatorImpl::getSettings() const
const Settings& NavigatorImpl::getSettings() const
{
return mSettings;
}
void NavigatorImpl::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
mNavMeshManager.reportStats(frameNumber, stats);
}
void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId)
{
updateId(id, avoidId, mWaterIds);
@ -156,4 +162,15 @@ namespace DetourNavigator
inserted.first->second = updateId;
}
}
void NavigatorImpl::removeUnusedNavMeshes()
{
for (auto it = mAgents.begin(); it != mAgents.end();)
{
if (it->second == 0 && mNavMeshManager.reset(it->first))
it = mAgents.erase(it);
else
++it;
}
}
}

@ -46,7 +46,9 @@ namespace DetourNavigator
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const override;
Settings getSettings() const override;
const Settings& getSettings() const override;
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
private:
Settings mSettings;
@ -58,6 +60,7 @@ namespace DetourNavigator
void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId);
void updateWaterShapeId(const ObjectId id, const ObjectId waterId);
void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map<ObjectId, ObjectId>& ids);
void removeUnusedNavMeshes();
};
}

@ -5,8 +5,9 @@
namespace DetourNavigator
{
struct NavigatorStub final : public Navigator
class NavigatorStub final : public Navigator
{
public:
NavigatorStub() = default;
void addAgent(const osg::Vec3f& /*agentHalfExtents*/) override {}
@ -65,7 +66,7 @@ namespace DetourNavigator
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override
{
return SharedNavMeshCacheItem();
return mEmptyNavMeshCacheItem;
}
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const override
@ -73,10 +74,16 @@ namespace DetourNavigator
return std::map<osg::Vec3f, SharedNavMeshCacheItem>();
}
Settings getSettings() const override
const Settings& getSettings() const override
{
return Settings {};
return mDefaultSettings;
}
void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {}
private:
Settings mDefaultSettings {};
SharedNavMeshCacheItem mEmptyNavMeshCacheItem;
};
}

@ -4,29 +4,113 @@
#include "sharednavmesh.hpp"
#include "tileposition.hpp"
#include "navmeshtilescache.hpp"
#include "dtstatus.hpp"
#include <components/misc/guarded.hpp>
#include <DetourNavMesh.h>
#include <map>
namespace DetourNavigator
{
class NavMeshCacheItem
enum class UpdateNavMeshStatus : unsigned
{
ignored = 0,
removed = 1 << 0,
added = 1 << 1,
replaced = removed | added,
failed = 1 << 2,
lost = removed | failed,
};
inline bool isSuccess(UpdateNavMeshStatus value)
{
return (static_cast<unsigned>(value) & static_cast<unsigned>(UpdateNavMeshStatus::failed)) == 0;
}
class UpdateNavMeshStatusBuilder
{
public:
NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation)
: mValue(value), mGeneration(generation), mNavMeshRevision(0)
UpdateNavMeshStatusBuilder() = default;
UpdateNavMeshStatusBuilder removed(bool value)
{
if (value)
set(UpdateNavMeshStatus::removed);
else
unset(UpdateNavMeshStatus::removed);
return *this;
}
UpdateNavMeshStatusBuilder added(bool value)
{
if (value)
set(UpdateNavMeshStatus::added);
else
unset(UpdateNavMeshStatus::added);
return *this;
}
UpdateNavMeshStatusBuilder failed(bool value)
{
if (value)
set(UpdateNavMeshStatus::failed);
else
unset(UpdateNavMeshStatus::failed);
return *this;
}
UpdateNavMeshStatus getResult() const
{
return mResult;
}
private:
UpdateNavMeshStatus mResult = UpdateNavMeshStatus::ignored;
void set(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) | static_cast<unsigned>(value));
}
void unset(UpdateNavMeshStatus value)
{
mResult = static_cast<UpdateNavMeshStatus>(static_cast<unsigned>(mResult) & ~static_cast<unsigned>(value));
}
};
inline unsigned char* getRawData(NavMeshData& navMeshData)
{
return navMeshData.mValue.get();
}
inline unsigned char* getRawData(NavMeshTilesCache::Value& cachedNavMeshData)
{
return cachedNavMeshData.get().mValue;
}
const dtNavMesh& getValue() const
inline int getSize(const NavMeshData& navMeshData)
{
return *mValue;
return navMeshData.mSize;
}
dtNavMesh& getValue()
inline int getSize(const NavMeshTilesCache::Value& cachedNavMeshData)
{
return *mValue;
return cachedNavMeshData.get().mSize;
}
class NavMeshCacheItem
{
public:
NavMeshCacheItem(const NavMeshPtr& impl, std::size_t generation)
: mImpl(impl), mGeneration(generation), mNavMeshRevision(0)
{
}
const dtNavMesh& getImpl() const
{
return *mImpl;
}
std::size_t getGeneration() const
@ -39,6 +123,38 @@ namespace DetourNavigator
return mNavMeshRevision;
}
template <class T>
UpdateNavMeshStatus updateTile(const TilePosition& position, T&& navMeshData)
{
const auto removed = removeTileImpl(position);
const auto addStatus = addTileImpl(getRawData(navMeshData), getSize(navMeshData));
if (dtStatusSucceed(addStatus))
{
setUsedTile(position, std::forward<T>(navMeshData));
return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult();
}
else
{
if (removed)
removeUsedTile(position);
return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult();
}
}
UpdateNavMeshStatus removeTile(const TilePosition& position)
{
const auto removed = dtStatusSucceed(removeTileImpl(position));
if (removed)
removeUsedTile(position);
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
}
private:
NavMeshPtr mImpl;
std::size_t mGeneration;
std::size_t mNavMeshRevision;
std::map<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles;
void setUsedTile(const TilePosition& tilePosition, NavMeshTilesCache::Value value)
{
mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData());
@ -57,14 +173,26 @@ namespace DetourNavigator
++mNavMeshRevision;
}
private:
NavMeshPtr mValue;
std::size_t mGeneration;
std::size_t mNavMeshRevision;
std::map<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles;
dtStatus addTileImpl(unsigned char* data, int size)
{
const int doNotTransferOwnership = 0;
const dtTileRef lastRef = 0;
dtTileRef* const result = nullptr;
return mImpl->addTile(data, size, doNotTransferOwnership, lastRef, result);
}
dtStatus removeTileImpl(const TilePosition& position)
{
const int layer = 0;
const auto tileRef = mImpl->getTileRefAt(position.x(), position.y(), layer);
unsigned char** const data = nullptr;
int* const dataSize = nullptr;
return mImpl->removeTile(tileRef, data, dataSize);
}
};
using SharedNavMeshCacheItem = Misc::SharedGuarded<NavMeshCacheItem>;
using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>;
using SharedNavMeshCacheItem = std::shared_ptr<GuardedNavMeshCacheItem>;
}
#endif

@ -16,6 +16,21 @@ namespace
{
return current == add ? current : ChangeType::mixed;
}
/// Safely reset shared_ptr with definite underlying object destrutor call.
/// Assuming there is another thread holding copy of this shared_ptr or weak_ptr to this shared_ptr.
template <class T>
bool resetIfUnique(std::shared_ptr<T>& ptr)
{
const std::weak_ptr<T> weak(ptr);
ptr.reset();
if (auto shared = weak.lock())
{
ptr = std::move(shared);
return false;
}
return true;
}
}
namespace DetourNavigator
@ -79,13 +94,22 @@ namespace DetourNavigator
if (cached != mCache.end())
return;
mCache.insert(std::make_pair(agentHalfExtents,
std::make_shared<NavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter)));
std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter)));
log("cache add for agent=", agentHalfExtents);
}
void NavMeshManager::reset(const osg::Vec3f& agentHalfExtents)
bool NavMeshManager::reset(const osg::Vec3f& agentHalfExtents)
{
const auto it = mCache.find(agentHalfExtents);
if (it == mCache.end())
return true;
if (!resetIfUnique(it->second))
return false;
mCache.erase(agentHalfExtents);
mChangedTiles.erase(agentHalfExtents);
mPlayerTile.erase(agentHalfExtents);
mLastRecastMeshManagerRevision.erase(agentHalfExtents);
return true;
}
void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end)
@ -139,8 +163,8 @@ namespace DetourNavigator
}
const auto changedTiles = mChangedTiles.find(agentHalfExtents);
{
const auto locked = cached.lock();
const auto& navMesh = locked->getValue();
const auto locked = cached->lockConst();
const auto& navMesh = locked->getImpl();
if (changedTiles != mChangedTiles.end())
{
for (const auto& tile : changedTiles->second)
@ -152,10 +176,6 @@ namespace DetourNavigator
else
tileToPost->second = addChangeType(tileToPost->second, tile.second);
}
for (const auto& tile : tilesToPost)
changedTiles->second.erase(tile.first);
if (changedTiles->second.empty())
mChangedTiles.erase(changedTiles);
}
const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles);
mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile)
@ -171,6 +191,8 @@ namespace DetourNavigator
});
}
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost);
if (changedTiles != mChangedTiles.end())
changedTiles->second.clear();
log("cache update posted for agent=", agentHalfExtents,
" playerTile=", lastPlayerTile->second,
" recastMeshManagerRevision=", lastRevision);
@ -191,6 +213,11 @@ namespace DetourNavigator
return mCache;
}
void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
mAsyncNavMeshUpdater.reportStats(frameNumber, stats);
}
void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform,
const ChangeType changeType)
{

@ -36,7 +36,7 @@ namespace DetourNavigator
bool removeWater(const osg::Vec2i& cellPosition);
void reset(const osg::Vec3f& agentHalfExtents);
bool reset(const osg::Vec3f& agentHalfExtents);
void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end);
@ -50,6 +50,8 @@ namespace DetourNavigator
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const;
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
private:
const Settings& mSettings;
TileCachedRecastMeshManager mRecastMeshManager;

@ -1,6 +1,10 @@
#include "navmeshtilescache.hpp"
#include "exceptions.hpp"
#include <osg/Stats>
#include <cstring>
namespace DetourNavigator
{
namespace
@ -61,8 +65,7 @@ namespace DetourNavigator
if (tileValues == agentValues->second.end())
return Value();
// TODO: use different function to make key to avoid unnecessary std::string allocation
const auto tile = tileValues->second.mMap.find(makeNavMeshKey(recastMesh, offMeshConnections));
const auto tile = tileValues->second.mMap.find(RecastMeshKeyView(recastMesh, offMeshConnections));
if (tile == tileValues->second.mMap.end())
return Value();
@ -85,7 +88,7 @@ namespace DetourNavigator
if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize))
return Value();
const auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections);
auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections);
const auto itemSize = navMeshSize + 2 * navMeshKey.size();
if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize))
@ -94,9 +97,8 @@ namespace DetourNavigator
while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize)
removeLeastRecentlyUsed();
const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, navMeshKey);
// TODO: use std::string_view or some alternative to avoid navMeshKey copy into both mFreeItems and mValues
const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(navMeshKey, iterator);
const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey));
const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator);
if (!emplaced.second)
{
@ -113,6 +115,24 @@ namespace DetourNavigator
return Value(*this, iterator);
}
void NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
std::size_t navMeshCacheSize = 0;
std::size_t usedNavMeshTiles = 0;
std::size_t cachedNavMeshTiles = 0;
{
const std::lock_guard<std::mutex> lock(mMutex);
navMeshCacheSize = mUsedNavMeshDataSize;
usedNavMeshTiles = mBusyItems.size();
cachedNavMeshTiles = mFreeItems.size();
}
stats.setAttribute(frameNumber, "NavMesh CacheSize", navMeshCacheSize);
stats.setAttribute(frameNumber, "NavMesh UsedTiles", usedNavMeshTiles);
stats.setAttribute(frameNumber, "NavMesh CachedTiles", cachedNavMeshTiles);
}
void NavMeshTilesCache::removeLeastRecentlyUsed()
{
const auto& item = mFreeItems.back();
@ -131,9 +151,10 @@ namespace DetourNavigator
mUsedNavMeshDataSize -= getSize(item);
mFreeNavMeshDataSize -= getSize(item);
mFreeItems.pop_back();
tileValues->second.mMap.erase(value);
mFreeItems.pop_back();
if (!tileValues->second.mMap.empty())
return;
@ -163,4 +184,69 @@ namespace DetourNavigator
mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator);
mFreeNavMeshDataSize += getSize(*iterator);
}
namespace
{
struct CompareBytes
{
const char* mRhsIt;
const char* mRhsEnd;
template <class T>
int operator ()(const std::vector<T>& lhs)
{
const auto lhsBegin = reinterpret_cast<const char*>(lhs.data());
const auto lhsEnd = reinterpret_cast<const char*>(lhs.data() + lhs.size());
const auto lhsSize = static_cast<std::ptrdiff_t>(lhsEnd - lhsBegin);
const auto rhsSize = static_cast<std::ptrdiff_t>(mRhsEnd - mRhsIt);
if (lhsBegin == nullptr || mRhsIt == nullptr)
{
if (lhsSize < rhsSize)
return -1;
else if (lhsSize > rhsSize)
return 1;
else
return 0;
}
const auto size = std::min(lhsSize, rhsSize);
if (const auto result = std::memcmp(lhsBegin, mRhsIt, size))
return result;
if (lhsSize > rhsSize)
return 1;
mRhsIt += size;
return 0;
}
};
}
int NavMeshTilesCache::RecastMeshKeyView::compare(const std::string& other) const
{
CompareBytes compareBytes {other.data(), other.data() + other.size()};
if (const auto result = compareBytes(mRecastMesh.get().getIndices()))
return result;
if (const auto result = compareBytes(mRecastMesh.get().getVertices()))
return result;
if (const auto result = compareBytes(mRecastMesh.get().getAreaTypes()))
return result;
if (const auto result = compareBytes(mRecastMesh.get().getWater()))
return result;
if (const auto result = compareBytes(mOffMeshConnections.get()))
return result;
if (compareBytes.mRhsIt < compareBytes.mRhsEnd)
return -1;
return 0;
}
}

@ -10,6 +10,12 @@
#include <map>
#include <list>
#include <mutex>
#include <cassert>
namespace osg
{
class Stats;
}
namespace DetourNavigator
{
@ -104,14 +110,68 @@ namespace DetourNavigator
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections,
NavMeshData&& value);
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
private:
class KeyView
{
public:
KeyView() = default;
KeyView(const std::string& value)
: mValue(&value) {}
const std::string& getValue() const
{
assert(mValue);
return *mValue;
}
virtual int compare(const std::string& other) const
{
assert(mValue);
return mValue->compare(other);
}
virtual bool isLess(const KeyView& other) const
{
assert(mValue);
return other.compare(*mValue) > 0;
}
friend bool operator <(const KeyView& lhs, const KeyView& rhs)
{
return lhs.isLess(rhs);
}
private:
const std::string* mValue = nullptr;
};
class RecastMeshKeyView : public KeyView
{
public:
RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections)
: mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {}
int compare(const std::string& other) const override;
bool isLess(const KeyView& other) const override
{
return compare(other.getValue()) < 0;
}
private:
std::reference_wrapper<const RecastMesh> mRecastMesh;
std::reference_wrapper<const std::vector<OffMeshConnection>> mOffMeshConnections;
};
struct TileMap
{
std::map<std::string, ItemIterator> mMap;
std::map<KeyView, ItemIterator> mMap;
};
std::mutex mMutex;
mutable std::mutex mMutex;
std::size_t mMaxNavMeshDataSize;
std::size_t mUsedNavMeshDataSize;
std::size_t mFreeNavMeshDataSize;

@ -61,8 +61,8 @@ namespace DetourNavigator
for (const auto& tile : currentTiles)
if (!newTiles.count(tile) && removeTile(id, tile, tiles.get()))
changedTiles.push_back(tile);
std::swap(currentTiles, newTiles);
}
std::swap(currentTiles, newTiles);
if (!changedTiles.empty())
++mRevision;
return changedTiles;

@ -127,7 +127,7 @@ std::string ESMReader::getHString()
// them. For some reason, they break the rules, and contain a byte
// (value 0) even if the header says there is no data. If
// Morrowind accepts it, so should we.
if (mCtx.leftSub == 0)
if (mCtx.leftSub == 0 && mCtx.leftRec != 0)
{
// Skip the following zero byte
mCtx.leftRec--;

@ -11,18 +11,9 @@ class ESMWriter;
/*
* Texture used for texturing landscape.
*
* They are probably indexed by 'num', not 'id', but I don't know for
* sure. And num is not unique between files, so one option is to keep
* a separate list for each input file (that has LTEX records, of
* course.) We also need to resolve references to already existing
* land textures to save space.
* I'm not sure if it is even possible to override existing land
* textures, probably not. I'll have to try it, and have to mimic the
* behaviour of morrowind. First, check what you are allowed to do in
* the editor. Then make an esp which changes a commonly used land
* texture, and see if it affects the game.
* They are indexed by 'num', but still use 'id' to override base records.
* Original editor even does not allow to create new records with existing ID's.
* TODO: currently OpenMW-CS does not allow to override LTEX records at all.
*/
struct LandTexture

@ -104,7 +104,7 @@ namespace ESM
}
mScriptData.resize(subSize);
esm.getExact(&mScriptData[0], mScriptData.size());
esm.getExact(mScriptData.data(), mScriptData.size());
break;
}
case ESM::FourCC<'S','C','T','X'>::value:
@ -156,7 +156,7 @@ namespace ESM
}
esm.startSubRecord("SCDT");
esm.write(reinterpret_cast<const char * >(&mScriptData[0]), mData.mScriptDataSize);
esm.write(reinterpret_cast<const char *>(mScriptData.data()), mData.mScriptDataSize);
esm.endRecord("SCDT");
esm.writeHNOString("SCTX", mScriptText);

@ -46,19 +46,6 @@ namespace ESMTerrain
{
}
const ESM::Land::LandData *LandObject::getData(int flags) const
{
if ((mData.mDataLoaded & flags) != flags)
return nullptr;
return &mData;
}
int LandObject::getPlugin() const
{
return mLand->mPlugin;
}
const float defaultHeight = ESM::Land::DEFAULT_HEIGHT;
Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps)
@ -158,7 +145,7 @@ namespace ESMTerrain
normal.normalize();
}
void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row, LandCache& cache)
void Storage::fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache)
{
if (col == ESM::Land::LAND_SIZE-1)
{
@ -175,22 +162,22 @@ namespace ESMTerrain
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0;
if (data)
{
color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3];
color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1];
color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2];
}
else
{
color.r() = 1;
color.g() = 1;
color.b() = 1;
color.r() = 255;
color.g() = 255;
color.b() = 255;
}
}
void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
osg::ref_ptr<osg::Vec3Array> positions,
osg::ref_ptr<osg::Vec3Array> normals,
osg::ref_ptr<osg::Vec4Array> colours)
osg::ref_ptr<osg::Vec4ubArray> colours)
{
// LOD level n means every 2^n-th vertex is kept
size_t increment = static_cast<size_t>(1) << lodLevel;
@ -207,7 +194,7 @@ namespace ESMTerrain
colours->resize(numVerts*numVerts);
osg::Vec3f normal;
osg::Vec4f color;
osg::Vec4ub color;
float vertY = 0;
float vertX = 0;
@ -295,20 +282,20 @@ namespace ESMTerrain
if (colourData)
{
for (int i=0; i<3; ++i)
color[i] = colourData->mColours[srcArrayIndex+i] / 255.f;
color[i] = colourData->mColours[srcArrayIndex+i];
}
else
{
color.r() = 1;
color.g() = 1;
color.b() = 1;
color.r() = 255;
color.g() = 255;
color.b() = 255;
}
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row, cache);
color.a() = 1;
color.a() = 255;
(*colours)[static_cast<unsigned int>(vertX*numVerts + vertY)] = color;
@ -388,8 +375,7 @@ namespace ESMTerrain
return texture;
}
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter,
bool pack, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f);
int cellX = static_cast<int>(std::floor(origin.x()));
@ -429,11 +415,8 @@ namespace ESMTerrain
layerList.push_back(getLayerInfo(getTextureName(*it)));
}
int numTextures = textureIndices.size();
// numTextures-1 since the base layer doesn't need blending
int numBlendmaps = pack ? static_cast<int>(std::ceil((numTextures - 1) / 4.f)) : (numTextures - 1);
int channels = pack ? 4 : 1;
// size-1 since the base layer doesn't need blending
int numBlendmaps = textureIndices.size() - 1;
// Second iteration - create and fill in the blend maps
const int blendmapSize = (realTextureSize-1) * chunkSize + 1;
@ -443,10 +426,8 @@ namespace ESMTerrain
for (int i=0; i<numBlendmaps; ++i)
{
GLenum format = pack ? GL_RGBA : GL_ALPHA;
osg::ref_ptr<osg::Image> image (new osg::Image);
image->allocateImage(blendmapImageSize, blendmapImageSize, 1, format, GL_UNSIGNED_BYTE);
image->allocateImage(blendmapImageSize, blendmapImageSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE);
unsigned char* pData = image->data();
for (int y=0; y<blendmapSize; ++y)
@ -456,18 +437,16 @@ namespace ESMTerrain
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x+rowStart, y+colStart, cache);
assert(textureIndicesMap.find(id) != textureIndicesMap.end());
int layerIndex = textureIndicesMap.find(id)->second;
int blendIndex = (pack ? static_cast<int>(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1);
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
int alpha = (blendIndex == i) ? 255 : 0;
int alpha = (layerIndex == i+1) ? 255 : 0;
int realY = (blendmapSize - y - 1)*imageScaleFactor;
int realX = x*imageScaleFactor;
pData[((realY+0)*blendmapImageSize + realX + 0)*channels + channel] = alpha;
pData[((realY+1)*blendmapImageSize + realX + 0)*channels + channel] = alpha;
pData[((realY+0)*blendmapImageSize + realX + 1)*channels + channel] = alpha;
pData[((realY+1)*blendmapImageSize + realX + 1)*channels + channel] = alpha;
pData[(realY+0)*blendmapImageSize + realX + 0] = alpha;
pData[(realY+1)*blendmapImageSize + realX + 0] = alpha;
pData[(realY+0)*blendmapImageSize + realX + 1] = alpha;
pData[(realY+1)*blendmapImageSize + realX + 1] = alpha;
}
}
blendmaps.push_back(image);

@ -29,8 +29,17 @@ namespace ESMTerrain
META_Object(ESMTerrain, LandObject)
const ESM::Land::LandData* getData(int flags) const;
int getPlugin() const;
inline const ESM::Land::LandData* getData(int flags) const
{
if ((mData.mDataLoaded & flags) != flags)
return nullptr;
return &mData;
}
inline int getPlugin() const
{
return mLand->mPlugin;
}
private:
const ESM::Land* mLand;
@ -75,7 +84,7 @@ namespace ESMTerrain
virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
osg::ref_ptr<osg::Vec3Array> positions,
osg::ref_ptr<osg::Vec3Array> normals,
osg::ref_ptr<osg::Vec4Array> colours);
osg::ref_ptr<osg::Vec4ubArray> colours);
/// Create textures holding layer blend values for a terrain chunk.
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
@ -83,13 +92,9 @@ namespace ESMTerrain
/// @note May be called from background threads.
/// @param chunkSize size of the terrain chunk in cell units
/// @param chunkCenter center of the chunk in cell units
/// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
/// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here
virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack,
ImageVector& blendmaps,
virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
virtual float getHeightAt (const osg::Vec3f& worldPos);
@ -105,21 +110,20 @@ namespace ESMTerrain
private:
const VFS::Manager* mVFS;
void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
void fixColour (osg::Vec4f& colour, int cellX, int cellY, int col, int row, LandCache& cache);
void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
inline void fixColour (osg::Vec4ub& colour, int cellX, int cellY, int col, int row, LandCache& cache);
inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
float getVertexHeight (const ESM::Land::LandData* data, int x, int y);
inline float getVertexHeight (const ESM::Land::LandData* data, int x, int y);
const LandObject* getLand(int cellX, int cellY, LandCache& cache);
inline const LandObject* getLand(int cellX, int cellY, LandCache& cache);
// Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name.
// pair <texture id, plugin id>
typedef std::pair<short, short> UniqueTextureId;
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
int x, int y, LandCache&);
inline UniqueTextureId getVtexIndexAt(int cellX, int cellY, int x, int y, LandCache&);
std::string getTextureName (UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;

@ -83,38 +83,6 @@ namespace Misc
std::mutex mMutex;
T mValue;
};
template <class T>
class SharedGuarded
{
public:
SharedGuarded()
: mMutex(std::make_shared<std::mutex>()), mValue()
{}
SharedGuarded(std::shared_ptr<T> value)
: mMutex(std::make_shared<std::mutex>()), mValue(std::move(value))
{}
Locked<T> lock() const
{
return Locked<T>(*mMutex, *mValue);
}
Locked<const T> lockConst() const
{
return Locked<const T>(*mMutex, *mValue);
}
operator bool() const
{
return static_cast<bool>(mValue);
}
private:
std::shared_ptr<std::mutex> mMutex;
std::shared_ptr<T> mValue;
};
}
#endif

@ -120,7 +120,7 @@ void NiRotatingParticlesData::read(NIFStream *nif)
void NiPosData::read(NIFStream *nif)
{
mKeyList.reset(new Vector3KeyMap);
mKeyList = std::make_shared<Vector3KeyMap>();
mKeyList->read(nif);
}
@ -128,14 +128,14 @@ void NiUVData::read(NIFStream *nif)
{
for(int i = 0;i < 4;i++)
{
mKeyList[i].reset(new FloatKeyMap);
mKeyList[i] = std::make_shared<FloatKeyMap>();
mKeyList[i]->read(nif);
}
}
void NiFloatData::read(NIFStream *nif)
{
mKeyList.reset(new FloatKeyMap);
mKeyList = std::make_shared<FloatKeyMap>();
mKeyList->read(nif);
}
@ -177,7 +177,7 @@ void NiPixelData::read(NIFStream *nif)
void NiColorData::read(NIFStream *nif)
{
mKeyMap.reset(new Vector4KeyMap);
mKeyMap = std::make_shared<Vector4KeyMap>();
mKeyMap->read(nif);
}
@ -231,7 +231,7 @@ void NiMorphData::read(NIFStream *nif)
mMorphs.resize(morphCount);
for(int i = 0;i < morphCount;i++)
{
mMorphs[i].mKeyFrames.reset(new FloatKeyMap);
mMorphs[i].mKeyFrames = std::make_shared<FloatKeyMap>();
mMorphs[i].mKeyFrames->read(nif, true);
nif->getVector3s(mMorphs[i].mVertices, vertCount);
}
@ -239,22 +239,22 @@ void NiMorphData::read(NIFStream *nif)
void NiKeyframeData::read(NIFStream *nif)
{
mRotations.reset(new QuaternionKeyMap);
mRotations = std::make_shared<QuaternionKeyMap>();
mRotations->read(nif);
if(mRotations->mInterpolationType == Vector3KeyMap::sXYZInterpolation)
{
//Chomp unused float
nif->getFloat();
mXRotations.reset(new FloatKeyMap);
mYRotations.reset(new FloatKeyMap);
mZRotations.reset(new FloatKeyMap);
mXRotations = std::make_shared<FloatKeyMap>();
mYRotations = std::make_shared<FloatKeyMap>();
mZRotations = std::make_shared<FloatKeyMap>();
mXRotations->read(nif, true);
mYRotations->read(nif, true);
mZRotations->read(nif, true);
}
mTranslations.reset(new Vector3KeyMap);
mTranslations = std::make_shared<Vector3KeyMap>();
mTranslations->read(nif);
mScales.reset(new FloatKeyMap);
mScales = std::make_shared<FloatKeyMap>();
mScales->read(nif);
}

@ -311,10 +311,11 @@ void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv)
RollController::RollController(const Nif::NiFloatData *data)
: mData(data->mKeyList, 1.f)
, mStartingTime(0)
{
}
RollController::RollController()
RollController::RollController() : mStartingTime(0)
{
}

@ -1,165 +0,0 @@
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
#include "objectcache.hpp"
#include <osg/Object>
#include <osg/Node>
namespace Resource
{
////////////////////////////////////////////////////////////////////////////////////////////
//
// ObjectCache
//
ObjectCache::ObjectCache():
osg::Referenced(true)
{
}
ObjectCache::~ObjectCache()
{
}
void ObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp)
{
if (!object)
{
OSG_ALWAYS << " trying to add NULL object to cache for " << filename << std::endl;
return;
}
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
_objectCache[filename]=ObjectTimeStampPair(object,timestamp);
}
osg::ref_ptr<osg::Object> ObjectCache::getRefFromObjectCache(const std::string& fileName)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
ObjectCacheMap::iterator itr = _objectCache.find(fileName);
if (itr!=_objectCache.end())
{
return itr->second.first;
}
else return 0;
}
bool ObjectCache::checkInObjectCache(const std::string &fileName, double timeStamp)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
ObjectCacheMap::iterator itr = _objectCache.find(fileName);
if (itr!=_objectCache.end())
{
itr->second.second = timeStamp;
return true;
}
else return false;
}
void ObjectCache::updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
// look for objects with external references and update their time stamp.
for(ObjectCacheMap::iterator itr=_objectCache.begin();
itr!=_objectCache.end();
++itr)
{
// If ref count is greater than 1, the object has an external reference.
// If the timestamp is yet to be initialized, it needs to be updated too.
if (itr->second.first->referenceCount()>1 || itr->second.second == 0.0)
{
// So update it.
itr->second.second = referenceTime;
}
}
}
void ObjectCache::removeExpiredObjectsInCache(double expiryTime)
{
std::vector<osg::ref_ptr<osg::Object> > objectsToRemove;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
// Remove expired entries from object cache
ObjectCacheMap::iterator oitr = _objectCache.begin();
while(oitr != _objectCache.end())
{
if (oitr->second.second<=expiryTime)
{
objectsToRemove.push_back(oitr->second.first);
_objectCache.erase(oitr++);
}
else
{
++oitr;
}
}
}
// note, actual unref happens outside of the lock
objectsToRemove.clear();
}
void ObjectCache::removeFromObjectCache(const std::string& fileName)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
ObjectCacheMap::iterator itr = _objectCache.find(fileName);
if (itr!=_objectCache.end()) _objectCache.erase(itr);
}
void ObjectCache::clear()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
_objectCache.clear();
}
void ObjectCache::releaseGLObjects(osg::State* state)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(ObjectCacheMap::iterator itr = _objectCache.begin();
itr != _objectCache.end();
++itr)
{
osg::Object* object = itr->second.first.get();
object->releaseGLObjects(state);
}
}
void ObjectCache::accept(osg::NodeVisitor &nv)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(ObjectCacheMap::iterator itr = _objectCache.begin();
itr != _objectCache.end();
++itr)
{
osg::Object* object = itr->second.first.get();
if (object)
{
osg::Node* node = dynamic_cast<osg::Node*>(object);
if (node)
node->accept(nv);
}
}
}
unsigned int ObjectCache::getCacheSize() const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
return _objectCache.size();
}
}

@ -1,5 +1,8 @@
// Resource ObjectCache for OpenMW, forked from osgDB ObjectCache by Robert Osfield, see copyright notice below.
// The main change from the upstream version is that removeExpiredObjectsInCache no longer keeps a lock while the unref happens.
// Changes:
// - removeExpiredObjectsInCache no longer keeps a lock while the unref happens.
// - template allows customized KeyType.
// - objects with uninitialized time stamp are not removed.
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
@ -19,6 +22,7 @@
#include <osg/Referenced>
#include <osg/ref_ptr>
#include <osg/Node>
#include <string>
#include <map>
@ -32,11 +36,13 @@ namespace osg
namespace Resource {
class ObjectCache : public osg::Referenced
template <typename KeyType>
class GenericObjectCache : public osg::Referenced
{
public:
ObjectCache();
GenericObjectCache()
: osg::Referenced(true) {}
/** For each object in the cache which has an reference count greater than 1
* (and therefore referenced by elsewhere in the application) set the time stamp
@ -44,59 +50,149 @@ class ObjectCache : public osg::Referenced
* This would typically be called once per frame by applications which are doing database paging,
* and need to prune objects that are no longer required.
* The time used should be taken from the FrameStamp::getReferenceTime().*/
void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime);
void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime)
{
// look for objects with external references and update their time stamp.
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(typename ObjectCacheMap::iterator itr=_objectCache.begin(); itr!=_objectCache.end(); ++itr)
{
// If ref count is greater than 1, the object has an external reference.
// If the timestamp is yet to be initialized, it needs to be updated too.
if (itr->second.first->referenceCount()>1 || itr->second.second == 0.0)
itr->second.second = referenceTime;
}
}
/** Removed object in the cache which have a time stamp at or before the specified expiry time.
* This would typically be called once per frame by applications which are doing database paging,
* and need to prune objects that are no longer required, and called after the a called
* after the call to updateTimeStampOfObjectsInCacheWithExternalReferences(expirtyTime).*/
void removeExpiredObjectsInCache(double expiryTime);
void removeExpiredObjectsInCache(double expiryTime)
{
std::vector<osg::ref_ptr<osg::Object> > objectsToRemove;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
// Remove expired entries from object cache
typename ObjectCacheMap::iterator oitr = _objectCache.begin();
while(oitr != _objectCache.end())
{
if (oitr->second.second<=expiryTime)
{
objectsToRemove.push_back(oitr->second.first);
_objectCache.erase(oitr++);
}
else
++oitr;
}
}
// note, actual unref happens outside of the lock
objectsToRemove.clear();
}
/** Remove all objects in the cache regardless of having external references or expiry times.*/
void clear();
void clear()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
_objectCache.clear();
}
/** Add a filename,object,timestamp triple to the Registry::ObjectCache.*/
void addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp = 0.0);
/** Add a key,object,timestamp triple to the Registry::ObjectCache.*/
void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
_objectCache[key]=ObjectTimeStampPair(object,timestamp);
}
/** Remove Object from cache.*/
void removeFromObjectCache(const std::string& fileName);
void removeFromObjectCache(const KeyType& key)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr!=_objectCache.end()) _objectCache.erase(itr);
}
/** Get an ref_ptr<Object> from the object cache*/
osg::ref_ptr<osg::Object> getRefFromObjectCache(const std::string& fileName);
osg::ref_ptr<osg::Object> getRefFromObjectCache(const KeyType& key)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr!=_objectCache.end())
return itr->second.first;
else return 0;
}
/** Check if an object is in the cache, and if it is, update its usage time stamp. */
bool checkInObjectCache(const std::string& fileName, double timeStamp);
bool checkInObjectCache(const KeyType& key, double timeStamp)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr!=_objectCache.end())
{
itr->second.second = timeStamp;
return true;
}
else return false;
}
/** call releaseGLObjects on all objects attached to the object cache.*/
void releaseGLObjects(osg::State* state);
void releaseGLObjects(osg::State* state)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr)
{
osg::Object* object = itr->second.first.get();
object->releaseGLObjects(state);
}
}
/** call node->accept(nv); for all nodes in the objectCache. */
void accept(osg::NodeVisitor& nv);
void accept(osg::NodeVisitor& nv)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr)
{
osg::Object* object = itr->second.first.get();
if (object)
{
osg::Node* node = dynamic_cast<osg::Node*>(object);
if (node)
node->accept(nv);
}
}
}
/** call operator()(osg::Object*) for each object in the cache. */
template <class Functor>
void call(Functor& f)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for (ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it)
for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it)
f(it->second.first.get());
}
/** Get the number of objects in the cache. */
unsigned int getCacheSize() const;
unsigned int getCacheSize() const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
return _objectCache.size();
}
protected:
virtual ~ObjectCache();
virtual ~GenericObjectCache() {}
typedef std::pair<osg::ref_ptr<osg::Object>, double > ObjectTimeStampPair;
typedef std::map<std::string, ObjectTimeStampPair > ObjectCacheMap;
typedef std::map<KeyType, ObjectTimeStampPair > ObjectCacheMap;
ObjectCacheMap _objectCache;
mutable OpenThreads::Mutex _objectCacheMutex;
};
class ObjectCache : public GenericObjectCache<std::string>
{
};
}
#endif

@ -1,46 +0,0 @@
#include "resourcemanager.hpp"
#include "objectcache.hpp"
namespace Resource
{
ResourceManager::ResourceManager(const VFS::Manager *vfs)
: mVFS(vfs)
, mCache(new Resource::ObjectCache)
, mExpiryDelay(0.0)
{
}
ResourceManager::~ResourceManager()
{
}
void ResourceManager::updateCache(double referenceTime)
{
mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime);
mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay);
}
void ResourceManager::clearCache()
{
mCache->clear();
}
void ResourceManager::setExpiryDelay(double expiryDelay)
{
mExpiryDelay = expiryDelay;
}
const VFS::Manager* ResourceManager::getVFS() const
{
return mVFS;
}
void ResourceManager::releaseGLObjects(osg::State *state)
{
mCache->releaseGLObjects(state);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save