Add OpenMW commits up to 6 May 2021

# Conflicts:
#   CMakeLists.txt
#   components/CMakeLists.txt
This commit is contained in:
David Cernat 2021-05-06 23:32:48 +02:00
commit ea6d5c68ae
83 changed files with 551 additions and 782 deletions

View File

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

View File

@ -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 #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 #5758: Paralyzed actors behavior is inconsistent with vanilla
Bug #5762: Movement solver is insufficiently robust 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 #5807: Video decoding crash on ARM
Bug #5821: NPCs from mods getting removed if mod order was changed 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 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 #5923: Clicking on empty spaces between journal entries might show random topics
Bug #5934: AddItem command doesn't accept negative values Bug #5934: AddItem command doesn't accept negative values
Bug #5975: NIF controllers from sheath meshes are used 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 Bug #5995: NiUVController doesn't calculate the UV offset properly
Feature #390: 3rd person look "over the shoulder" Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references Feature #832: OpenMW-CS: Handle deleted references

View File

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

View File

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

View File

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

View File

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

View File

@ -13,9 +13,6 @@ if(POLICY CMP0083)
cmake_policy(SET CMP0083 NEW) cmake_policy(SET CMP0083 NEW)
endif() 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) 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) if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_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_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" 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_OPENMW_MP "Build OpenMW-MP" ON)
option(BUILD_BROWSER "Build tes3mp Server Browser" ON) option(BUILD_BROWSER "Build tes3mp Server Browser" ON)
option(BUILD_MASTER "Build tes3mp Master Server" OFF) option(BUILD_MASTER "Build tes3mp Master Server" OFF)
@ -286,16 +282,16 @@ if(FFmpeg_FOUND)
set(FFVER_OK FALSE) set(FFVER_OK FALSE)
endif() endif()
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() endif()
if(NOT FFmpeg_FOUND) if(NOT FFmpeg_FOUND)
message(FATAL_ERROR "FFmpeg was not found" ) message(FATAL_ERROR "FFmpeg was not found" )
endif() endif()
if(NOT FFVER_OK)
message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" )
endif()
if(WIN32) if(WIN32)
message("Can not detect FFmpeg version, at least the 3.2 is required" ) message("Can not detect FFmpeg version, at least the 3.2 is required" )
endif() endif()
@ -339,9 +335,33 @@ if(OPENMW_USE_SYSTEM_BULLET)
set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine
endif() endif()
# 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) find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath)
endif() 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 if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer
find_package(LIBUNSHIELD REQUIRED) # required only for non win32 when building openmw-wizard find_package(LIBUNSHIELD REQUIRED) # required only for non win32 when building openmw-wizard
set(OPENMW_USE_UNSHIELD TRUE) set(OPENMW_USE_UNSHIELD TRUE)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -158,7 +158,7 @@ void Actor::updatePosition()
mSimulationPosition = mWorldPosition; mSimulationPosition = mWorldPosition;
mPositionOffset = osg::Vec3f(); mPositionOffset = osg::Vec3f();
mStandingOnPtr = nullptr; mStandingOnPtr = nullptr;
mSkipSimulation = true; mSkipCollisions = true;
} }
void Actor::updateWorldPosition() void Actor::updateWorldPosition()
@ -175,9 +175,7 @@ osg::Vec3f Actor::getWorldPosition() const
void Actor::setSimulationPosition(const osg::Vec3f& position) void Actor::setSimulationPosition(const osg::Vec3f& position)
{ {
if (!mSkipSimulation)
mSimulationPosition = position; mSimulationPosition = position;
mSkipSimulation = false;
} }
osg::Vec3f Actor::getSimulationPosition() const osg::Vec3f Actor::getSimulationPosition() const
@ -211,21 +209,19 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
bool Actor::setPosition(const osg::Vec3f& position) bool Actor::setPosition(const osg::Vec3f& position)
{ {
std::scoped_lock lock(mPositionMutex); 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(); updateWorldPosition();
applyOffsetChange(); applyOffsetChange();
bool hasChanged = mPosition != position || mWorldPositionChanged;
mPreviousPosition = mPosition; mPreviousPosition = mPosition;
mPosition = position; mPosition = position;
return hasChanged; return hasChanged;
} }
void Actor::adjustPosition(const osg::Vec3f& offset) void Actor::adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions)
{ {
std::scoped_lock lock(mPositionMutex); std::scoped_lock lock(mPositionMutex);
mPositionOffset += offset; mPositionOffset += offset;
mSkipCollisions = mSkipCollisions || ignoreCollisions;
} }
void Actor::applyOffsetChange() void Actor::applyOffsetChange()
@ -337,4 +333,9 @@ void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr)
mStandingOnPtr = ptr; mStandingOnPtr = ptr;
} }
bool Actor::skipCollisions()
{
return std::exchange(mSkipCollisions, false);
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -91,14 +91,30 @@ namespace MWWorld
// move all slots one towards begin(), then equip the item in the slot that is now free // move all slots one towards begin(), then equip the item in the slot that is now free
if (slot == slots_.first.end()) if (slot == slots_.first.end())
{ {
ContainerStoreIterator enchItem = invStore.getSelectedEnchantItem();
bool reEquip = false;
for (slot = slots_.first.begin(); slot != slots_.first.end(); ++slot) for (slot = slots_.first.begin(); slot != slots_.first.end(); ++slot)
{ {
invStore.unequipSlot(*slot, actor, false); invStore.unequipSlot(*slot, actor, false);
if (slot + 1 != slots_.first.end()) if (slot + 1 != slots_.first.end())
{
invStore.equip(*slot, invStore.getSlot(*(slot + 1)), actor); invStore.equip(*slot, invStore.getSlot(*(slot + 1)), actor);
}
else else
{
invStore.equip(*slot, it, actor); 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);
}
} }
} }
} }

View File

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

View File

@ -8,28 +8,11 @@
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <iterator>
#include <stdexcept> #include <stdexcept>
namespace 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 struct Compare
{ {
bool operator()(const ESM::Land *x, const ESM::Land *y) { 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 const T *Store<T>::searchRandom(const std::string &id) const
{ {
std::vector<const T*> results; 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()) if(!results.empty())
return results[Misc::Rng::rollDice(results.size())]; return results[Misc::Rng::rollDice(results.size())];
return nullptr; return nullptr;
@ -186,17 +173,6 @@ namespace MWWorld
return ptr; return ptr;
} }
template<typename T> 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) RecordId Store<T>::load(ESM::ESMReader &esm)
{ {
T record; T record;

View File

@ -179,10 +179,6 @@ namespace MWWorld
const T *find(const std::string &id) const; 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 begin() const;
iterator end() const; iterator end() const;

View File

@ -1441,12 +1441,12 @@ namespace MWWorld
return moveObject(ptr, cell, x, y, z, movePhysics); 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); auto* actor = mPhysics->getActor(ptr);
osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec;
if (actor) if (actor)
actor->adjustPosition(vec); actor->adjustPosition(vec, ignoreCollisions);
if (ptr.getClass().isActor()) 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(), false, moveToActive && ptr != getPlayerPtr());
return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); return moveObject(ptr, newpos.x(), newpos.y(), newpos.z());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -349,11 +349,7 @@ if (BUILD_OPENMW OR BUILD_OPENCS)
endif () endif ()
# End of tes3mp change (major) # 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}) target_link_libraries(components ${BULLET_LIBRARIES})
endif()
if (WIN32) if (WIN32)
target_link_libraries(components target_link_libraries(components
@ -387,6 +383,4 @@ endif()
# Make the variable accessible for other subdirectories # Make the variable accessible for other subdirectories
set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE)
if (BULLET_USE_DOUBLES)
target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION)
endif()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H
#include "areatype.hpp" #include "areatype.hpp"
#include "chunkytrimesh.hpp"
#include "bounds.hpp" #include "bounds.hpp"
#include <components/bullethelpers/operators.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, 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 std::size_t getGeneration() const
{ {
@ -70,11 +69,6 @@ namespace DetourNavigator
return mIndices.size() / 3; return mIndices.size() / 3;
} }
const ChunkyTriMesh& getChunkyTriMesh() const
{
return mChunkyTriMesh;
}
const Bounds& getBounds() const const Bounds& getBounds() const
{ {
return mBounds; return mBounds;
@ -87,7 +81,6 @@ namespace DetourNavigator
std::vector<float> mVertices; std::vector<float> mVertices;
std::vector<AreaType> mAreaTypes; std::vector<AreaType> mAreaTypes;
std::vector<Water> mWater; std::vector<Water> mWater;
ChunkyTriMesh mChunkyTriMesh;
Bounds mBounds; Bounds mBounds;
}; };

View File

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

View File

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

View File

@ -33,7 +33,6 @@ namespace DetourNavigator
navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast<std::size_t>(::Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); 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.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.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.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator");
navigatorSettings.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav 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"); navigatorSettings.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator");

View File

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

View File

@ -161,9 +161,9 @@ namespace ESM
{ {
// Generate WNAM record // Generate WNAM record
signed char wnam[LAND_GLOBAL_MAP_LOD_SIZE]; signed char wnam[LAND_GLOBAL_MAP_LOD_SIZE];
float max = std::numeric_limits<signed char>::max(); constexpr float max = std::numeric_limits<signed char>::max();
float min = std::numeric_limits<signed char>::min(); constexpr 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 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 row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row)
{ {
for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col)

View File

@ -212,7 +212,7 @@ void NIFFile::parse(Files::IStreamPtr stream)
userVer = nif.getUInt(); userVer = nif.getUInt();
// Number of records // Number of records
unsigned int recNum = nif.getUInt(); const std::size_t recNum = nif.getUInt();
records.resize(recNum); records.resize(recNum);
// Bethesda stream header // Bethesda stream header
@ -251,7 +251,7 @@ void NIFFile::parse(Files::IStreamPtr stream)
std::vector<unsigned int> recSizes; // Currently unused std::vector<unsigned int> recSizes; // Currently unused
nif.getUInts(recSizes, recNum); nif.getUInts(recSizes, recNum);
} }
unsigned int stringNum = nif.getUInt(); const std::size_t stringNum = nif.getUInt();
nif.getUInt(); // Max string length nif.getUInt(); // Max string length
if (stringNum) if (stringNum)
nif.getSizedStrings(strings, 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); 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; Record *r = nullptr;
@ -308,21 +308,21 @@ void NIFFile::parse(Files::IStreamPtr stream)
r->read(&nif); r->read(&nif);
} }
unsigned int rootNum = nif.getUInt(); const std::size_t rootNum = nif.getUInt();
roots.resize(rootNum); roots.resize(rootNum);
//Determine which records are roots //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(); 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]; roots[i] = records[idx];
} }
else else
{ {
roots[i] = nullptr; 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));
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -212,8 +212,8 @@ public:
{ {
// We arrived at a leaf. // 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. // 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(); constexpr float minZ = -std::numeric_limits<float>::max();
float maxZ = std::numeric_limits<float>::max(); constexpr float maxZ = std::numeric_limits<float>::max();
float cellWorldSize = mStorage->getCellWorldSize(); float cellWorldSize = mStorage->getCellWorldSize();
osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ), 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)); osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ));

View File

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

View File

@ -2,7 +2,11 @@
Custom Models 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. * **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.

View File

@ -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 To help debug possible issues OpenMW will log its progress in loading
every file that uses an unsupported NIF version. 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.

View File

@ -231,15 +231,6 @@ max smooth path size
Maximum size of smoothed path. 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 Expert Recastnavigation related settings
**************************************** ****************************************

View File

@ -28,7 +28,7 @@ if(NOT OPENMW_USE_SYSTEM_BULLET)
set(BUILD_CPU_DEMOS OFF CACHE BOOL "") set(BUILD_CPU_DEMOS OFF CACHE BOOL "")
set(BUILD_EGL 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 "") set(BULLET2_MULTITHREADING ON CACHE BOOL "")
if(BULLET_STATIC) if(BULLET_STATIC)

View File

@ -875,9 +875,6 @@ max polygon path size = 1024
# Maximum size of smoothed path (value > 0) # Maximum size of smoothed path (value > 0)
max smooth path size = 1024 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) # Write recast mesh to file in .obj format for each use to update nav mesh (true, false)
enable write recast mesh to file = false enable write recast mesh to file = false

View File

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

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>617</width> <width>732</width>
<height>487</height> <height>487</height>
</rect> </rect>
</property> </property>
@ -273,6 +273,30 @@
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<layout class="QGridLayout" name="gridLayout_2"> <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"> <item row="5" column="1">
<widget class="QCheckBox" name="turnToMovementDirectionCheckBox"> <widget class="QCheckBox" name="turnToMovementDirectionCheckBox">
<property name="toolTip"> <property name="toolTip">
@ -283,6 +307,16 @@
</property> </property>
</widget> </widget>
</item> </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"> <item row="6" column="1">
<layout class="QHBoxLayout" name="lightingMethodLayout"> <layout class="QHBoxLayout" name="lightingMethodLayout">
<item> <item>
@ -313,16 +347,6 @@
</item> </item>
</layout> </layout>
</item> </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"> <item row="0" column="1">
<widget class="QCheckBox" name="distantLandCheckBox"> <widget class="QCheckBox" name="distantLandCheckBox">
<property name="toolTip"> <property name="toolTip">
@ -333,26 +357,6 @@
</property> </property>
</widget> </widget>
</item> </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"> <item row="1" column="1">
<widget class="QCheckBox" name="activeGridObjectPagingCheckBox"> <widget class="QCheckBox" name="activeGridObjectPagingCheckBox">
<property name="toolTip"> <property name="toolTip">
@ -363,51 +367,6 @@
</property> </property>
</widget> </widget>
</item> </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"> <item row="0" column="0">
<layout class="QHBoxLayout" name="viewingDistanceLayout"> <layout class="QHBoxLayout" name="viewingDistanceLayout">
<item> <item>
@ -432,6 +391,16 @@ Affected objects will use shaders.
</item> </item>
</layout> </layout>
</item> </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"> <item row="2" column="0">
<widget class="QCheckBox" name="autoUseObjectSpecularMapsCheckBox"> <widget class="QCheckBox" name="autoUseObjectSpecularMapsCheckBox">
<property name="toolTip"> <property name="toolTip">
@ -446,20 +415,49 @@ If this option is disabled, normal maps are only used if they are explicitly lis
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="4" column="0">
<widget class="QCheckBox" name="radialFogCheckBox"> <widget class="QCheckBox" name="autoUseTerrainSpecularMapsCheckBox">
<property name="toolTip"> <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. <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>
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>
<property name="text"> <property name="text">
<string>Radial fog</string> <string>Auto use terrain specular maps</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> <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 &quot;turn to movement direction&quot; enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Smooth movement</string>
</property>
</widget>
</item> </item>
<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="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="7" column="0">
<layout class="QVBoxLayout" name="sheathingLayout"> <layout class="QVBoxLayout" name="sheathingLayout">
<property name="leftMargin"> <property name="leftMargin">
<number>20</number> <number>20</number>
@ -492,6 +490,8 @@ This setting makes the fog use the actual eye point distance (or so called Eucli
</item> </item>
</layout> </layout>
</item> </item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer_4"> <spacer name="verticalSpacer_4">
<property name="orientation"> <property name="orientation">
@ -516,12 +516,12 @@ This setting makes the fog use the actual eye point distance (or so called Eucli
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="audioDeviceSelectorLabel"> <widget class="QLabel" name="audioDeviceSelectorLabel">
<property name="text">
<string>Audio Device</string>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Select your preferred audio device.</string> <string>Select your preferred audio device.</string>
</property> </property>
<property name="text">
<string>Audio Device</string>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -554,12 +554,12 @@ This setting makes the fog use the actual eye point distance (or so called Eucli
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="enableHRTFLabel"> <widget class="QLabel" name="enableHRTFLabel">
<property name="text">
<string>HRTF</string>
</property>
<property name="toolTip"> <property name="toolTip">
<string>This setting controls HRTF, which simulates 3D sound on stereo systems.</string> <string>This setting controls HRTF, which simulates 3D sound on stereo systems.</string>
</property> </property>
<property name="text">
<string>HRTF</string>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -602,12 +602,12 @@ This setting makes the fog use the actual eye point distance (or so called Eucli
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="hrtfProfileSelectorLabel"> <widget class="QLabel" name="hrtfProfileSelectorLabel">
<property name="text">
<string>HRTF Profile</string>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Select your preferred HRTF profile.</string> <string>Select your preferred HRTF profile.</string>
</property> </property>
<property name="text">
<string>HRTF Profile</string>
</property>
</widget> </widget>
</item> </item>
<item> <item>