Add OpenMW commits up to 6 May 2021

# Conflicts:
#   CMakeLists.txt
#   components/CMakeLists.txt
pull/593/head
David Cernat 4 years ago
commit ea6d5c68ae

@ -147,6 +147,7 @@ Debian_Clang_tests:
macOS11_Xcode12:
extends: .MacOS
image: macos-11-xcode-12
allow_failure: true
cache:
key: macOS11_Xcode12.v1
variables:
@ -155,7 +156,6 @@ macOS11_Xcode12:
macOS10.15_Xcode11:
extends: .MacOS
image: macos-10.15-xcode-11
allow_failure: true
cache:
key: macOS10.15_Xcode11.v1
variables:

@ -100,6 +100,7 @@
Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage
Bug #5758: Paralyzed actors behavior is inconsistent with vanilla
Bug #5762: Movement solver is insufficiently robust
BUG #5800: Equipping a CE enchanted ring deselects an already equipped and selected enchanted ring from the spell menu
Bug #5807: Video decoding crash on ARM
Bug #5821: NPCs from mods getting removed if mod order was changed
Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee
@ -118,6 +119,7 @@
Bug #5923: Clicking on empty spaces between journal entries might show random topics
Bug #5934: AddItem command doesn't accept negative values
Bug #5975: NIF controllers from sheath meshes are used
Bug #5991: Activate should always be allowed for inventory items
Bug #5995: NiUVController doesn't calculate the UV offset properly
Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references

@ -17,3 +17,6 @@ qmake --version
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-8f5ef6e.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
# additional libraries
[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew install fontconfig

@ -73,9 +73,6 @@ CONFIGURATIONS=()
TEST_FRAMEWORK=""
GOOGLE_INSTALL_ROOT=""
INSTALL_PREFIX="."
BULLET_DOUBLE=true
BULLET_DBL=""
BULLET_DBL_DISPLAY="Single precision"
ACTIVATE_MSVC=""
SINGLE_CONFIG=""
@ -99,9 +96,6 @@ while [ $# -gt 0 ]; do
d )
SKIP_DOWNLOAD=true ;;
D )
BULLET_DOUBLE=true ;;
e )
SKIP_EXTRACT=true ;;
@ -149,8 +143,6 @@ Options:
For single-config generators, several configurations can be set up at once by specifying -c multiple times.
-d
Skip checking the downloads.
-D
Use double-precision Bullet
-e
Skip extracting dependencies.
-h
@ -433,9 +425,6 @@ if [ -n "$SINGLE_CONFIG" ]; then
if [ -n "$SKIP_DOWNLOAD" ]; then
RECURSIVE_OPTIONS+=("-d")
fi
if [ -n "$BULLET_DOUBLE" ]; then
RECURSIVE_OPTIONS+=("-D")
fi
if [ -n "$SKIP_EXTRACT" ]; then
RECURSIVE_OPTIONS+=("-e")
fi
@ -508,12 +497,6 @@ if ! [ -z $UNITY_BUILD ]; then
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
fi
if [ -n "$BULLET_DOUBLE" ]; then
BULLET_DBL="-double"
BULLET_DBL_DISPLAY="Double precision"
add_cmake_opts "-DBULLET_USE_DOUBLES=True"
fi
echo
echo "==================================="
echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}"
@ -538,9 +521,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
# Bullet
download "Bullet 2.89 (${BULLET_DBL_DISPLAY})" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z" \
"Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z"
download "Bullet 2.89" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double.7z" \
"Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double.7z"
# FFmpeg
download "FFmpeg 4.2.2" \
@ -680,15 +663,15 @@ fi
cd $DEPS
echo
# Bullet
printf "Bullet 2.89 (${BULLET_DBL_DISPLAY})... "
printf "Bullet 2.89... "
{
cd $DEPS_INSTALL
if [ -d Bullet ]; then
printf -- "Exists. (No version checking) "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf Bullet
eval 7z x -y "${DEPS}/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z" $STRIP
mv "Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}" Bullet
eval 7z x -y "${DEPS}/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double.7z" $STRIP
mv "Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}-double" Bullet
fi
add_cmake_opts -DBULLET_ROOT="$(real_pwd)/Bullet"
echo Done.

@ -25,6 +25,5 @@ cmake \
-D BUILD_BSATOOL=TRUE \
-D BUILD_ESSIMPORTER=TRUE \
-D BUILD_NIFTEST=TRUE \
-D BULLET_USE_DOUBLES=TRUE \
-G"Unix Makefiles" \
..

@ -22,12 +22,12 @@ declare -rA GROUPED_DEPS=(
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev
libbullet-dev liblz4-dev libpng-dev libjpeg-dev
ca-certificates
"
# TODO: add librecastnavigation-dev when debian is ready
# These dependencies can alternatively be built and linked statically.
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev"
[coverity]="curl"
# Pre-requisites for building MyGUI and OSG for static linking.
@ -64,4 +64,4 @@ export APT_CACHE_DIR="${PWD}/apt-cache"
set -x
mkdir -pv "$APT_CACHE_DIR"
apt-get update -yq
apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y "${deps[@]}"
apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}"

@ -13,9 +13,6 @@ if(POLICY CMP0083)
cmake_policy(SET CMP0083 NEW)
endif()
# Detect OS
include(cmake/OSIdentity.cmake)
option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF)
if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
@ -35,7 +32,6 @@ option(BUILD_DOCS "Build documentation." OFF )
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF)
option(BULLET_USE_DOUBLES "Use double precision for Bullet" ON)
option(BUILD_OPENMW_MP "Build OpenMW-MP" ON)
option(BUILD_BROWSER "Build tes3mp Server Browser" ON)
option(BUILD_MASTER "Build tes3mp Master Server" OFF)
@ -286,16 +282,16 @@ if(FFmpeg_FOUND)
set(FFVER_OK FALSE)
endif()
endif()
if(NOT FFVER_OK AND NOT APPLE) # unable to detect on version on MacOS < 11.0
message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" )
endif()
endif()
if(NOT FFmpeg_FOUND)
message(FATAL_ERROR "FFmpeg was not found" )
endif()
if(NOT FFVER_OK)
message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" )
endif()
if(WIN32)
message("Can not detect FFmpeg version, at least the 3.2 is required" )
endif()
@ -339,7 +335,31 @@ if(OPENMW_USE_SYSTEM_BULLET)
set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine
endif()
find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath)
# First, try BulletConfig-float64.cmake which comes with Debian derivatives.
# This file does not define the Bullet version in a CMake-friendly way.
find_package(Bullet CONFIGS BulletConfig-float64.cmake QUIET COMPONENTS BulletCollision LinearMath)
if (BULLET_FOUND)
string(REPLACE "." "" _bullet_version_num ${BULLET_VERSION_STRING})
if (_bullet_version_num VERSION_LESS REQUIRED_BULLET_VERSION)
message(FATAL_ERROR "System bullet version too old, OpenMW requires at least ${REQUIRED_BULLET_VERSION}, got ${_bullet_version_num}")
endif()
# Fix the relative include:
set(BULLET_INCLUDE_DIRS "${BULLET_ROOT_DIR}/${BULLET_INCLUDE_DIRS}")
include(FindPackageMessage)
find_package_message(Bullet "Found Bullet: ${BULLET_LIBRARIES} ${BULLET_VERSION_STRING}" "${BULLET_VERSION_STRING}-float64")
else()
find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath)
endif()
# Only link the Bullet libraries that we need:
string(REGEX MATCHALL "((optimized|debug);)?[^;]*(BulletCollision|LinearMath)[^;]*" BULLET_LIBRARIES "${BULLET_LIBRARIES}")
include(cmake/CheckBulletPrecision.cmake)
if (HAS_DOUBLE_PRECISION_BULLET)
message(STATUS "Bullet uses double precision")
else()
message(FATAL_ERROR "Bullet does not uses double precision")
endif()
endif()
if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer

@ -114,9 +114,8 @@ namespace
generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random);
std::vector<RecastMesh::Water> water;
generateWater(std::back_inserter(water), 2, random);
const std::size_t trianglesPerChunk = 256;
RecastMesh recastMesh(generation, revision, std::move(indices), std::move(vertices),
std::move(areaTypes), std::move(water), trianglesPerChunk);
std::move(areaTypes), std::move(water));
std::vector<OffMeshConnection> offMeshConnections;
generateOffMeshConnection(std::back_inserter(offMeshConnections), 300, random);
return Key {agentHalfExtents, tilePosition, std::move(recastMesh), std::move(offMeshConnections)};

@ -3,10 +3,8 @@
#include <array>
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#include <QFileDialog>
#include <QCompleter>
#include <QProxyStyle>
#include <QString>
#include <components/contentselector/view/contentselector.hpp>
#include <components/contentselector/model/esmfile.hpp>

@ -1,7 +1,6 @@
#ifndef ADVANCEDPAGE_H
#define ADVANCEDPAGE_H
#include <QWidget>
#include <QCompleter>
#include <QStringListModel>

@ -4,7 +4,6 @@
#include <QPushButton>
#include <QMessageBox>
#include <QCheckBox>
#include <QMenu>
#include <QSortFilterProxyModel>
#include <thread>
@ -14,7 +13,6 @@
#include <components/files/configurationmanager.hpp>
#include <components/contentselector/model/esmfile.hpp>
#include <components/contentselector/model/naturalsort.hpp>
#include <components/contentselector/view/contentselector.hpp>
#include <components/config/gamesettings.hpp>

@ -6,7 +6,6 @@
#include <QDir>
#include <QFile>
#include <QStringList>
class QSortFilterProxyModel;

@ -1,8 +1,6 @@
#ifndef GRAPHICSPAGE_H
#define GRAPHICSPAGE_H
#include <QWidget>
#include "ui_graphicspage.h"
#include <components/settings/settings.hpp>

@ -1,10 +1,8 @@
#include <iostream>
#include <QApplication>
#include <QTranslator>
#include <QTextCodec>
#include <QDir>
#include <QDebug>
#ifdef MAC_OS_X_VERSION_MIN_REQUIRED
#undef MAC_OS_X_VERSION_MIN_REQUIRED

@ -5,15 +5,12 @@
#include <QDate>
#include <QMessageBox>
#include <QPushButton>
#include <QFontDatabase>
#include <QInputDialog>
#include <QFileDialog>
#include <QCloseEvent>
#include <QTextCodec>
#include <QDebug>
#include "playpage.hpp"
#include "graphicspage.hpp"
#include "datafilespage.hpp"

@ -1,8 +1,6 @@
#ifndef MAINDIALOG_H
#define MAINDIALOG_H
#include <QMainWindow>
#include <QProcess>
#ifndef Q_MOC_RUN
#include <components/files/configurationmanager.hpp>

@ -1,8 +1,6 @@
#ifndef PLAYPAGE_H
#define PLAYPAGE_H
#include <QWidget>
#include "ui_playpage.h"
class QComboBox;

@ -1,7 +1,6 @@
#include <signal.h>
#include <SDL.h>
#include <SDL_video.h>
bool initSDL()
{

@ -2,7 +2,6 @@
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QDir>
#include <components/files/configurationmanager.hpp>

@ -1,9 +1,6 @@
#ifndef SETTINGSPAGE_HPP
#define SETTINGSPAGE_HPP
#include <QWidget>
#include <QProcess>
#include <components/process/processinvoker.hpp>
#include "ui_settingspage.h"

@ -1,7 +1,6 @@
#ifndef OPENMW_CELLNAMELOADER_H
#define OPENMW_CELLNAMELOADER_H
#include <QComboBox>
#include <QSet>
#include <QString>

@ -11,7 +11,6 @@
#define LINEEDIT_H
#include <QLineEdit>
#include <QStyle>
#include <QStylePainter>
#include <QToolButton>

@ -1,5 +1,4 @@
#include <QRegExpValidator>
#include <QLineEdit>
#include <QString>
#include <QApplication>
#include <QKeyEvent>

@ -407,7 +407,7 @@ namespace MWBase
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
///< @return an updated Ptr
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0;
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) = 0;
///< @return an updated Ptr
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;

@ -158,7 +158,7 @@ void Actor::updatePosition()
mSimulationPosition = mWorldPosition;
mPositionOffset = osg::Vec3f();
mStandingOnPtr = nullptr;
mSkipSimulation = true;
mSkipCollisions = true;
}
void Actor::updateWorldPosition()
@ -175,9 +175,7 @@ osg::Vec3f Actor::getWorldPosition() const
void Actor::setSimulationPosition(const osg::Vec3f& position)
{
if (!mSkipSimulation)
mSimulationPosition = position;
mSkipSimulation = false;
mSimulationPosition = position;
}
osg::Vec3f Actor::getSimulationPosition() const
@ -211,21 +209,19 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
bool Actor::setPosition(const osg::Vec3f& position)
{
std::scoped_lock lock(mPositionMutex);
// position is being forced, ignore simulation results until we sync up
if (mSkipSimulation)
return false;
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
updateWorldPosition();
applyOffsetChange();
bool hasChanged = mPosition != position || mWorldPositionChanged;
mPreviousPosition = mPosition;
mPosition = position;
return hasChanged;
}
void Actor::adjustPosition(const osg::Vec3f& offset)
void Actor::adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions)
{
std::scoped_lock lock(mPositionMutex);
mPositionOffset += offset;
mSkipCollisions = mSkipCollisions || ignoreCollisions;
}
void Actor::applyOffsetChange()
@ -337,4 +333,9 @@ void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr)
mStandingOnPtr = ptr;
}
bool Actor::skipCollisions()
{
return std::exchange(mSkipCollisions, false);
}
}

@ -101,7 +101,7 @@ namespace MWPhysics
void updatePosition();
// register a position offset that will be applied during simulation.
void adjustPosition(const osg::Vec3f& offset);
void adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions);
// apply position offset. Can't be called during simulation
void applyOffsetChange();
@ -177,6 +177,8 @@ namespace MWPhysics
mLastStuckPosition = position;
}
bool skipCollisions();
private:
MWWorld::Ptr mStandingOnPtr;
/// Removes then re-adds the collision object to the dynamics world
@ -206,7 +208,7 @@ namespace MWPhysics
osg::Vec3f mPreviousPosition;
osg::Vec3f mPositionOffset;
bool mWorldPositionChanged;
bool mSkipSimulation;
bool mSkipCollisions;
btTransform mLocalTransform;
mutable std::mutex mPositionMutex;

@ -131,7 +131,7 @@ namespace MWPhysics
// Reset per-frame data
physicActor->setWalkingOnWater(false);
// Anything to collide with?
if(!physicActor->getCollisionMode())
if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection)
{
actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))
@ -437,7 +437,7 @@ namespace MWPhysics
return;
auto* physicActor = actor.mActorRaw;
if(!physicActor->getCollisionMode()) // noclipping/tcl
if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) // noclipping/tcl
return;
auto* collisionObject = physicActor->getCollisionObject();

@ -361,7 +361,6 @@ namespace MWPhysics
for (const auto& [_, actor] : actors)
{
actor->updatePosition();
actor->setSimulationPosition(actor->getWorldPosition()); // updatePosition skip next simulation, now we need to "consume" it
actor->updateCollisionObjectPosition();
mMovedActors.emplace_back(actor->getPtr());
}

@ -978,9 +978,7 @@ namespace MWPhysics
void ActorFrameData::updatePosition(btCollisionWorld* world)
{
mActorRaw->updateWorldPosition();
// If physics runs "fast enough", position are interpolated without simulation
// By calling this here, we are sure that offsets are applied at least once per frame,
// regardless of simulation speed.
mSkipCollisionDetection = mActorRaw->skipCollisions();
mActorRaw->applyOffsetChange();
mPosition = mActorRaw->getPosition();
if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world))

@ -91,6 +91,7 @@ namespace MWPhysics
bool mFloatToSurface;
bool mNeedLand;
bool mWaterCollision;
bool mSkipCollisionDetection;
float mWaterlevel;
float mSlowFall;
float mOldHeight;

@ -394,7 +394,7 @@ namespace MWScript
MWWorld::Ptr ptr = R()(runtime);
if (ptr.getRefData().activateByScript())
if (ptr.getRefData().activateByScript() || ptr.getContainerStore())
context.executeActivation(ptr, MWMechanics::getPlayer());
}
};

@ -49,7 +49,7 @@ namespace MWScript
std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
for (auto& actor : actors)
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false);
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false, false);
}
template<class R>
@ -363,7 +363,7 @@ namespace MWScript
}
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true));
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true, true));
}
};
@ -902,7 +902,7 @@ namespace MWScript
// This approach can be used to create elevators.
moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false, true));
}
};
@ -938,7 +938,7 @@ namespace MWScript
// This approach can be used to create elevators.
moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false, true));
}
};

@ -91,13 +91,29 @@ namespace MWWorld
// move all slots one towards begin(), then equip the item in the slot that is now free
if (slot == slots_.first.end())
{
for (slot=slots_.first.begin();slot!=slots_.first.end(); ++slot)
ContainerStoreIterator enchItem = invStore.getSelectedEnchantItem();
bool reEquip = false;
for (slot = slots_.first.begin(); slot != slots_.first.end(); ++slot)
{
invStore.unequipSlot(*slot, actor, false);
if (slot+1 != slots_.first.end())
invStore.equip(*slot, invStore.getSlot(*(slot+1)), actor);
if (slot + 1 != slots_.first.end())
{
invStore.equip(*slot, invStore.getSlot(*(slot + 1)), actor);
}
else
{
invStore.equip(*slot, it, actor);
}
//Fix for issue of selected enchated item getting remmoved on cycle
if (invStore.getSlot(*slot) == enchItem)
{
reEquip = true;
}
}
if (reEquip)
{
invStore.setSelectedEnchantItem(enchItem);
}
}
}

@ -448,7 +448,7 @@ namespace MWWorld
void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos)
{
const float resetThreshold = ESM::Land::REAL_SIZE;
for (auto pos : mTerrainPreloadPositions)
for (const auto& pos : mTerrainPreloadPositions)
if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second)
return;
if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone())
@ -461,10 +461,10 @@ namespace MWWorld
bool contains(const std::vector<CellPreloader::PositionCellGrid>& container, const std::vector<CellPreloader::PositionCellGrid>& contained)
{
for (auto pos : contained)
for (const auto& pos : contained)
{
bool found = false;
for (auto pos2 : container)
for (const auto& pos2 : container)
{
if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second)
{

@ -8,28 +8,11 @@
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/misc/rng.hpp>
#include <iterator>
#include <stdexcept>
namespace
{
template<typename T>
class GetRecords
{
const std::string mFind;
std::vector<const T*> *mRecords;
public:
GetRecords(const std::string &str, std::vector<const T*> *records)
: mFind(Misc::StringUtils::lowerCase(str)), mRecords(records)
{ }
void operator()(const T *item)
{
if(Misc::StringUtils::ciCompareLen(mFind, item->mId, mFind.size()) == 0)
mRecords->push_back(item);
}
};
struct Compare
{
bool operator()(const ESM::Land *x, const ESM::Land *y) {
@ -169,7 +152,11 @@ namespace MWWorld
const T *Store<T>::searchRandom(const std::string &id) const
{
std::vector<const T*> results;
std::for_each(mShared.begin(), mShared.end(), GetRecords<T>(id, &results));
std::copy_if(mShared.begin(), mShared.end(), std::back_inserter(results),
[&id](const T* item)
{
return Misc::StringUtils::ciCompareLen(id, item->mId, id.size()) == 0;
});
if(!results.empty())
return results[Misc::Rng::rollDice(results.size())];
return nullptr;
@ -186,17 +173,6 @@ namespace MWWorld
return ptr;
}
template<typename T>
const T *Store<T>::findRandom(const std::string &id) const
{
const T *ptr = searchRandom(id);
if(ptr == nullptr)
{
const std::string msg = T::getRecordType() + " starting with '" + id + "' not found";
throw std::runtime_error(msg);
}
return ptr;
}
template<typename T>
RecordId Store<T>::load(ESM::ESMReader &esm)
{
T record;

@ -179,10 +179,6 @@ namespace MWWorld
const T *find(const std::string &id) const;
/** Returns a random record that starts with the named ID. An exception is thrown if none
* are found. */
const T *findRandom(const std::string &id) const;
iterator begin() const;
iterator end() const;

@ -1441,12 +1441,12 @@ namespace MWWorld
return moveObject(ptr, cell, x, y, z, movePhysics);
}
MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive)
MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions)
{
auto* actor = mPhysics->getActor(ptr);
osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec;
if (actor)
actor->adjustPosition(vec);
actor->adjustPosition(vec, ignoreCollisions);
if (ptr.getClass().isActor())
return moveObject(ptr, newpos.x(), newpos.y(), newpos.z(), false, moveToActive && ptr != getPlayerPtr());
return moveObject(ptr, newpos.x(), newpos.y(), newpos.z());

@ -476,7 +476,7 @@ namespace MWWorld
MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override;
///< @return an updated Ptr
MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) override;
MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) override;
///< @return an updated Ptr
void scaleObject (const Ptr& ptr, float scale) override;

@ -68,7 +68,6 @@ namespace
mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024;
mSettings.mMaxPolygonPathSize = 1024;
mSettings.mMaxSmoothPathSize = 1024;
mSettings.mTrianglesPerChunk = 256;
mSettings.mMaxPolys = 4096;
mSettings.mMaxTilesNumber = 512;
mSettings.mMinUpdateInterval = std::chrono::milliseconds(50);

@ -31,9 +31,7 @@ namespace
const std::vector<float> mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}};
const std::vector<AreaType> mAreaTypes {1, AreaType_ground};
const std::vector<RecastMesh::Water> mWater {};
const std::size_t mTrianglesPerChunk {1};
const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, mWater, mTrianglesPerChunk};
const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, mWater};
const std::vector<OffMeshConnection> mOffMeshConnections {};
unsigned char* const mData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData mNavMeshData {mData, 1};
@ -130,8 +128,7 @@ namespace
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, water, mTrianglesPerChunk};
const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections));
@ -145,8 +142,7 @@ namespace
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, water, mTrianglesPerChunk};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
@ -166,8 +162,7 @@ namespace
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, water, mTrianglesPerChunk};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
@ -186,13 +181,13 @@ namespace
const std::vector<RecastMesh::Water> leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, leastRecentlySetWater, mTrianglesPerChunk};
mAreaTypes, leastRecentlySetWater};
const auto leastRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1};
const std::vector<RecastMesh::Water> mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, mostRecentlySetWater, mTrianglesPerChunk};
mAreaTypes, mostRecentlySetWater};
const auto mostRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1};
@ -218,13 +213,13 @@ namespace
const std::vector<RecastMesh::Water> leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, leastRecentlyUsedWater, mTrianglesPerChunk};
mAreaTypes, leastRecentlyUsedWater};
const auto leastRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1};
const std::vector<RecastMesh::Water> mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, mostRecentlyUsedWater, mTrianglesPerChunk};
mAreaTypes, mostRecentlyUsedWater};
const auto mostRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1};
@ -261,7 +256,7 @@ namespace
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
@ -280,13 +275,13 @@ namespace
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, anotherWater};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
const std::vector<RecastMesh::Water> tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, tooLargeWater, mTrianglesPerChunk};
mAreaTypes, tooLargeWater};
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
@ -310,7 +305,7 @@ namespace
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, water, mTrianglesPerChunk};
mAreaTypes, water};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
@ -333,7 +328,7 @@ namespace
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};

@ -37,7 +37,6 @@ namespace
DetourNavigatorRecastMeshBuilderTest()
{
mSettings.mRecastScaleFactor = 1.0f;
mSettings.mTrianglesPerChunk = 256;
mBounds.mMin = osg::Vec2f(-std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon(),
-std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon());
mBounds.mMax = osg::Vec2f(std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon(),

@ -24,7 +24,6 @@ namespace
mSettings.mCellSize = 0.2f;
mSettings.mRecastScaleFactor = 0.017647058823529415f;
mSettings.mTileSize = 64;
mSettings.mTrianglesPerChunk = 256;
}
void onChangedTile(const TilePosition& tilePosition)

@ -1,8 +1,6 @@
#ifndef COMPONENTSELECTIONPAGE_HPP
#define COMPONENTSELECTIONPAGE_HPP
#include <QWizardPage>
#include "ui_componentselectionpage.h"
namespace Wizard

@ -1,8 +1,6 @@
#ifndef CONCLUSIONPAGE_HPP
#define CONCLUSIONPAGE_HPP
#include <QWizardPage>
#include "ui_conclusionpage.h"
namespace Wizard

@ -1,8 +1,6 @@
#ifndef EXISTINGINSTALLATIONPAGE_HPP
#define EXISTINGINSTALLATIONPAGE_HPP
#include <QWizardPage>
#include "ui_existinginstallationpage.h"
namespace Wizard

@ -1,8 +1,6 @@
#ifndef IMPORTPAGE_HPP
#define IMPORTPAGE_HPP
#include <QWizardPage>
#include "ui_importpage.h"
namespace Wizard

@ -1,7 +1,5 @@
#include "inisettings.hpp"
#include <QDir>
#include <QTextStream>
#include <QFile>
#include <QStringList>

@ -1,8 +1,6 @@
#ifndef INSTALLATIONTARGETPAGE_HPP
#define INSTALLATIONTARGETPAGE_HPP
#include <QWizardPage>
#include "ui_installationtargetpage.h"
namespace Files

@ -1,8 +1,6 @@
#ifndef INTROPAGE_HPP
#define INTROPAGE_HPP
#include <QWizardPage>
#include "ui_intropage.h"
namespace Wizard

@ -1,8 +1,6 @@
#ifndef LANGUAGESELECTIONPAGE_HPP
#define LANGUAGESELECTIONPAGE_HPP
#include <QWizardPage>
#include "ui_languageselectionpage.h"
namespace Wizard

@ -1,7 +1,5 @@
#include <QApplication>
#include <QTextCodec>
#include <QDir>
#include <QDebug>
#include "mainwizard.hpp"

@ -1,9 +1,7 @@
#ifndef MAINWIZARD_HPP
#define MAINWIZARD_HPP
#include <QProcess>
#include <QWizard>
#include <QMap>
#include <components/process/processinvoker.hpp>

@ -1,8 +1,6 @@
#ifndef METHODSELECTIONPAGE_HPP
#define METHODSELECTIONPAGE_HPP
#include <QWizardPage>
#include "ui_methodselectionpage.h"
namespace Wizard

@ -1,7 +1,6 @@
#include "componentlistwidget.hpp"
#include <QDebug>
#include <QStringList>
ComponentListWidget::ComponentListWidget(QWidget *parent) :
QListWidget(parent)

@ -0,0 +1,26 @@
set(TMP_ROOT ${CMAKE_BINARY_DIR}/try-compile)
file(MAKE_DIRECTORY ${TMP_ROOT})
file(WRITE ${TMP_ROOT}/checkbullet.cpp
"
#include <BulletCollision/CollisionShapes/btSphereShape.h>
int main(int argc, char** argv)
{
btSphereShape shape(1.0);
btScalar mass(1.0);
btVector3 inertia;
shape.calculateLocalInertia(mass, inertia);
return 0;
}
")
message(STATUS "Checking if Bullet uses double precision")
try_compile(RESULT_VAR
${TMP_ROOT}/temp
${TMP_ROOT}/checkbullet.cpp
COMPILE_DEFINITIONS "-DBT_USE_DOUBLE_PRECISION"
LINK_LIBRARIES ${BULLET_LIBRARIES}
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${BULLET_INCLUDE_DIRS}"
)
set(HAS_DOUBLE_PRECISION_BULLET ${RESULT_VAR})

@ -1,67 +0,0 @@
if (UNIX)
if (APPLE)
set(CMAKE_OS_NAME "OSX" CACHE STRING "Operating system name" FORCE)
else (APPLE)
## Check for Debian GNU/Linux ________________
find_file(DEBIAN_FOUND debian_version debconf.conf
PATHS /etc
)
if (DEBIAN_FOUND)
set(CMAKE_OS_NAME "Debian" CACHE STRING "Operating system name" FORCE)
endif (DEBIAN_FOUND)
## Check for Fedora _________________________
find_file(FEDORA_FOUND fedora-release
PATHS /etc
)
if (FEDORA_FOUND)
set(CMAKE_OS_NAME "Fedora" CACHE STRING "Operating system name" FORCE)
endif (FEDORA_FOUND)
## Check for RedHat _________________________
find_file(REDHAT_FOUND redhat-release inittab.RH
PATHS /etc
)
if (REDHAT_FOUND)
set(CMAKE_OS_NAME "RedHat" CACHE STRING "Operating system name" FORCE)
endif (REDHAT_FOUND)
## Extra check for Ubuntu ____________________
if (DEBIAN_FOUND)
## At its core Ubuntu is a Debian system, with
## a slightly altered configuration; hence from
## a first superficial inspection a system will
## be considered as Debian, which signifies an
## extra check is required.
find_file(UBUNTU_EXTRA legal issue
PATHS /etc
)
if (UBUNTU_EXTRA)
## Scan contents of file
file(STRINGS ${UBUNTU_EXTRA} UBUNTU_FOUND
REGEX Ubuntu
)
## Check result of string search
if (UBUNTU_FOUND)
set(CMAKE_OS_NAME "Ubuntu" CACHE STRING "Operating system name" FORCE)
set(DEBIAN_FOUND FALSE)
endif (UBUNTU_FOUND)
endif (UBUNTU_EXTRA)
endif (DEBIAN_FOUND)
endif (APPLE)
endif (UNIX)

@ -349,11 +349,7 @@ if (BUILD_OPENMW OR BUILD_OPENCS)
endif ()
# End of tes3mp change (major)
if (BULLET_USE_DOUBLES AND (UBUNTU_FOUND OR DEBIAN_FOUND) AND OPENMW_USE_SYSTEM_BULLET)
target_link_libraries(components BulletCollision-float64 LinearMath-float64)
else()
target_link_libraries(components ${BULLET_LIBRARIES})
endif()
target_link_libraries(components ${BULLET_LIBRARIES})
if (WIN32)
target_link_libraries(components
@ -387,6 +383,4 @@ endif()
# Make the variable accessible for other subdirectories
set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE)
if (BULLET_USE_DOUBLES)
target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION)
endif()
target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION)

@ -1,179 +0,0 @@
#include "chunkytrimesh.hpp"
#include "exceptions.hpp"
#include <osg/Vec2f>
#include <algorithm>
namespace DetourNavigator
{
namespace
{
struct BoundsItem
{
Rect mBounds;
std::ptrdiff_t mOffset;
unsigned char mAreaTypes;
};
template <std::size_t axis>
struct LessBoundsItem
{
bool operator ()(const BoundsItem& lhs, const BoundsItem& rhs) const
{
return lhs.mBounds.mMinBound[axis] < rhs.mBounds.mMinBound[axis];
}
};
void calcExtends(const std::vector<BoundsItem>& items, const std::size_t imin, const std::size_t imax,
Rect& bounds)
{
bounds = items[imin].mBounds;
std::for_each(
items.begin() + static_cast<std::ptrdiff_t>(imin) + 1,
items.begin() + static_cast<std::ptrdiff_t>(imax),
[&] (const BoundsItem& item)
{
for (int i = 0; i < 2; ++i)
{
bounds.mMinBound[i] = std::min(bounds.mMinBound[i], item.mBounds.mMinBound[i]);
bounds.mMaxBound[i] = std::max(bounds.mMaxBound[i], item.mBounds.mMaxBound[i]);
}
});
}
void subdivide(std::vector<BoundsItem>& items, const std::size_t imin, const std::size_t imax,
const std::size_t trisPerChunk, const std::vector<int>& inIndices, const std::vector<AreaType>& inAreaTypes,
std::size_t& curNode, std::vector<ChunkyTriMeshNode>& nodes, std::size_t& curTri,
std::vector<int>& outIndices, std::vector<AreaType>& outAreaTypes)
{
const auto inum = imax - imin;
const auto icur = curNode;
if (curNode >= nodes.size())
return;
ChunkyTriMeshNode& node = nodes[curNode++];
if (inum <= trisPerChunk)
{
// Leaf
calcExtends(items, imin, imax, node.mBounds);
// Copy triangles.
node.mOffset = static_cast<std::ptrdiff_t>(curTri);
node.mSize = inum;
for (std::size_t i = imin; i < imax; ++i)
{
std::copy(
inIndices.begin() + items[i].mOffset * 3,
inIndices.begin() + items[i].mOffset * 3 + 3,
outIndices.begin() + static_cast<std::ptrdiff_t>(curTri) * 3
);
outAreaTypes[curTri] = inAreaTypes[static_cast<std::size_t>(items[i].mOffset)];
curTri++;
}
}
else
{
// Split
calcExtends(items, imin, imax, node.mBounds);
if (node.mBounds.mMaxBound.x() - node.mBounds.mMinBound.x()
>= node.mBounds.mMaxBound.y() - node.mBounds.mMinBound.y())
{
// Sort along x-axis
std::sort(
items.begin() + static_cast<std::ptrdiff_t>(imin),
items.begin() + static_cast<std::ptrdiff_t>(imax),
LessBoundsItem<0> {}
);
}
else
{
// Sort along y-axis
std::sort(
items.begin() + static_cast<std::ptrdiff_t>(imin),
items.begin() + static_cast<std::ptrdiff_t>(imax),
LessBoundsItem<1> {}
);
}
const auto isplit = imin + inum / 2;
// Left
subdivide(items, imin, isplit, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes);
// Right
subdivide(items, isplit, imax, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes);
const auto iescape = static_cast<std::ptrdiff_t>(curNode) - static_cast<std::ptrdiff_t>(icur);
// Negative index means escape.
node.mOffset = -iescape;
}
}
}
ChunkyTriMesh::ChunkyTriMesh(const std::vector<float>& verts, const std::vector<int>& indices,
const std::vector<AreaType>& flags, const std::size_t trisPerChunk)
: mMaxTrisPerChunk(0)
{
const auto trianglesCount = indices.size() / 3;
if (trianglesCount == 0)
return;
const auto nchunks = (trianglesCount + trisPerChunk - 1) / trisPerChunk;
mNodes.resize(nchunks * 4);
mIndices.resize(trianglesCount * 3);
mAreaTypes.resize(trianglesCount);
// Build tree
std::vector<BoundsItem> items(trianglesCount);
for (std::size_t i = 0; i < trianglesCount; i++)
{
auto& item = items[i];
item.mOffset = static_cast<std::ptrdiff_t>(i);
item.mAreaTypes = flags[i];
// Calc triangle XZ bounds.
const auto baseIndex = static_cast<std::size_t>(indices[i * 3]) * 3;
item.mBounds.mMinBound.x() = item.mBounds.mMaxBound.x() = verts[baseIndex + 0];
item.mBounds.mMinBound.y() = item.mBounds.mMaxBound.y() = verts[baseIndex + 2];
for (std::size_t j = 1; j < 3; ++j)
{
const auto index = static_cast<std::size_t>(indices[i * 3 + j]) * 3;
item.mBounds.mMinBound.x() = std::min(item.mBounds.mMinBound.x(), verts[index + 0]);
item.mBounds.mMinBound.y() = std::min(item.mBounds.mMinBound.y(), verts[index + 2]);
item.mBounds.mMaxBound.x() = std::max(item.mBounds.mMaxBound.x(), verts[index + 0]);
item.mBounds.mMaxBound.y() = std::max(item.mBounds.mMaxBound.y(), verts[index + 2]);
}
}
std::size_t curTri = 0;
std::size_t curNode = 0;
subdivide(items, 0, trianglesCount, trisPerChunk, indices, flags, curNode, mNodes, curTri, mIndices, mAreaTypes);
items.clear();
mNodes.resize(curNode);
// Calc max tris per node.
for (auto& node : mNodes)
{
const bool isLeaf = node.mOffset >= 0;
if (!isLeaf)
continue;
if (node.mSize > mMaxTrisPerChunk)
mMaxTrisPerChunk = node.mSize;
}
}
}

@ -1,102 +0,0 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H
#include "areatype.hpp"
#include <osg/Vec2f>
#include <array>
#include <vector>
namespace DetourNavigator
{
struct Rect
{
osg::Vec2f mMinBound;
osg::Vec2f mMaxBound;
};
struct ChunkyTriMeshNode
{
Rect mBounds;
std::ptrdiff_t mOffset;
std::size_t mSize;
};
struct Chunk
{
const int* const mIndices;
const AreaType* const mAreaTypes;
const std::size_t mSize;
};
inline bool checkOverlapRect(const Rect& lhs, const Rect& rhs)
{
bool overlap = true;
overlap = (lhs.mMinBound.x() > rhs.mMaxBound.x() || lhs.mMaxBound.x() < rhs.mMinBound.x()) ? false : overlap;
overlap = (lhs.mMinBound.y() > rhs.mMaxBound.y() || lhs.mMaxBound.y() < rhs.mMinBound.y()) ? false : overlap;
return overlap;
}
class ChunkyTriMesh
{
public:
/// Creates partitioned triangle mesh (AABB tree),
/// where each node contains at max trisPerChunk triangles.
ChunkyTriMesh(const std::vector<float>& verts, const std::vector<int>& tris,
const std::vector<AreaType>& flags, const std::size_t trisPerChunk);
ChunkyTriMesh(ChunkyTriMesh&&) = default;
ChunkyTriMesh& operator=(ChunkyTriMesh&&) = default;
ChunkyTriMesh(const ChunkyTriMesh&) = delete;
ChunkyTriMesh& operator=(const ChunkyTriMesh&) = delete;
/// Returns the chunk indices which overlap the input rectable.
template <class Function>
void forEachChunksOverlappingRect(const Rect& rect, Function&& function) const
{
// Traverse tree
for (std::size_t i = 0; i < mNodes.size(); )
{
const ChunkyTriMeshNode* node = &mNodes[i];
const bool overlap = checkOverlapRect(rect, node->mBounds);
const bool isLeafNode = node->mOffset >= 0;
if (isLeafNode && overlap)
function(i);
if (overlap || isLeafNode)
i++;
else
{
const auto escapeIndex = -node->mOffset;
i += static_cast<std::size_t>(escapeIndex);
}
}
}
Chunk getChunk(const std::size_t chunkId) const
{
const auto& node = mNodes[chunkId];
return Chunk {
mIndices.data() + node.mOffset * 3,
mAreaTypes.data() + node.mOffset,
node.mSize
};
}
std::size_t getMaxTrisPerChunk() const
{
return mMaxTrisPerChunk;
}
private:
std::vector<ChunkyTriMeshNode> mNodes;
std::vector<int> mIndices;
std::vector<AreaType> mAreaTypes;
std::size_t mMaxTrisPerChunk;
};
}
#endif

@ -1,5 +1,4 @@
#include "makenavmesh.hpp"
#include "chunkytrimesh.hpp"
#include "debug.hpp"
#include "dtstatus.hpp"
#include "exceptions.hpp"
@ -22,6 +21,7 @@
#include <algorithm>
#include <iomanip>
#include <limits>
#include <array>
namespace
{
@ -178,65 +178,30 @@ namespace
bool rasterizeSolidObjectsTriangles(rcContext& context, const RecastMesh& recastMesh, const rcConfig& config,
rcHeightfield& solid)
{
const auto& chunkyMesh = recastMesh.getChunkyTriMesh();
std::vector<unsigned char> areas(chunkyMesh.getMaxTrisPerChunk(), AreaType_null);
const osg::Vec2f tileBoundsMin(config.bmin[0], config.bmin[2]);
const osg::Vec2f tileBoundsMax(config.bmax[0], config.bmax[2]);
bool result = false;
chunkyMesh.forEachChunksOverlappingRect(Rect {tileBoundsMin, tileBoundsMax},
[&] (const std::size_t cid)
{
const auto chunk = chunkyMesh.getChunk(cid);
std::fill(
areas.begin(),
std::min(areas.begin() + static_cast<std::ptrdiff_t>(chunk.mSize),
areas.end()),
AreaType_null
);
rcMarkWalkableTriangles(
&context,
config.walkableSlopeAngle,
recastMesh.getVertices().data(),
static_cast<int>(recastMesh.getVerticesCount()),
chunk.mIndices,
static_cast<int>(chunk.mSize),
areas.data()
);
for (std::size_t i = 0; i < chunk.mSize; ++i)
areas[i] = chunk.mAreaTypes[i];
rcClearUnwalkableTriangles(
&context,
config.walkableSlopeAngle,
recastMesh.getVertices().data(),
static_cast<int>(recastMesh.getVerticesCount()),
chunk.mIndices,
static_cast<int>(chunk.mSize),
areas.data()
);
const auto trianglesRasterized = rcRasterizeTriangles(
&context,
recastMesh.getVertices().data(),
static_cast<int>(recastMesh.getVerticesCount()),
chunk.mIndices,
areas.data(),
static_cast<int>(chunk.mSize),
solid,
config.walkableClimb
);
if (!trianglesRasterized)
throw NavigatorException("Failed to create rasterize triangles from recast mesh for navmesh");
result = true;
});
return result;
std::vector<unsigned char> areas(recastMesh.getAreaTypes().begin(), recastMesh.getAreaTypes().end());
rcClearUnwalkableTriangles(
&context,
config.walkableSlopeAngle,
recastMesh.getVertices().data(),
static_cast<int>(recastMesh.getVerticesCount()),
recastMesh.getIndices().data(),
static_cast<int>(areas.size()),
areas.data()
);
return rcRasterizeTriangles(
&context,
recastMesh.getVertices().data(),
static_cast<int>(recastMesh.getVerticesCount()),
recastMesh.getIndices().data(),
areas.data(),
static_cast<int>(areas.size()),
solid,
config.walkableClimb
);
}
void rasterizeWaterTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh,

@ -6,7 +6,6 @@
#include "settings.hpp"
#include "objectid.hpp"
#include "navmeshcacheitem.hpp"
#include "recastmesh.hpp"
#include "recastmeshtiles.hpp"
namespace ESM

@ -6,14 +6,13 @@
namespace DetourNavigator
{
RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, std::vector<int> indices, std::vector<float> vertices,
std::vector<AreaType> areaTypes, std::vector<Water> water, const std::size_t trianglesPerChunk)
std::vector<AreaType> areaTypes, std::vector<Water> water)
: mGeneration(generation)
, mRevision(revision)
, mIndices(std::move(indices))
, mVertices(std::move(vertices))
, mAreaTypes(std::move(areaTypes))
, mWater(std::move(water))
, mChunkyTriMesh(mVertices, mIndices, mAreaTypes, trianglesPerChunk)
{
if (getTrianglesCount() != mAreaTypes.size())
throw InvalidArgument("Number of flags doesn't match number of triangles: triangles="

@ -2,7 +2,6 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H
#include "areatype.hpp"
#include "chunkytrimesh.hpp"
#include "bounds.hpp"
#include <components/bullethelpers/operators.hpp>
@ -28,7 +27,7 @@ namespace DetourNavigator
};
RecastMesh(std::size_t generation, std::size_t revision, std::vector<int> indices, std::vector<float> vertices,
std::vector<AreaType> areaTypes, std::vector<Water> water, const std::size_t trianglesPerChunk);
std::vector<AreaType> areaTypes, std::vector<Water> water);
std::size_t getGeneration() const
{
@ -70,11 +69,6 @@ namespace DetourNavigator
return mIndices.size() / 3;
}
const ChunkyTriMesh& getChunkyTriMesh() const
{
return mChunkyTriMesh;
}
const Bounds& getBounds() const
{
return mBounds;
@ -87,7 +81,6 @@ namespace DetourNavigator
std::vector<float> mVertices;
std::vector<AreaType> mAreaTypes;
std::vector<Water> mWater;
ChunkyTriMesh mChunkyTriMesh;
Bounds mBounds;
};

@ -1,5 +1,4 @@
#include "recastmeshbuilder.hpp"
#include "chunkytrimesh.hpp"
#include "debug.hpp"
#include "settings.hpp"
#include "settingsutils.hpp"
@ -19,6 +18,7 @@
#include <algorithm>
#include <cassert>
#include <tuple>
#include <array>
namespace DetourNavigator
{
@ -156,8 +156,7 @@ namespace DetourNavigator
{
optimizeRecastMesh(mIndices, mVertices);
std::sort(mWater.begin(), mWater.end());
return std::make_shared<RecastMesh>(generation, revision, mIndices, mVertices, mAreaTypes,
mWater, mSettings.get().mTrianglesPerChunk);
return std::make_shared<RecastMesh>(generation, revision, mIndices, mVertices, mAreaTypes, mWater);
}
void RecastMeshBuilder::reset()

@ -2,13 +2,14 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHTILE_H
#include "tileposition.hpp"
#include "recastmesh.hpp"
#include <map>
#include <memory>
namespace DetourNavigator
{
class RecastMesh;
using RecastMeshTiles = std::map<TilePosition, std::shared_ptr<RecastMesh>>;
}

@ -33,7 +33,6 @@ namespace DetourNavigator
navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast<std::size_t>(::Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator"));
navigatorSettings.mMaxPolygonPathSize = static_cast<std::size_t>(::Settings::Manager::getInt("max polygon path size", "Navigator"));
navigatorSettings.mMaxSmoothPathSize = static_cast<std::size_t>(::Settings::Manager::getInt("max smooth path size", "Navigator"));
navigatorSettings.mTrianglesPerChunk = static_cast<std::size_t>(::Settings::Manager::getInt("triangles per chunk", "Navigator"));
navigatorSettings.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator");
navigatorSettings.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator");
navigatorSettings.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator");

@ -35,7 +35,6 @@ namespace DetourNavigator
std::size_t mMaxNavMeshTilesCacheSize = 0;
std::size_t mMaxPolygonPathSize = 0;
std::size_t mMaxSmoothPathSize = 0;
std::size_t mTrianglesPerChunk = 0;
std::string mRecastMeshPathPrefix;
std::string mNavMeshPathPrefix;
std::chrono::milliseconds mMinUpdateInterval;

@ -161,9 +161,9 @@ namespace ESM
{
// Generate WNAM record
signed char wnam[LAND_GLOBAL_MAP_LOD_SIZE];
float max = std::numeric_limits<signed char>::max();
float min = std::numeric_limits<signed char>::min();
float vertMult = static_cast<float>(ESM::Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT;
constexpr float max = std::numeric_limits<signed char>::max();
constexpr float min = std::numeric_limits<signed char>::min();
constexpr float vertMult = static_cast<float>(ESM::Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT;
for (int row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row)
{
for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col)

@ -212,7 +212,7 @@ void NIFFile::parse(Files::IStreamPtr stream)
userVer = nif.getUInt();
// Number of records
unsigned int recNum = nif.getUInt();
const std::size_t recNum = nif.getUInt();
records.resize(recNum);
// Bethesda stream header
@ -251,7 +251,7 @@ void NIFFile::parse(Files::IStreamPtr stream)
std::vector<unsigned int> recSizes; // Currently unused
nif.getUInts(recSizes, recNum);
}
unsigned int stringNum = nif.getUInt();
const std::size_t stringNum = nif.getUInt();
nif.getUInt(); // Max string length
if (stringNum)
nif.getSizedStrings(strings, stringNum);
@ -264,7 +264,7 @@ void NIFFile::parse(Files::IStreamPtr stream)
}
const bool hasRecordSeparators = ver >= NIFStream::generateVersion(10,0,0,0) && ver < NIFStream::generateVersion(10,2,0,0);
for (unsigned int i = 0; i < recNum; i++)
for (std::size_t i = 0; i < recNum; i++)
{
Record *r = nullptr;
@ -308,21 +308,21 @@ void NIFFile::parse(Files::IStreamPtr stream)
r->read(&nif);
}
unsigned int rootNum = nif.getUInt();
const std::size_t rootNum = nif.getUInt();
roots.resize(rootNum);
//Determine which records are roots
for (unsigned int i = 0; i < rootNum; i++)
for (std::size_t i = 0; i < rootNum; i++)
{
int idx = nif.getInt();
if (idx >= 0 && idx < int(records.size()))
if (idx >= 0 && static_cast<std::size_t>(idx) < records.size())
{
roots[i] = records[idx];
}
else
{
roots[i] = nullptr;
warn("Null Root found");
warn("Root " + std::to_string(i + 1) + " does not point to a record: index " + std::to_string(idx));
}
}

@ -7,7 +7,7 @@ namespace Nif
osg::Quat NIFStream::getQuaternion()
{
float f[4];
readLittleEndianBufferOfType<4, float>(inp, (float*)&f);
readLittleEndianBufferOfType<4, float>(inp, f);
osg::Quat quat;
quat.w() = f[0];
quat.x() = f[1];

@ -7,6 +7,8 @@
#include <stdint.h>
#include <stdexcept>
#include <vector>
#include <typeinfo>
#include <type_traits>
#include <components/files/constrainedfilestream.hpp>
#include <components/misc/endianness.hpp>
@ -22,31 +24,32 @@ namespace Nif
class NIFFile;
/*
readLittleEndianBufferOfType: This template should only be used with arithmetic types
*/
template <uint32_t numInstances, typename T> inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest)
template <std::size_t numInstances, typename T> inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest)
{
static_assert(std::is_arithmetic_v<T>, "Buffer element type is not arithmetic");
pIStream->read((char*)dest, numInstances * sizeof(T));
if (pIStream->bad())
throw std::runtime_error("Failed to read little endian typed (" + std::string(typeid(T).name()) + ") buffer of "
+ std::to_string(numInstances) + " instances");
if constexpr (Misc::IS_BIG_ENDIAN)
for (uint32_t i = 0; i < numInstances; i++)
for (std::size_t i = 0; i < numInstances; i++)
Misc::swapEndiannessInplace(dest[i]);
}
/*
readLittleEndianDynamicBufferOfType: This template should only be used with arithmetic types
*/
template <typename T> inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances)
template <typename T> inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, std::size_t numInstances)
{
static_assert(std::is_arithmetic_v<T>, "Buffer element type is not arithmetic");
pIStream->read((char*)dest, numInstances * sizeof(T));
if (pIStream->bad())
throw std::runtime_error("Failed to read little endian dynamic buffer of " + std::to_string(numInstances) + " instances");
if constexpr (Misc::IS_BIG_ENDIAN)
for (uint32_t i = 0; i < numInstances; i++)
for (std::size_t i = 0; i < numInstances; i++)
Misc::swapEndiannessInplace(dest[i]);
}
template<typename type> type inline readLittleEndianType(Files::IStreamPtr &pIStream)
{
type val;
readLittleEndianBufferOfType<1, type>(pIStream, (type*)&val);
readLittleEndianBufferOfType<1, type>(pIStream, &val);
return val;
}
@ -96,21 +99,21 @@ public:
osg::Vec2f getVector2()
{
osg::Vec2f vec;
readLittleEndianBufferOfType<2,float>(inp, (float*)&vec._v[0]);
readLittleEndianBufferOfType<2,float>(inp, vec._v);
return vec;
}
osg::Vec3f getVector3()
{
osg::Vec3f vec;
readLittleEndianBufferOfType<3, float>(inp, (float*)&vec._v[0]);
readLittleEndianBufferOfType<3, float>(inp, vec._v);
return vec;
}
osg::Vec4f getVector4()
{
osg::Vec4f vec;
readLittleEndianBufferOfType<4, float>(inp, (float*)&vec._v[0]);
readLittleEndianBufferOfType<4, float>(inp, vec._v);
return vec;
}
@ -142,11 +145,11 @@ public:
///Read in a string of the given length
std::string getSizedString(size_t length)
{
std::vector<char> str(length + 1, 0);
std::string str(length, '\0');
inp->read(str.data(), length);
return str.data();
if (inp->bad())
throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars");
return str;
}
///Read in a string of the length specified in the file
std::string getSizedString()
@ -167,6 +170,8 @@ public:
{
std::string result;
std::getline(*inp, result);
if (inp->bad())
throw std::runtime_error("Failed to read version string");
return result;
}

@ -121,12 +121,14 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
mAvoidStaticMesh.reset();
const size_t numRoots = nif.numRoots();
std::vector<Nif::Node*> roots;
std::vector<const Nif::Node*> roots;
for (size_t i = 0; i < numRoots; ++i)
{
Nif::Record* r = nif.getRoot(i);
assert(r != nullptr);
if (Nif::Node* node = dynamic_cast<Nif::Node*>(r))
const Nif::Record* r = nif.getRoot(i);
if (!r)
continue;
const Nif::Node* node = dynamic_cast<const Nif::Node*>(r);
if (node)
roots.emplace_back(node);
}
const std::string filename = nif.getFilename();

@ -254,8 +254,7 @@ namespace NifOsg
for (size_t i = 0; i < numRoots; ++i)
{
const Nif::Record *r = nif->getRoot(i);
assert(r != nullptr);
if (r->recType == Nif::RC_NiSequenceStreamHelper)
if (r && r->recType == Nif::RC_NiSequenceStreamHelper)
{
seq = static_cast<const Nif::NiSequenceStreamHelper*>(r);
break;
@ -312,7 +311,10 @@ namespace NifOsg
for (size_t i = 0; i < numRoots; ++i)
{
const Nif::Record* r = nif->getRoot(i);
if (const Nif::Node* nifNode = dynamic_cast<const Nif::Node*>(r))
if (!r)
continue;
const Nif::Node* nifNode = dynamic_cast<const Nif::Node*>(r);
if (nifNode)
roots.emplace_back(nifNode);
}
if (roots.empty())

@ -1361,7 +1361,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
if (settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED)
{
cropShadowCameraToMainFrustum(frustum, camera, cascaseNear, cascadeFar, extraPlanes);
for (auto plane : extraPlanes)
for (const auto& plane : extraPlanes)
local_polytope.getPlaneList().push_back(plane);
local_polytope.setupMask();
}
@ -1998,19 +1998,19 @@ struct ConvexHull
Vertices findInternalEdges(osg::Vec3d mainVertex, Vertices connectedVertices)
{
Vertices internalEdgeVertices;
for (auto vertex : connectedVertices)
for (const auto& vertex : connectedVertices)
{
osg::Matrixd matrix;
osg::Vec3d dir = vertex - mainVertex;
matrix.makeLookAt(mainVertex, vertex, dir.z() == 0 ? osg::Vec3d(0, 0, 1) : osg::Vec3d(1, 0, 0));
Vertices testVertices;
for (auto testVertex : connectedVertices)
for (const auto& testVertex : connectedVertices)
{
if (vertex != testVertex)
testVertices.push_back(testVertex);
}
std::vector<double> bearings;
for (auto testVertex : testVertices)
for (const auto& testVertex : testVertices)
{
osg::Vec3d transformedVertex = testVertex * matrix;
bearings.push_back(atan2(transformedVertex.y(), transformedVertex.x()));
@ -2039,7 +2039,7 @@ struct ConvexHull
// Collect the set of vertices
VertexSet vertices;
for (Edge edge : _edges)
for (const Edge& edge : _edges)
{
vertices.insert(edge.first);
vertices.insert(edge.second);
@ -2069,7 +2069,7 @@ struct ConvexHull
for (auto vertex : extremeVertices)
{
Vertices connectedVertices;
for (Edge edge : _edges)
for (const Edge& edge : _edges)
{
if (edge.first == vertex)
connectedVertices.push_back(edge.second);
@ -2107,7 +2107,7 @@ struct ConvexHull
osg::Vec3d vertex = *unprocessedConnectedVertices.begin();
unprocessedConnectedVertices.erase(unprocessedConnectedVertices.begin());
connectedVertices.insert(vertex);
for (Edge edge : _edges)
for (const Edge& edge : _edges)
{
osg::Vec3d otherEnd;
if (edge.first == vertex)
@ -2124,7 +2124,7 @@ struct ConvexHull
}
}
for (Edge edge : _edges)
for (const Edge& edge : _edges)
{
if (connectedVertices.count(edge.first) || connectedVertices.count(edge.second))
finalEdges.push_back(edge);

@ -212,8 +212,8 @@ public:
{
// We arrived at a leaf.
// Since the tree is used for LOD level selection instead of culling, we do not need to load the actual height data here.
float minZ = -std::numeric_limits<float>::max();
float maxZ = std::numeric_limits<float>::max();
constexpr float minZ = -std::numeric_limits<float>::max();
constexpr float maxZ = std::numeric_limits<float>::max();
float cellWorldSize = mStorage->getCellWorldSize();
osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ),
osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ));

@ -55,12 +55,7 @@ namespace VFS
bool FileSystemArchive::contains(const std::string& file, char (*normalize_function)(char)) const
{
for (const auto& it : mIndex)
{
if(it.first == file)
return true;
}
return false;
return mIndex.find(file) != mIndex.end();
}
std::string FileSystemArchive::getDescription() const

@ -2,7 +2,11 @@
Custom Models
#############
Custom models can be imported into OpenMW using a variety of formats. Below is a quick overview of supported formats, followed by separate articles with further look at the pipelines.
Custom models can be imported into OpenMW using a variety of formats. Models for the majority of in-game objects are assigned through openMW-CS :doc:`/manuals/openmw-cs/index`.
Some models, however, are essential for OpenMW to even run. These include player and NPC animations, and meshes for the sky. They are assigned in the ``settings.cfg`` file, with more information available in :doc:`/reference/modding/settings/models` .
Below is a quick overview of supported formats, followed by separate articles with further look at the pipelines.
* **COLLADA** has no license restrictions and is suitable for modding as well as standalone games based on the OpenMW engine. It supports static and animated models. While it doesn't yet work in all parts of the engine, work is being done to resolve the remaining limitations.

@ -29,3 +29,213 @@ If enabled, this setting allows the NIF loader to make use of that functionality
To help debug possible issues OpenMW will log its progress in loading
every file that uses an unsupported NIF version.
xbaseanim
---------
:Type: string
:Range:
:Default: meshes/xbase_anim.nif
Path to the file used for 3rd person base animation model that looks also for the corresponding kf-file.
.. note::
If you are using the COLLADA format, you don't need to separate the files as they are separated between .nif and .kf files. It works if you plug the same COLLADA file into all animation-related entries, just make sure there is a corresponding textkeys file. You can read more about the textkeys in :doc:`../../modding/custom-models/pipeline-blender-collada`.
baseanim
--------
:Type: string
:Range:
:Default: meshes/base_anim.nif
Path to the file used for 3rd person base model with textkeys-data.
xbaseanim1st
------------
:Type: string
:Range:
:Default: meshes/xbase_anim.1st.nif
Path to the file used for 1st person base animation model that looks also for corresponding kf-file.
baseanimkna
-----------
:Type: string
:Range:
:Default: meshes/base_animkna.nif
Path to the file used for 3rd person beast race base model with textkeys-data.
baseanimkna1st
--------------
:Type: string
:Range:
:Default: meshes/base_animkna.1st.nif
Path to the file used for 1st person beast race base animation model.
xbaseanimfemale
---------------
:Type: string
:Range:
:Default: meshes/xbase_anim_female.nif
Path to the file used for 3rd person female base animation model.
baseanimfemale
--------------
:Type: string
:Range:
:Default: meshes/base_anim_female.nif
Path to the file used for 3rd person female base model with textkeys-data.
baseanimfemale1st
-----------------
:Type: string
:Range:
:Default: meshes/base_anim_female.1st.nif
Path to the file used for 1st person female base model with textkeys-data.
wolfskin
--------
:Type: string
:Range:
:Default: meshes/wolf/skin.nif
Path to the file used for 3rd person werewolf skin.
wolfskin1st
-----------
:Type: string
:Range:
:Default: meshes/wolf/skin.1st.nif
Path to the file used for 1st person werewolf skin.
xargonianswimkna
----------------
:Type: string
:Range:
:Default: meshes/xargonian_swimkna.nif
Path to the file used for Argonian swimkna.
xbaseanimkf
-----------
:Type: string
:Range:
:Default: meshes/xbase_anim.kf
File to load xbaseanim 3rd person animations.
xbaseanim1stkf
--------------
:Type: string
:Range:
:Default: meshes/xbase_anim.1st.kf
File to load xbaseanim 3rd person animations.
xbaseanimfemalekf
-----------------
:Type: string
:Range:
:Default: meshes/xbase_anim_female.kf
File to load xbaseanim animations from.
xargonianswimknakf
------------------
:Type: string
:Range:
:Default: meshes/xargonian_swimkna.kf
File to load xargonianswimkna animations from.
skyatmosphere
-------------
:Type: string
:Range:
:Default: meshes/sky_atmosphere.nif
Path to the file used for the sky atmosphere mesh, which is one of the three meshes needed to render the sky. It's used to make the top half of the sky blue and renders in front of the background color.
skyclouds
---------
:Type: string
:Range:
:Default: meshes/sky_clouds_01.nif.
Path to the file used for the sky clouds mesh, which is one of the three meshes needed to render the sky. It displays a scrolling texture of clouds in front of the atmosphere mesh and background color
skynight01
----------
:Type: string
:Range:
:Default: meshes/sky_night_01.nif
Path to the file used for the sky stars mesh, which is one of the three meshes needed to render the sky. During night, it displays a texture with stars in front of the atmosphere and behind the clouds. If skynight02 is present, skynight01 will not be used.
skynight02
----------
:Type: string
:Range:
:Default: meshes/sky_night_02.nif
Path to the file used for the sky stars mesh, which is one of the three meshes needed to render the sky. During night, it displays a texture with stars in front of the atmosphere and behind the clouds. If it's present it will be used instead of skynight01.
weatherashcloud
---------------
:Type: string
:Range:
:Default: meshes/ashcloud.nif
Path to the file used for the ash clouds weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect.
weatherblightcloud
------------------
:Type: string
:Range:
:Default: meshes/blightcloud.nif
Path to the file used for the blight clouds weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect.
weathersnow
-----------
:Type: string
:Range:
:Default: meshes/snow.nif
Path to the file used for the snow falling weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect.
weatherblizzard
---------------
:Type: string
:Range:
:Default: meshes/blizzard.nif
Path to the file used for the blizzard clouds weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect.

@ -231,15 +231,6 @@ max smooth path size
Maximum size of smoothed path.
triangles per chunk
-------------------
:Type: integer
:Range: > 0
:Default: 256
Maximum number of triangles in each node of mesh AABB tree.
Expert Recastnavigation related settings
****************************************

@ -28,7 +28,7 @@ if(NOT OPENMW_USE_SYSTEM_BULLET)
set(BUILD_CPU_DEMOS OFF CACHE BOOL "")
set(BUILD_EGL OFF CACHE BOOL "")
set(USE_DOUBLE_PRECISION ${BULLET_USE_DOUBLES} CACHE BOOL "")
set(USE_DOUBLE_PRECISION ON CACHE BOOL "")
set(BULLET2_MULTITHREADING ON CACHE BOOL "")
if(BULLET_STATIC)

@ -875,9 +875,6 @@ max polygon path size = 1024
# Maximum size of smoothed path (value > 0)
max smooth path size = 1024
# Maximum number of triangles in each node of mesh AABB tree (value > 0)
triangles per chunk = 256
# Write recast mesh to file in .obj format for each use to update nav mesh (true, false)
enable write recast mesh to file = false

@ -56,8 +56,6 @@ void main()
#if @normalMap
vec4 normalTex = texture2D(normalMap, normalMapUV);
// Must flip Y for DirectX format normal maps
normalTex.y = 1.0 - normalTex.y;
vec3 normalizedNormal = normalize(passNormal);
vec3 normalizedTangent = normalize(passTangent.xyz);

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>617</width>
<width>732</width>
<height>487</height>
</rect>
</property>
@ -273,6 +273,30 @@
<layout class="QVBoxLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="1">
<widget class="QCheckBox" name="radialFogCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen.
This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Radial fog</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="bumpMapLocalLightingCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark.
Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option.
Affected objects will use shaders.
&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Bump/reflect map local lighting</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="turnToMovementDirectionCheckBox">
<property name="toolTip">
@ -283,6 +307,16 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="animSourcesCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Load per-group KF-files and skeleton files from Animations folder&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use additional animation sources</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="lightingMethodLayout">
<item>
@ -297,7 +331,7 @@
<item>
<property name="text">
<string>legacy</string>
</property>
</property>
</item>
<item>
<property name="text">
@ -313,16 +347,6 @@
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="smoothMovementCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Smooth movement</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="distantLandCheckBox">
<property name="toolTip">
@ -333,26 +357,6 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="autoUseTerrainNormalMapsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;See 'auto use object normal maps'. Affects terrain.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Auto use terrain normal maps</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="autoUseTerrainSpecularMapsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Auto use terrain specular maps</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="activeGridObjectPagingCheckBox">
<property name="toolTip">
@ -363,51 +367,6 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="magicItemAnimationsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use casting animations for magic items, just as for spells.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use magic item animation</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="animSourcesCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Load per-group KF-files and skeleton files from Animations folder&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use additional animation sources</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="autoUseObjectNormalMapsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this option is enabled, normal maps are automatically recognized and used if they are named appropriately
(see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds).
If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Auto use object normal maps</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="bumpMapLocalLightingCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark.
Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option.
Affected objects will use shaders.
&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Bump/reflect map local lighting</string>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="viewingDistanceLayout">
<item>
@ -432,6 +391,16 @@ Affected objects will use shaders.
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="autoUseTerrainNormalMapsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;See 'auto use object normal maps'. Affects terrain.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Auto use terrain normal maps</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="autoUseObjectSpecularMapsCheckBox">
<property name="toolTip">
@ -446,50 +415,81 @@ If this option is disabled, normal maps are only used if they are explicitly lis
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="radialFogCheckBox">
<item row="4" column="0">
<widget class="QCheckBox" name="autoUseTerrainSpecularMapsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen.
This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Radial fog</string>
<string>Auto use terrain specular maps</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="sheathingLayout">
<property name="leftMargin">
<number>20</number>
</property>
<item>
<widget class="QCheckBox" name="weaponSheathingCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<item row="5" column="0">
<widget class="QCheckBox" name="smoothMovementCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Render holstered weapons (with quivers and scabbards), requires modded assets.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes NPCs and player movement more smooth. Recommended to use with &quot;turn to movement direction&quot; enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Weapon sheathing</string>
<string>Smooth movement</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="shieldSheathingCheckBox">
<property name="enabled">
<bool>false</bool>
<item row="2" column="1">
<widget class="QCheckBox" name="magicItemAnimationsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use casting animations for magic items, just as for spells.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use magic item animation</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="autoUseObjectNormalMapsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Render holstered shield, requires modded assets.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this option is enabled, normal maps are automatically recognized and used if they are named appropriately
(see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds).
If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Shield sheathing</string>
<string>Auto use object normal maps</string>
</property>
</widget>
</item>
<item row="7" column="0">
<layout class="QVBoxLayout" name="sheathingLayout">
<property name="leftMargin">
<number>20</number>
</property>
<item>
<widget class="QCheckBox" name="weaponSheathingCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Render holstered weapons (with quivers and scabbards), requires modded assets.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Weapon sheathing</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="shieldSheathingCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Render holstered shield, requires modded assets.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Shield sheathing</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
@ -516,12 +516,12 @@ This setting makes the fog use the actual eye point distance (or so called Eucli
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="audioDeviceSelectorLabel">
<property name="text">
<string>Audio Device</string>
</property>
<property name="toolTip">
<string>Select your preferred audio device.</string>
</property>
<property name="text">
<string>Audio Device</string>
</property>
</widget>
</item>
<item>
@ -554,12 +554,12 @@ This setting makes the fog use the actual eye point distance (or so called Eucli
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="enableHRTFLabel">
<property name="text">
<string>HRTF</string>
</property>
<property name="toolTip">
<string>This setting controls HRTF, which simulates 3D sound on stereo systems.</string>
</property>
<property name="text">
<string>HRTF</string>
</property>
</widget>
</item>
<item>
@ -602,12 +602,12 @@ This setting makes the fog use the actual eye point distance (or so called Eucli
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="hrtfProfileSelectorLabel">
<property name="text">
<string>HRTF Profile</string>
</property>
<property name="toolTip">
<string>Select your preferred HRTF profile.</string>
</property>
<property name="text">
<string>HRTF Profile</string>
</property>
</widget>
</item>
<item>

Loading…
Cancel
Save