mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-03-29 16:06:44 +00:00
Merge remote-tracking branch 'remotes/origin/master' into openmw-vr
This commit is contained in:
commit
6c9df35725
219 changed files with 4641 additions and 1910 deletions
28
CHANGELOG.md
28
CHANGELOG.md
|
@ -3,12 +3,14 @@
|
||||||
|
|
||||||
Bug #832: OpenMW-CS: Handle deleted references
|
Bug #832: OpenMW-CS: Handle deleted references
|
||||||
Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path
|
Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path
|
||||||
|
Bug #1901: Actors colliding behaviour is different from vanilla
|
||||||
Bug #1952: Incorrect particle lighting
|
Bug #1952: Incorrect particle lighting
|
||||||
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
|
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
|
||||||
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
|
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
|
||||||
Bug #2473: Unable to overstock merchants
|
Bug #2473: Unable to overstock merchants
|
||||||
Bug #2798: Mutable ESM records
|
Bug #2798: Mutable ESM records
|
||||||
Bug #2976 [reopened]: Issues combining settings from the command line and both config files
|
Bug #2976 [reopened]: Issues combining settings from the command line and both config files
|
||||||
|
Bug #3137: Walking into a wall prevents jumping
|
||||||
Bug #3372: Projectiles and magic bolts go through moving targets
|
Bug #3372: Projectiles and magic bolts go through moving targets
|
||||||
Bug #3676: NiParticleColorModifier isn't applied properly
|
Bug #3676: NiParticleColorModifier isn't applied properly
|
||||||
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
|
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
|
||||||
|
@ -16,15 +18,26 @@
|
||||||
Bug #3862: Random container contents behave differently than vanilla
|
Bug #3862: Random container contents behave differently than vanilla
|
||||||
Bug #3929: Leveled list merchant containers respawn on barter
|
Bug #3929: Leveled list merchant containers respawn on barter
|
||||||
Bug #4021: Attributes and skills are not stored as floats
|
Bug #4021: Attributes and skills are not stored as floats
|
||||||
|
Bug #4039: Multiple followers should have the same following distance
|
||||||
Bug #4055: Local scripts don't inherit variables from their base record
|
Bug #4055: Local scripts don't inherit variables from their base record
|
||||||
Bug #4083: Door animation freezes when colliding with actors
|
Bug #4083: Door animation freezes when colliding with actors
|
||||||
|
Bug #4201: Projectile-projectile collision
|
||||||
|
Bug #4247: Cannot walk up stairs in Ebonheart docks
|
||||||
|
Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional
|
||||||
|
Bug #4363: Editor: Defect in Clone Function for Dialogue Info records
|
||||||
|
Bug #4447: Actor collision capsule shape allows looking through some walls
|
||||||
|
Bug #4465: Collision shape overlapping causes twitching
|
||||||
|
Bug #4476: Abot Gondoliers: player hangs in air during scenic travel
|
||||||
|
Bug #4568: Too many actors in one spot can push other actors out of bounds
|
||||||
Bug #4623: Corprus implementation is incorrect
|
Bug #4623: Corprus implementation is incorrect
|
||||||
Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level
|
Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level
|
||||||
Bug #4764: Data race in osg ParticleSystem
|
Bug #4764: Data race in osg ParticleSystem
|
||||||
|
Bug #4765: Data race in ChunkManager -> Array::setBinding
|
||||||
Bug #4774: Guards are ignorant of an invisible player that tries to attack them
|
Bug #4774: Guards are ignorant of an invisible player that tries to attack them
|
||||||
Bug #5101: Hostile followers travel with the player
|
Bug #5101: Hostile followers travel with the player
|
||||||
Bug #5108: Savegame bloating due to inefficient fog textures format
|
Bug #5108: Savegame bloating due to inefficient fog textures format
|
||||||
Bug #5165: Active spells should use real time intead of timestamps
|
Bug #5165: Active spells should use real time intead of timestamps
|
||||||
|
Bug #5300: NPCs don't switch from torch to shield when starting combat
|
||||||
Bug #5358: ForceGreeting always resets the dialogue window completely
|
Bug #5358: ForceGreeting always resets the dialogue window completely
|
||||||
Bug #5363: Enchantment autocalc not always 0/1
|
Bug #5363: Enchantment autocalc not always 0/1
|
||||||
Bug #5364: Script fails/stops if trying to startscript an unknown script
|
Bug #5364: Script fails/stops if trying to startscript an unknown script
|
||||||
|
@ -33,12 +46,14 @@
|
||||||
Bug #5370: Opening an unlocked but trapped door uses the key
|
Bug #5370: Opening an unlocked but trapped door uses the key
|
||||||
Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor
|
Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor
|
||||||
Bug #5387: Move/MoveWorld don't update the object's cell properly
|
Bug #5387: Move/MoveWorld don't update the object's cell properly
|
||||||
|
Bug #5391: Races Redone 1.2 bodies don't show on the inventory
|
||||||
Bug #5397: NPC greeting does not reset if you leave + reenter area
|
Bug #5397: NPC greeting does not reset if you leave + reenter area
|
||||||
Bug #5400: Editor: Verifier checks race of non-skin bodyparts
|
Bug #5400: Editor: Verifier checks race of non-skin bodyparts
|
||||||
Bug #5403: Enchantment effect doesn't show on an enemy during death animation
|
Bug #5403: Enchantment effect doesn't show on an enemy during death animation
|
||||||
Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work
|
Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work
|
||||||
Bug #5416: Junk non-node records before the root node are not handled gracefully
|
Bug #5416: Junk non-node records before the root node are not handled gracefully
|
||||||
Bug #5422: The player loses all spells when resurrected
|
Bug #5422: The player loses all spells when resurrected
|
||||||
|
Bug #5423: Guar follows actors too closely
|
||||||
Bug #5424: Creatures do not headtrack player
|
Bug #5424: Creatures do not headtrack player
|
||||||
Bug #5425: Poison effect only appears for one frame
|
Bug #5425: Poison effect only appears for one frame
|
||||||
Bug #5427: GetDistance unknown ID error is misleading
|
Bug #5427: GetDistance unknown ID error is misleading
|
||||||
|
@ -46,6 +61,7 @@
|
||||||
Bug #5441: Enemies can't push a player character when in critical strike stance
|
Bug #5441: Enemies can't push a player character when in critical strike stance
|
||||||
Bug #5451: Magic projectiles don't disappear with the caster
|
Bug #5451: Magic projectiles don't disappear with the caster
|
||||||
Bug #5452: Autowalk is being included in savegames
|
Bug #5452: Autowalk is being included in savegames
|
||||||
|
Bug #5469: Local map is reset when re-entering certain cells
|
||||||
Bug #5472: Mistify mod causes CTD in 0.46 on Mac
|
Bug #5472: Mistify mod causes CTD in 0.46 on Mac
|
||||||
Bug #5479: NPCs who should be walking around town are standing around without walking
|
Bug #5479: NPCs who should be walking around town are standing around without walking
|
||||||
Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold
|
Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold
|
||||||
|
@ -63,21 +79,31 @@
|
||||||
Bug #5604: Only one valid NIF root node is loaded from a single file
|
Bug #5604: Only one valid NIF root node is loaded from a single file
|
||||||
Bug #5611: Usable items with "0 Uses" should be used only once
|
Bug #5611: Usable items with "0 Uses" should be used only once
|
||||||
Bug #5622: Can't properly interact with the console when in pause menu
|
Bug #5622: Can't properly interact with the console when in pause menu
|
||||||
|
Bug #5627: Bookart not shown if it isn't followed by <BR> statement
|
||||||
Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them
|
Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them
|
||||||
Bug #5639: Tooltips cover Messageboxes
|
Bug #5639: Tooltips cover Messageboxes
|
||||||
Bug #5644: Summon effects running on the player during game initialization cause crashes
|
Bug #5644: Summon effects running on the player during game initialization cause crashes
|
||||||
Bug #5656: Sneaking characters block hits while standing
|
Bug #5656: Sneaking characters block hits while standing
|
||||||
Bug #5661: Region sounds don't play at the right interval
|
Bug #5661: Region sounds don't play at the right interval
|
||||||
Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx
|
Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx
|
||||||
|
Bug #5681: Player character can clip or pass through bridges instead of colliding against them
|
||||||
|
Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game
|
||||||
Bug #5688: Water shader broken indoors with enable indoor shadows = false
|
Bug #5688: Water shader broken indoors with enable indoor shadows = false
|
||||||
Bug #5695: ExplodeSpell for actors doesn't target the ground
|
Bug #5695: ExplodeSpell for actors doesn't target the ground
|
||||||
Bug #5703: OpenMW-CS menu system crashing on XFCE
|
Bug #5703: OpenMW-CS menu system crashing on XFCE
|
||||||
|
Bug #5706: AI sequences stop looping after the saved game is reloaded
|
||||||
|
Bug #5713: OpenMW-CS: Collada models are corrupted in Qt-based scene view
|
||||||
Bug #5731: Editor: skirts are invisible on characters
|
Bug #5731: Editor: skirts are invisible on characters
|
||||||
|
Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage
|
||||||
|
Bug #5758: Paralyzed actors behavior is inconsistent with vanilla
|
||||||
|
Bug #5762: Movement solver is insufficiently robust
|
||||||
|
Bug #5821: NPCs from mods getting removed if mod order was changed
|
||||||
Feature #390: 3rd person look "over the shoulder"
|
Feature #390: 3rd person look "over the shoulder"
|
||||||
Feature #1536: Show more information about level on menu
|
Feature #1536: Show more information about level on menu
|
||||||
Feature #2386: Distant Statics in the form of Object Paging
|
Feature #2386: Distant Statics in the form of Object Paging
|
||||||
Feature #2404: Levelled List can not be placed into a container
|
Feature #2404: Levelled List can not be placed into a container
|
||||||
Feature #2686: Timestamps in openmw.log
|
Feature #2686: Timestamps in openmw.log
|
||||||
|
Feature #3171: OpenMW-CS: Instance drag selection
|
||||||
Feature #4894: Consider actors as obstacles for pathfinding
|
Feature #4894: Consider actors as obstacles for pathfinding
|
||||||
Feature #5043: Head Bobbing
|
Feature #5043: Head Bobbing
|
||||||
Feature #5199: Improve Scene Colors
|
Feature #5199: Improve Scene Colors
|
||||||
|
@ -100,6 +126,8 @@
|
||||||
Feature #5672: Make stretch menu background configuration more accessible
|
Feature #5672: Make stretch menu background configuration more accessible
|
||||||
Feature #5692: Improve spell/magic item search to factor in magic effect names
|
Feature #5692: Improve spell/magic item search to factor in magic effect names
|
||||||
Feature #5730: Add graphic herbalism option to the launcher and documents
|
Feature #5730: Add graphic herbalism option to the launcher and documents
|
||||||
|
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
|
||||||
|
Feature #5813: Instanced groundcover support
|
||||||
Task #5480: Drop Qt4 support
|
Task #5480: Drop Qt4 support
|
||||||
Task #5520: Improve cell name autocompleter implementation
|
Task #5520: Improve cell name autocompleter implementation
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ New Features:
|
||||||
- Basics of Collada animations are now supported via osgAnimation plugin (#5456)
|
- Basics of Collada animations are now supported via osgAnimation plugin (#5456)
|
||||||
|
|
||||||
New Editor Features:
|
New Editor Features:
|
||||||
- ?
|
- Instance selection modes are now implemented (centred cube, corner-dragged cube, sphere) with four user-configurable actions (select only, add to selection, remove from selection, invert selection) (#3171)
|
||||||
|
|
||||||
Bug Fixes:
|
Bug Fixes:
|
||||||
- NiParticleColorModifier in NIF files is now properly handled which solves issues regarding particle effects, e.g., smoke and fire (#1952, #3676)
|
- NiParticleColorModifier in NIF files is now properly handled which solves issues regarding particle effects, e.g., smoke and fire (#1952, #3676)
|
||||||
|
@ -35,9 +35,12 @@ Bug Fixes:
|
||||||
|
|
||||||
Editor Bug Fixes:
|
Editor Bug Fixes:
|
||||||
- Deleted and moved objects within a cell are now saved properly (#832)
|
- Deleted and moved objects within a cell are now saved properly (#832)
|
||||||
|
- Disabled record sorting in Topic and Journal Info tables, implemented drag-move for records (#4357)
|
||||||
|
- Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363)
|
||||||
- Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400)
|
- Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400)
|
||||||
- Loading mods now keeps the master index (#5675)
|
- Loading mods now keeps the master index (#5675)
|
||||||
- Flicker and crashing on XFCE4 fixed (#5703)
|
- Flicker and crashing on XFCE4 fixed (#5703)
|
||||||
|
- Collada models render properly in the Editor (#5713)
|
||||||
|
|
||||||
Miscellaneous:
|
Miscellaneous:
|
||||||
- Prevent save-game bloating by using an appropriate fog texture format (#5108)
|
- Prevent save-game bloating by using an appropriate fog texture format (#5108)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh -ex
|
#!/bin/sh -ex
|
||||||
|
|
||||||
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201018.zip -o ~/openmw-android-deps.zip
|
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201129.zip -o ~/openmw-android-deps.zip
|
||||||
unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null
|
unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
#!/bin/sh -e
|
#!/bin/sh -e
|
||||||
|
|
||||||
|
# workaround python issue on travis
|
||||||
|
HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true
|
||||||
|
HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true
|
||||||
|
|
||||||
# Some of these tools can come from places other than brew, so check before installing
|
# Some of these tools can come from places other than brew, so check before installing
|
||||||
command -v ccache >/dev/null 2>&1 || brew install ccache
|
command -v ccache >/dev/null 2>&1 || brew install ccache
|
||||||
command -v cmake >/dev/null 2>&1 || brew install cmake
|
command -v cmake >/dev/null 2>&1 || brew install cmake
|
||||||
|
|
|
@ -73,7 +73,7 @@ CONFIGURATIONS=()
|
||||||
TEST_FRAMEWORK=""
|
TEST_FRAMEWORK=""
|
||||||
GOOGLE_INSTALL_ROOT=""
|
GOOGLE_INSTALL_ROOT=""
|
||||||
INSTALL_PREFIX="."
|
INSTALL_PREFIX="."
|
||||||
BULLET_DOUBLE=""
|
BULLET_DOUBLE=true
|
||||||
BULLET_DBL=""
|
BULLET_DBL=""
|
||||||
BULLET_DBL_DISPLAY="Single precision"
|
BULLET_DBL_DISPLAY="Single precision"
|
||||||
SKIP_VR=""
|
SKIP_VR=""
|
||||||
|
|
|
@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.1.0)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# Detect OS
|
||||||
|
include(cmake/OSIdentity.cmake)
|
||||||
|
|
||||||
# for link time optimization, remove if cmake version is >= 3.9
|
# for link time optimization, remove if cmake version is >= 3.9
|
||||||
if(POLICY CMP0069)
|
if(POLICY CMP0069)
|
||||||
cmake_policy(SET CMP0069 NEW)
|
cmake_policy(SET CMP0069 NEW)
|
||||||
|
@ -22,7 +25,7 @@ option(BUILD_DOCS "Build documentation." OFF )
|
||||||
option(BUILD_OPENMW_VR "Build VR support using OpenXR" ON)
|
option(BUILD_OPENMW_VR "Build VR support using OpenXR" ON)
|
||||||
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(BULLET_USE_DOUBLES "Use double precision for Bullet" OFF)
|
option(BULLET_USE_DOUBLES "Use double precision for Bullet" ON)
|
||||||
|
|
||||||
set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up.
|
set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up.
|
||||||
|
|
||||||
|
@ -324,6 +327,7 @@ include_directories("."
|
||||||
${Boost_INCLUDE_DIR}
|
${Boost_INCLUDE_DIR}
|
||||||
${MyGUI_INCLUDE_DIRS}
|
${MyGUI_INCLUDE_DIRS}
|
||||||
${OPENAL_INCLUDE_DIR}
|
${OPENAL_INCLUDE_DIR}
|
||||||
|
${OPENGL_INCLUDE_DIR}
|
||||||
${BULLET_INCLUDE_DIRS}
|
${BULLET_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,9 @@
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
|
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
|
||||||
Config::GameSettings &gameSettings,
|
|
||||||
Settings::Manager &engineSettings, QWidget *parent)
|
Settings::Manager &engineSettings, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, mCfgMgr(cfg)
|
|
||||||
, mGameSettings(gameSettings)
|
, mGameSettings(gameSettings)
|
||||||
, mEngineSettings(engineSettings)
|
, mEngineSettings(engineSettings)
|
||||||
{
|
{
|
||||||
|
@ -64,12 +62,12 @@ namespace
|
||||||
|
|
||||||
double convertToCells(double unitRadius)
|
double convertToCells(double unitRadius)
|
||||||
{
|
{
|
||||||
return std::round((unitRadius / 0.93 + 1024) / CellSizeInUnits);
|
return std::round((unitRadius + 1024) / CellSizeInUnits);
|
||||||
}
|
}
|
||||||
|
|
||||||
double convertToUnits(double CellGridRadius)
|
double convertToUnits(double CellGridRadius)
|
||||||
{
|
{
|
||||||
return (CellSizeInUnits * CellGridRadius - 1024) * 0.93;
|
return CellSizeInUnits * CellGridRadius - 1024;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +108,7 @@ bool Launcher::AdvancedPage::loadSettings()
|
||||||
loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
|
loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
|
||||||
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
|
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
|
||||||
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
|
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
|
||||||
if (animSourcesCheckBox->checkState())
|
if (animSourcesCheckBox->checkState() != Qt::Unchecked)
|
||||||
{
|
{
|
||||||
loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
|
loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
|
||||||
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
namespace Files { struct ConfigurationManager; }
|
|
||||||
namespace Config { class GameSettings; }
|
namespace Config { class GameSettings; }
|
||||||
|
|
||||||
namespace Launcher
|
namespace Launcher
|
||||||
|
@ -19,7 +18,7 @@ namespace Launcher
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
|
AdvancedPage(Config::GameSettings &gameSettings,
|
||||||
Settings::Manager &engineSettings, QWidget *parent = nullptr);
|
Settings::Manager &engineSettings, QWidget *parent = nullptr);
|
||||||
|
|
||||||
bool loadSettings();
|
bool loadSettings();
|
||||||
|
@ -35,7 +34,6 @@ namespace Launcher
|
||||||
void slotViewOverShoulderToggled(bool checked);
|
void slotViewOverShoulderToggled(bool checked);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Files::ConfigurationManager &mCfgMgr;
|
|
||||||
Config::GameSettings &mGameSettings;
|
Config::GameSettings &mGameSettings;
|
||||||
Settings::Manager &mEngineSettings;
|
Settings::Manager &mEngineSettings;
|
||||||
QCompleter mCellNameCompleter;
|
QCompleter mCellNameCompleter;
|
||||||
|
|
|
@ -88,11 +88,7 @@ namespace Launcher
|
||||||
QStringList previousSelectedFiles;
|
QStringList previousSelectedFiles;
|
||||||
QString mDataLocal;
|
QString mDataLocal;
|
||||||
|
|
||||||
void setPluginsCheckstates(Qt::CheckState state);
|
|
||||||
|
|
||||||
void buildView();
|
void buildView();
|
||||||
void setupConfig();
|
|
||||||
void readConfig();
|
|
||||||
void setProfile (int index, bool savePrevious);
|
void setProfile (int index, bool savePrevious);
|
||||||
void setProfile (const QString &previous, const QString ¤t, bool savePrevious);
|
void setProfile (const QString &previous, const QString ¤t, bool savePrevious);
|
||||||
void removeProfile (const QString &profile);
|
void removeProfile (const QString &profile);
|
||||||
|
|
|
@ -29,9 +29,8 @@ QString getAspect(int x, int y)
|
||||||
return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
|
return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
|
||||||
}
|
}
|
||||||
|
|
||||||
Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
|
Launcher::GraphicsPage::GraphicsPage(Settings::Manager &engineSettings, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, mCfgMgr(cfg)
|
|
||||||
, mEngineSettings(engineSettings)
|
, mEngineSettings(engineSettings)
|
||||||
{
|
{
|
||||||
setObjectName ("GraphicsPage");
|
setObjectName ("GraphicsPage");
|
||||||
|
@ -207,7 +206,7 @@ void Launcher::GraphicsPage::saveSettings()
|
||||||
if (cScreen != mEngineSettings.getInt("screen", "Video"))
|
if (cScreen != mEngineSettings.getInt("screen", "Video"))
|
||||||
mEngineSettings.setInt("screen", "Video", cScreen);
|
mEngineSettings.setInt("screen", "Video", cScreen);
|
||||||
|
|
||||||
if (framerateLimitCheckBox->checkState())
|
if (framerateLimitCheckBox->checkState() != Qt::Unchecked)
|
||||||
{
|
{
|
||||||
float cFpsLimit = framerateLimitSpinBox->value();
|
float cFpsLimit = framerateLimitSpinBox->value();
|
||||||
if (cFpsLimit != mEngineSettings.getFloat("framerate limit", "Video"))
|
if (cFpsLimit != mEngineSettings.getFloat("framerate limit", "Video"))
|
||||||
|
@ -218,7 +217,7 @@ void Launcher::GraphicsPage::saveSettings()
|
||||||
mEngineSettings.setFloat("framerate limit", "Video", 0);
|
mEngineSettings.setFloat("framerate limit", "Video", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cShadowDist = shadowDistanceCheckBox->checkState() ? shadowDistanceSpinBox->value() : 0;
|
int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0;
|
||||||
if (mEngineSettings.getInt("maximum shadow map distance", "Shadows") != cShadowDist)
|
if (mEngineSettings.getInt("maximum shadow map distance", "Shadows") != cShadowDist)
|
||||||
mEngineSettings.setInt("maximum shadow map distance", "Shadows", cShadowDist);
|
mEngineSettings.setInt("maximum shadow map distance", "Shadows", cShadowDist);
|
||||||
float cFadeStart = fadeStartSpinBox->value();
|
float cFadeStart = fadeStartSpinBox->value();
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace Launcher
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = nullptr);
|
GraphicsPage(Settings::Manager &engineSettings, QWidget *parent = nullptr);
|
||||||
|
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
bool loadSettings();
|
bool loadSettings();
|
||||||
|
@ -35,7 +35,6 @@ namespace Launcher
|
||||||
void slotShadowDistLimitToggled(bool checked);
|
void slotShadowDistLimitToggled(bool checked);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Files::ConfigurationManager &mCfgMgr;
|
|
||||||
Settings::Manager &mEngineSettings;
|
Settings::Manager &mEngineSettings;
|
||||||
|
|
||||||
QVector<QStringList> mResolutionsPerScreen;
|
QVector<QStringList> mResolutionsPerScreen;
|
||||||
|
|
|
@ -126,9 +126,9 @@ void Launcher::MainDialog::createPages()
|
||||||
|
|
||||||
mPlayPage = new PlayPage(this);
|
mPlayPage = new PlayPage(this);
|
||||||
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
||||||
mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this);
|
mGraphicsPage = new GraphicsPage(mEngineSettings, this);
|
||||||
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
||||||
mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, mEngineSettings, this);
|
mAdvancedPage = new AdvancedPage(mGameSettings, mEngineSettings, this);
|
||||||
|
|
||||||
// Set the combobox of the play page to imitate the combobox on the datafilespage
|
// Set the combobox of the play page to imitate the combobox on the datafilespage
|
||||||
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
|
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
|
||||||
|
|
|
@ -46,3 +46,4 @@ QString CellNameLoader::getCellName(ESM::ESMReader &esmReader)
|
||||||
|
|
||||||
return QString::fromStdString(cell.mName);
|
return QString::fromStdString(cell.mName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,9 @@ int runApplication(int argc, char *argv[])
|
||||||
application.setWindowIcon (QIcon (":./openmw-cs.png"));
|
application.setWindowIcon (QIcon (":./openmw-cs.png"));
|
||||||
|
|
||||||
CS::Editor editor(argc, argv);
|
CS::Editor editor(argc, argv);
|
||||||
|
#ifdef __linux__
|
||||||
|
setlocale(LC_NUMERIC,"C");
|
||||||
|
#endif
|
||||||
|
|
||||||
if(!editor.makeIPCServer())
|
if(!editor.makeIPCServer())
|
||||||
{
|
{
|
||||||
|
|
|
@ -325,12 +325,6 @@ std::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseNAry (const Token& keyw
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodes.empty())
|
|
||||||
{
|
|
||||||
error();
|
|
||||||
return std::shared_ptr<Node>();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (keyword.mType)
|
switch (keyword.mType)
|
||||||
{
|
{
|
||||||
case Token::Type_Keyword_And: return std::shared_ptr<CSMFilter::Node> (new AndNode (nodes));
|
case Token::Type_Keyword_And: return std::shared_ptr<CSMFilter::Node> (new AndNode (nodes));
|
||||||
|
|
|
@ -247,6 +247,15 @@ void CSMPrefs::State::declare()
|
||||||
EnumValues landeditOutsideVisibleCell;
|
EnumValues landeditOutsideVisibleCell;
|
||||||
landeditOutsideVisibleCell.add (showAndLandEdit).add (dontLandEdit);
|
landeditOutsideVisibleCell.add (showAndLandEdit).add (dontLandEdit);
|
||||||
|
|
||||||
|
EnumValue SelectOnly ("Select only");
|
||||||
|
EnumValue SelectAdd ("Add to selection");
|
||||||
|
EnumValue SelectRemove ("Remove from selection");
|
||||||
|
EnumValue selectInvert ("Invert selection");
|
||||||
|
EnumValues primarySelectAction;
|
||||||
|
primarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert);
|
||||||
|
EnumValues secondarySelectAction;
|
||||||
|
secondarySelectAction.add (SelectOnly).add (SelectAdd).add (SelectRemove).add (selectInvert);
|
||||||
|
|
||||||
declareCategory ("3D Scene Editing");
|
declareCategory ("3D Scene Editing");
|
||||||
declareInt ("distance", "Drop Distance", 50).
|
declareInt ("distance", "Drop Distance", 50).
|
||||||
setTooltip ("If an instance drop can not be placed against another object at the "
|
setTooltip ("If an instance drop can not be placed against another object at the "
|
||||||
|
@ -276,6 +285,12 @@ void CSMPrefs::State::declare()
|
||||||
declareBool ("open-list-view", "Open displays list view", false).
|
declareBool ("open-list-view", "Open displays list view", false).
|
||||||
setTooltip ("When opening a reference from the scene view, it will open the"
|
setTooltip ("When opening a reference from the scene view, it will open the"
|
||||||
" instance list view instead of the individual instance record view.");
|
" instance list view instead of the individual instance record view.");
|
||||||
|
declareEnum ("primary-select-action", "Action for primary select", SelectOnly).
|
||||||
|
setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection.").
|
||||||
|
addValues (primarySelectAction);
|
||||||
|
declareEnum ("secondary-select-action", "Action for secondary select", SelectAdd).
|
||||||
|
setTooltip("Selection can be chosen between select only, add to selection, remove from selection and invert selection.").
|
||||||
|
addValues (secondarySelectAction);
|
||||||
|
|
||||||
declareCategory ("Key Bindings");
|
declareCategory ("Key Bindings");
|
||||||
|
|
||||||
|
|
|
@ -397,6 +397,10 @@ void CSMWorld::CloneCommand::redo()
|
||||||
{
|
{
|
||||||
mModel.cloneRecord (mIdOrigin, mId, mType);
|
mModel.cloneRecord (mIdOrigin, mId, mType);
|
||||||
applyModifications();
|
applyModifications();
|
||||||
|
for (auto& value : mOverrideValues)
|
||||||
|
{
|
||||||
|
mModel.setData(mModel.getModelIndex (mId, value.first), value.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMWorld::CloneCommand::undo()
|
void CSMWorld::CloneCommand::undo()
|
||||||
|
@ -404,6 +408,11 @@ void CSMWorld::CloneCommand::undo()
|
||||||
mModel.removeRow (mModel.getModelIndex (mId, 0).row());
|
mModel.removeRow (mModel.getModelIndex (mId, 0).row());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSMWorld::CloneCommand::setOverrideValue(int column, QVariant value)
|
||||||
|
{
|
||||||
|
mOverrideValues.emplace_back(std::make_pair(column, value));
|
||||||
|
}
|
||||||
|
|
||||||
CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent)
|
CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent)
|
||||||
: CreateCommand(model, id, parent)
|
: CreateCommand(model, id, parent)
|
||||||
{
|
{
|
||||||
|
|
|
@ -183,6 +183,7 @@ namespace CSMWorld
|
||||||
class CloneCommand : public CreateCommand
|
class CloneCommand : public CreateCommand
|
||||||
{
|
{
|
||||||
std::string mIdOrigin;
|
std::string mIdOrigin;
|
||||||
|
std::vector<std::pair<int, QVariant>> mOverrideValues;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -194,6 +195,8 @@ namespace CSMWorld
|
||||||
void redo() override;
|
void redo() override;
|
||||||
|
|
||||||
void undo() override;
|
void undo() override;
|
||||||
|
|
||||||
|
void setOverrideValue(int column, QVariant value);
|
||||||
};
|
};
|
||||||
|
|
||||||
class RevertCommand : public QUndoCommand
|
class RevertCommand : public QUndoCommand
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
#include "nestedtablewrapper.hpp"
|
#include "nestedtablewrapper.hpp"
|
||||||
|
|
||||||
CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns)
|
CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns)
|
||||||
: InventoryColumns (columns) {}
|
: InventoryColumns (columns), mEffects(nullptr) {}
|
||||||
|
|
||||||
CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter (const PotionColumns& columns,
|
CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter (const PotionColumns& columns,
|
||||||
const RefIdColumn *autoCalc)
|
const RefIdColumn *autoCalc)
|
||||||
|
|
|
@ -115,7 +115,7 @@ namespace CSMWorld
|
||||||
{
|
{
|
||||||
const RefIdColumn *mModel;
|
const RefIdColumn *mModel;
|
||||||
|
|
||||||
ModelColumns (const BaseColumns& base) : BaseColumns (base) {}
|
ModelColumns (const BaseColumns& base) : BaseColumns (base), mModel(nullptr) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief Adapter for IDs with models (all but levelled lists)
|
/// \brief Adapter for IDs with models (all but levelled lists)
|
||||||
|
@ -1858,18 +1858,18 @@ namespace CSMWorld
|
||||||
break; // always save
|
break; // always save
|
||||||
case 16:
|
case 16:
|
||||||
if (content.mType == ESM::AI_Travel)
|
if (content.mType == ESM::AI_Travel)
|
||||||
content.mTravel.mZ = value.toFloat();
|
content.mTravel.mX = value.toFloat();
|
||||||
else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
|
else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
|
||||||
content.mTarget.mZ = value.toFloat();
|
content.mTarget.mX = value.toFloat();
|
||||||
else
|
else
|
||||||
return; // return without saving
|
return; // return without saving
|
||||||
|
|
||||||
break; // always save
|
break; // always save
|
||||||
case 17:
|
case 17:
|
||||||
if (content.mType == ESM::AI_Travel)
|
if (content.mType == ESM::AI_Travel)
|
||||||
content.mTravel.mZ = value.toFloat();
|
content.mTravel.mY = value.toFloat();
|
||||||
else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
|
else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort)
|
||||||
content.mTarget.mZ = value.toFloat();
|
content.mTarget.mY = value.toFloat();
|
||||||
else
|
else
|
||||||
return; // return without saving
|
return; // return without saving
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ void CSMWorld::ResourcesManager::addResources (const Resources& resources)
|
||||||
const char * const * CSMWorld::ResourcesManager::getMeshExtensions()
|
const char * const * CSMWorld::ResourcesManager::getMeshExtensions()
|
||||||
{
|
{
|
||||||
// maybe we could go over the osgDB::Registry to list all supported node formats
|
// maybe we could go over the osgDB::Registry to list all supported node formats
|
||||||
static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 };
|
static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae", 0 };
|
||||||
return sMeshTypes;
|
return sMeshTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -438,8 +438,8 @@ void CSVDoc::View::updateActions()
|
||||||
for (std::vector<QAction *>::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter)
|
for (std::vector<QAction *>::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter)
|
||||||
(*iter)->setEnabled (editing);
|
(*iter)->setEnabled (editing);
|
||||||
|
|
||||||
mUndo->setEnabled (editing & mDocument->getUndoStack().canUndo());
|
mUndo->setEnabled (editing && mDocument->getUndoStack().canUndo());
|
||||||
mRedo->setEnabled (editing & mDocument->getUndoStack().canRedo());
|
mRedo->setEnabled (editing && mDocument->getUndoStack().canRedo());
|
||||||
|
|
||||||
mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving) && !running);
|
mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving) && !running);
|
||||||
mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying));
|
mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying));
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "cell.hpp"
|
#include "cell.hpp"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#include <osg/PositionAttitudeTransform>
|
#include <osg/PositionAttitudeTransform>
|
||||||
#include <osg/Geode>
|
#include <osg/Geode>
|
||||||
#include <osg/Geometry>
|
#include <osg/Geometry>
|
||||||
|
@ -25,6 +27,7 @@
|
||||||
#include "pathgrid.hpp"
|
#include "pathgrid.hpp"
|
||||||
#include "terrainstorage.hpp"
|
#include "terrainstorage.hpp"
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
|
#include "instancedragmodes.hpp"
|
||||||
|
|
||||||
namespace CSVRender
|
namespace CSVRender
|
||||||
{
|
{
|
||||||
|
@ -496,6 +499,50 @@ void CSVRender::Cell::selectAllWithSameParentId (int elementMask)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSVRender::Cell::handleSelectDrag(Object* object, DragMode dragMode)
|
||||||
|
{
|
||||||
|
if (dragMode == DragMode_Select_Only || dragMode == DragMode_Select_Add)
|
||||||
|
object->setSelected(true);
|
||||||
|
|
||||||
|
else if (dragMode == DragMode_Select_Remove)
|
||||||
|
object->setSelected(false);
|
||||||
|
|
||||||
|
else if (dragMode == DragMode_Select_Invert)
|
||||||
|
object->setSelected (!object->getSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSVRender::Cell::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode)
|
||||||
|
{
|
||||||
|
for (auto& object : mObjects)
|
||||||
|
{
|
||||||
|
if (dragMode == DragMode_Select_Only) object.second->setSelected (false);
|
||||||
|
|
||||||
|
if ( ( object.second->getPosition().pos[0] > pointA[0] && object.second->getPosition().pos[0] < pointB[0] ) ||
|
||||||
|
( object.second->getPosition().pos[0] > pointB[0] && object.second->getPosition().pos[0] < pointA[0] ))
|
||||||
|
{
|
||||||
|
if ( ( object.second->getPosition().pos[1] > pointA[1] && object.second->getPosition().pos[1] < pointB[1] ) ||
|
||||||
|
( object.second->getPosition().pos[1] > pointB[1] && object.second->getPosition().pos[1] < pointA[1] ))
|
||||||
|
{
|
||||||
|
if ( ( object.second->getPosition().pos[2] > pointA[2] && object.second->getPosition().pos[2] < pointB[2] ) ||
|
||||||
|
( object.second->getPosition().pos[2] > pointB[2] && object.second->getPosition().pos[2] < pointA[2] ))
|
||||||
|
handleSelectDrag(object.second, dragMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSVRender::Cell::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode)
|
||||||
|
{
|
||||||
|
for (auto& object : mObjects)
|
||||||
|
{
|
||||||
|
if (dragMode == DragMode_Select_Only) object.second->setSelected (false);
|
||||||
|
|
||||||
|
float distanceFromObject = (point - object.second->getPosition().asVec3()).length();
|
||||||
|
if (distanceFromObject < distance) handleSelectDrag(object.second, dragMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CSVRender::Cell::setCellArrows (int mask)
|
void CSVRender::Cell::setCellArrows (int mask)
|
||||||
{
|
{
|
||||||
for (int i=0; i<4; ++i)
|
for (int i=0; i<4; ++i)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "../../model/world/cellcoordinates.hpp"
|
#include "../../model/world/cellcoordinates.hpp"
|
||||||
#include "terrainstorage.hpp"
|
#include "terrainstorage.hpp"
|
||||||
|
#include "instancedragmodes.hpp"
|
||||||
|
|
||||||
class QModelIndex;
|
class QModelIndex;
|
||||||
|
|
||||||
|
@ -152,6 +153,12 @@ namespace CSVRender
|
||||||
// already selected
|
// already selected
|
||||||
void selectAllWithSameParentId (int elementMask);
|
void selectAllWithSameParentId (int elementMask);
|
||||||
|
|
||||||
|
void handleSelectDrag(Object* object, DragMode dragMode);
|
||||||
|
|
||||||
|
void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode);
|
||||||
|
|
||||||
|
void selectWithinDistance(const osg::Vec3d& pointA, float distance, DragMode dragMode);
|
||||||
|
|
||||||
void setCellArrows (int mask);
|
void setCellArrows (int mask);
|
||||||
|
|
||||||
/// \brief Set marker for this cell.
|
/// \brief Set marker for this cell.
|
||||||
|
|
18
apps/opencs/view/render/instancedragmodes.hpp
Normal file
18
apps/opencs/view/render/instancedragmodes.hpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef CSV_WIDGET_INSTANCEDRAGMODES_H
|
||||||
|
#define CSV_WIDGET_INSTANCEDRAGMODES_H
|
||||||
|
|
||||||
|
namespace CSVRender
|
||||||
|
{
|
||||||
|
enum DragMode
|
||||||
|
{
|
||||||
|
DragMode_None,
|
||||||
|
DragMode_Move,
|
||||||
|
DragMode_Rotate,
|
||||||
|
DragMode_Scale,
|
||||||
|
DragMode_Select_Only,
|
||||||
|
DragMode_Select_Add,
|
||||||
|
DragMode_Select_Remove,
|
||||||
|
DragMode_Select_Invert
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -96,6 +96,33 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos)
|
||||||
return pos * combined;
|
return pos * combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
osg::Vec3f CSVRender::InstanceMode::getProjectionSpaceCoords(const osg::Vec3f& pos)
|
||||||
|
{
|
||||||
|
osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix();
|
||||||
|
osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix();
|
||||||
|
osg::Matrix combined = viewMatrix * projMatrix;
|
||||||
|
|
||||||
|
return pos * combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart)
|
||||||
|
{
|
||||||
|
osg::Matrix viewMatrix;
|
||||||
|
viewMatrix.invert(getWorldspaceWidget().getCamera()->getViewMatrix());
|
||||||
|
osg::Matrix projMatrix;
|
||||||
|
projMatrix.invert(getWorldspaceWidget().getCamera()->getProjectionMatrix());
|
||||||
|
osg::Matrix combined = projMatrix * viewMatrix;
|
||||||
|
|
||||||
|
/* calculate viewport normalized coordinates
|
||||||
|
note: is there a reason to use getCamera()->getViewport()->computeWindowMatrix() instead? */
|
||||||
|
float x = (point.x() * 2) / getWorldspaceWidget().getCamera()->getViewport()->width() - 1.0f;
|
||||||
|
float y = 1.0f - (point.y() * 2) / getWorldspaceWidget().getCamera()->getViewport()->height();
|
||||||
|
|
||||||
|
osg::Vec3f mousePlanePoint = osg::Vec3f(x, y, dragStart.z()) * combined;
|
||||||
|
|
||||||
|
return mousePlanePoint;
|
||||||
|
}
|
||||||
|
|
||||||
CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget *parent)
|
CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget *parent)
|
||||||
: EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing",
|
: EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing",
|
||||||
parent), mSubMode (nullptr), mSubModeId ("move"), mSelectionMode (nullptr), mDragMode (DragMode_None),
|
parent), mSubMode (nullptr), mSubModeId ("move"), mSelectionMode (nullptr), mDragMode (DragMode_None),
|
||||||
|
@ -146,7 +173,7 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mSelectionMode)
|
if (!mSelectionMode)
|
||||||
mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget());
|
mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget(), mParentNode);
|
||||||
|
|
||||||
mDragMode = DragMode_None;
|
mDragMode = DragMode_None;
|
||||||
|
|
||||||
|
@ -322,6 +349,42 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CSVRender::InstanceMode::primarySelectStartDrag (const QPoint& pos)
|
||||||
|
{
|
||||||
|
if (mDragMode!=DragMode_None || mLocked)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string primarySelectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString();
|
||||||
|
|
||||||
|
if ( primarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only;
|
||||||
|
else if ( primarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add;
|
||||||
|
else if ( primarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove;
|
||||||
|
else if ( primarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert;
|
||||||
|
|
||||||
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
|
||||||
|
mSelectionMode->setDragStart(hit.worldPos);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CSVRender::InstanceMode::secondarySelectStartDrag (const QPoint& pos)
|
||||||
|
{
|
||||||
|
if (mDragMode!=DragMode_None || mLocked)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string secondarySelectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString();
|
||||||
|
|
||||||
|
if ( secondarySelectAction == "Select only" ) mDragMode = DragMode_Select_Only;
|
||||||
|
else if ( secondarySelectAction == "Add to selection" ) mDragMode = DragMode_Select_Add;
|
||||||
|
else if ( secondarySelectAction == "Remove from selection" ) mDragMode = DragMode_Select_Remove;
|
||||||
|
else if ( secondarySelectAction == "Invert selection" ) mDragMode = DragMode_Select_Invert;
|
||||||
|
|
||||||
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
|
||||||
|
mSelectionMode->setDragStart(hit.worldPos);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor)
|
void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor)
|
||||||
{
|
{
|
||||||
osg::Vec3f offset;
|
osg::Vec3f offset;
|
||||||
|
@ -432,6 +495,24 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou
|
||||||
// Only uniform scaling is currently supported
|
// Only uniform scaling is currently supported
|
||||||
offset = osg::Vec3f(scale, scale, scale);
|
offset = osg::Vec3f(scale, scale, scale);
|
||||||
}
|
}
|
||||||
|
else if (mSelectionMode->getCurrentId() == "cube-centre")
|
||||||
|
{
|
||||||
|
osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart()));
|
||||||
|
mSelectionMode->drawSelectionCubeCentre (mousePlanePoint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (mSelectionMode->getCurrentId() == "cube-corner")
|
||||||
|
{
|
||||||
|
osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart()));
|
||||||
|
mSelectionMode->drawSelectionCubeCorner (mousePlanePoint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (mSelectionMode->getCurrentId() == "sphere")
|
||||||
|
{
|
||||||
|
osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart()));
|
||||||
|
mSelectionMode->drawSelectionSphere (mousePlanePoint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Apply
|
// Apply
|
||||||
for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin()); iter!=selection.end(); ++iter)
|
for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin()); iter!=selection.end(); ++iter)
|
||||||
|
@ -495,6 +576,22 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos)
|
||||||
case DragMode_Move: description = "Move Instances"; break;
|
case DragMode_Move: description = "Move Instances"; break;
|
||||||
case DragMode_Rotate: description = "Rotate Instances"; break;
|
case DragMode_Rotate: description = "Rotate Instances"; break;
|
||||||
case DragMode_Scale: description = "Scale Instances"; break;
|
case DragMode_Scale: description = "Scale Instances"; break;
|
||||||
|
case DragMode_Select_Only :
|
||||||
|
handleSelectDrag(pos);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
case DragMode_Select_Add :
|
||||||
|
handleSelectDrag(pos);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
case DragMode_Select_Remove :
|
||||||
|
handleSelectDrag(pos);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
case DragMode_Select_Invert :
|
||||||
|
handleSelectDrag(pos);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
case DragMode_None: break;
|
case DragMode_None: break;
|
||||||
}
|
}
|
||||||
|
@ -680,6 +777,13 @@ void CSVRender::InstanceMode::subModeChanged (const std::string& id)
|
||||||
getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference);
|
getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSVRender::InstanceMode::handleSelectDrag(const QPoint& pos)
|
||||||
|
{
|
||||||
|
osg::Vec3f mousePlanePoint = getMousePlaneCoords(pos, getProjectionSpaceCoords(mSelectionMode->getDragStart()));
|
||||||
|
mSelectionMode->dragEnded (mousePlanePoint, mDragMode);
|
||||||
|
mDragMode = DragMode_None;
|
||||||
|
}
|
||||||
|
|
||||||
void CSVRender::InstanceMode::deleteSelectedInstances(bool active)
|
void CSVRender::InstanceMode::deleteSelectedInstances(bool active)
|
||||||
{
|
{
|
||||||
std::vector<osg::ref_ptr<TagBase> > selection = getWorldspaceWidget().getSelection (Mask_Reference);
|
std::vector<osg::ref_ptr<TagBase> > selection = getWorldspaceWidget().getSelection (Mask_Reference);
|
||||||
|
@ -698,39 +802,15 @@ void CSVRender::InstanceMode::deleteSelectedInstances(bool active)
|
||||||
getWorldspaceWidget().clearSelection (Mask_Reference);
|
getWorldspaceWidget().clearSelection (Mask_Reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVRender::InstanceMode::dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight)
|
void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight)
|
||||||
{
|
{
|
||||||
osg::Vec3d point = object->getPosition().asVec3();
|
|
||||||
|
|
||||||
osg::Vec3d start = point;
|
|
||||||
start.z() += objectHeight;
|
|
||||||
osg::Vec3d end = point;
|
|
||||||
end.z() = std::numeric_limits<float>::lowest();
|
|
||||||
|
|
||||||
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector (new osgUtil::LineSegmentIntersector(
|
|
||||||
osgUtil::Intersector::MODEL, start, end) );
|
|
||||||
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT);
|
|
||||||
osgUtil::IntersectionVisitor visitor(intersector);
|
|
||||||
|
|
||||||
if (dropMode == TerrainSep)
|
|
||||||
visitor.setTraversalMask(Mask_Terrain);
|
|
||||||
if (dropMode == CollisionSep)
|
|
||||||
visitor.setTraversalMask(Mask_Terrain | Mask_Reference);
|
|
||||||
|
|
||||||
mParentNode->accept(visitor);
|
|
||||||
|
|
||||||
osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin();
|
|
||||||
if (it != intersector->getIntersections().end())
|
|
||||||
{
|
|
||||||
osgUtil::LineSegmentIntersector::Intersection intersection = *it;
|
|
||||||
ESM::Position position = object->getPosition();
|
|
||||||
object->setEdited(Object::Override_Position);
|
object->setEdited(Object::Override_Position);
|
||||||
position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight;
|
ESM::Position position = object->getPosition();
|
||||||
|
position.pos[2] -= dropHeight;
|
||||||
object->setPosition(position.pos);
|
object->setPosition(position.pos);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight)
|
float CSVRender::InstanceMode::calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight)
|
||||||
{
|
{
|
||||||
osg::Vec3d point = object->getPosition().asVec3();
|
osg::Vec3d point = object->getPosition().asVec3();
|
||||||
|
|
||||||
|
@ -744,9 +824,9 @@ float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Objec
|
||||||
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT);
|
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT);
|
||||||
osgUtil::IntersectionVisitor visitor(intersector);
|
osgUtil::IntersectionVisitor visitor(intersector);
|
||||||
|
|
||||||
if (dropMode == Terrain)
|
if (dropMode & Terrain)
|
||||||
visitor.setTraversalMask(Mask_Terrain);
|
visitor.setTraversalMask(Mask_Terrain);
|
||||||
if (dropMode == Collision)
|
if (dropMode & Collision)
|
||||||
visitor.setTraversalMask(Mask_Terrain | Mask_Reference);
|
visitor.setTraversalMask(Mask_Terrain | Mask_Reference);
|
||||||
|
|
||||||
mParentNode->accept(visitor);
|
mParentNode->accept(visitor);
|
||||||
|
@ -774,12 +854,12 @@ void CSVRender::InstanceMode::dropSelectedInstancesToTerrain()
|
||||||
|
|
||||||
void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately()
|
void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately()
|
||||||
{
|
{
|
||||||
handleDropMethod(TerrainSep, "Drop instances to next collision level separately");
|
handleDropMethod(CollisionSep, "Drop instances to next collision level separately");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately()
|
void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately()
|
||||||
{
|
{
|
||||||
handleDropMethod(CollisionSep, "Drop instances to terrain level separately");
|
handleDropMethod(TerrainSep, "Drop instances to terrain level separately");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg)
|
void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg)
|
||||||
|
@ -793,19 +873,30 @@ void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString comman
|
||||||
|
|
||||||
CSMWorld::CommandMacro macro (undoStack, commandMsg);
|
CSMWorld::CommandMacro macro (undoStack, commandMsg);
|
||||||
|
|
||||||
DropObjectDataHandler dropObjectDataHandler(&getWorldspaceWidget());
|
DropObjectHeightHandler dropObjectDataHandler(&getWorldspaceWidget());
|
||||||
|
|
||||||
switch (dropMode)
|
if(dropMode & Separate)
|
||||||
{
|
{
|
||||||
case Terrain:
|
int counter = 0;
|
||||||
case Collision:
|
for (osg::ref_ptr<TagBase> tag : selection)
|
||||||
|
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
||||||
|
{
|
||||||
|
float objectHeight = dropObjectDataHandler.mObjectHeights[counter];
|
||||||
|
float dropHeight = calculateDropHeight(dropMode, objectTag->mObject, objectHeight);
|
||||||
|
dropInstance(objectTag->mObject, dropHeight);
|
||||||
|
objectTag->mObject->apply(macro);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
float smallestDropHeight = std::numeric_limits<float>::max();
|
float smallestDropHeight = std::numeric_limits<float>::max();
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
for (osg::ref_ptr<TagBase> tag : selection)
|
for (osg::ref_ptr<TagBase> tag : selection)
|
||||||
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
||||||
{
|
{
|
||||||
float thisDrop = getDropHeight(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]);
|
float objectHeight = dropObjectDataHandler.mObjectHeights[counter];
|
||||||
|
float thisDrop = calculateDropHeight(dropMode, objectTag->mObject, objectHeight);
|
||||||
if (thisDrop < smallestDropHeight)
|
if (thisDrop < smallestDropHeight)
|
||||||
smallestDropHeight = thisDrop;
|
smallestDropHeight = thisDrop;
|
||||||
counter++;
|
counter++;
|
||||||
|
@ -813,32 +904,13 @@ void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString comman
|
||||||
for (osg::ref_ptr<TagBase> tag : selection)
|
for (osg::ref_ptr<TagBase> tag : selection)
|
||||||
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
||||||
{
|
{
|
||||||
objectTag->mObject->setEdited (Object::Override_Position);
|
dropInstance(objectTag->mObject, smallestDropHeight);
|
||||||
ESM::Position position = objectTag->mObject->getPosition();
|
|
||||||
position.pos[2] -= smallestDropHeight;
|
|
||||||
objectTag->mObject->setPosition(position.pos);
|
|
||||||
objectTag->mObject->apply(macro);
|
objectTag->mObject->apply(macro);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case TerrainSep:
|
|
||||||
case CollisionSep:
|
|
||||||
{
|
|
||||||
int counter = 0;
|
|
||||||
for(osg::ref_ptr<TagBase> tag: selection)
|
|
||||||
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
|
||||||
{
|
|
||||||
dropInstance(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]);
|
|
||||||
objectTag->mObject->apply (macro);
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget)
|
CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget)
|
||||||
: mWorldspaceWidget(worldspacewidget)
|
: mWorldspaceWidget(worldspacewidget)
|
||||||
{
|
{
|
||||||
std::vector<osg::ref_ptr<TagBase> > selection = mWorldspaceWidget->getSelection (Mask_Reference);
|
std::vector<osg::ref_ptr<TagBase> > selection = mWorldspaceWidget->getSelection (Mask_Reference);
|
||||||
|
@ -865,7 +937,7 @@ CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worlds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CSVRender::DropObjectDataHandler::~DropObjectDataHandler()
|
CSVRender::DropObjectHeightHandler::~DropObjectHeightHandler()
|
||||||
{
|
{
|
||||||
std::vector<osg::ref_ptr<TagBase> > selection = mWorldspaceWidget->getSelection (Mask_Reference);
|
std::vector<osg::ref_ptr<TagBase> > selection = mWorldspaceWidget->getSelection (Mask_Reference);
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
#include "editmode.hpp"
|
#include "editmode.hpp"
|
||||||
|
#include "instancedragmodes.hpp"
|
||||||
|
|
||||||
namespace CSVWidget
|
namespace CSVWidget
|
||||||
{
|
{
|
||||||
|
@ -25,20 +26,15 @@ namespace CSVRender
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
enum DragMode
|
|
||||||
{
|
|
||||||
DragMode_None,
|
|
||||||
DragMode_Move,
|
|
||||||
DragMode_Rotate,
|
|
||||||
DragMode_Scale
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DropMode
|
enum DropMode
|
||||||
{
|
{
|
||||||
Collision,
|
Separate = 0b1,
|
||||||
Terrain,
|
|
||||||
CollisionSep,
|
Collision = 0b10,
|
||||||
TerrainSep
|
Terrain = 0b100,
|
||||||
|
|
||||||
|
CollisionSep = Collision | Separate,
|
||||||
|
TerrainSep = Terrain | Separate,
|
||||||
};
|
};
|
||||||
|
|
||||||
CSVWidget::SceneToolMode *mSubMode;
|
CSVWidget::SceneToolMode *mSubMode;
|
||||||
|
@ -57,8 +53,11 @@ namespace CSVRender
|
||||||
|
|
||||||
osg::Vec3f getSelectionCenter(const std::vector<osg::ref_ptr<TagBase> >& selection) const;
|
osg::Vec3f getSelectionCenter(const std::vector<osg::ref_ptr<TagBase> >& selection) const;
|
||||||
osg::Vec3f getScreenCoords(const osg::Vec3f& pos);
|
osg::Vec3f getScreenCoords(const osg::Vec3f& pos);
|
||||||
void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight);
|
osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos);
|
||||||
float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight);
|
osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart);
|
||||||
|
void handleSelectDrag(const QPoint& pos);
|
||||||
|
void dropInstance(CSVRender::Object* object, float dropHeight);
|
||||||
|
float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -84,6 +83,10 @@ namespace CSVRender
|
||||||
|
|
||||||
bool secondaryEditStartDrag (const QPoint& pos) override;
|
bool secondaryEditStartDrag (const QPoint& pos) override;
|
||||||
|
|
||||||
|
bool primarySelectStartDrag(const QPoint& pos) override;
|
||||||
|
|
||||||
|
bool secondarySelectStartDrag(const QPoint& pos) override;
|
||||||
|
|
||||||
void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override;
|
void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) override;
|
||||||
|
|
||||||
void dragCompleted(const QPoint& pos) override;
|
void dragCompleted(const QPoint& pos) override;
|
||||||
|
@ -116,11 +119,11 @@ namespace CSVRender
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief Helper class to handle object mask data in safe way
|
/// \brief Helper class to handle object mask data in safe way
|
||||||
class DropObjectDataHandler
|
class DropObjectHeightHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DropObjectDataHandler(WorldspaceWidget* worldspacewidget);
|
DropObjectHeightHandler(WorldspaceWidget* worldspacewidget);
|
||||||
~DropObjectDataHandler();
|
~DropObjectHeightHandler();
|
||||||
std::vector<float> mObjectHeights;
|
std::vector<float> mObjectHeights;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -2,17 +2,24 @@
|
||||||
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
|
#include <QPoint>
|
||||||
|
|
||||||
|
#include <osg/Group>
|
||||||
|
#include <osg/PositionAttitudeTransform>
|
||||||
|
#include <osg/ref_ptr>
|
||||||
|
#include <osg/Vec3d>
|
||||||
|
|
||||||
#include "../../model/world/idtable.hpp"
|
#include "../../model/world/idtable.hpp"
|
||||||
#include "../../model/world/commands.hpp"
|
#include "../../model/world/commands.hpp"
|
||||||
|
|
||||||
|
#include "instancedragmodes.hpp"
|
||||||
#include "worldspacewidget.hpp"
|
#include "worldspacewidget.hpp"
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
|
|
||||||
namespace CSVRender
|
namespace CSVRender
|
||||||
{
|
{
|
||||||
InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget)
|
InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode)
|
||||||
: SelectionMode(parent, worldspaceWidget, Mask_Reference)
|
: SelectionMode(parent, worldspaceWidget, Mask_Reference), mParentNode(cellNode)
|
||||||
{
|
{
|
||||||
mSelectSame = new QAction("Extend selection to instances with same object ID", this);
|
mSelectSame = new QAction("Extend selection to instances with same object ID", this);
|
||||||
mDeleteSelection = new QAction("Delete selected instances", this);
|
mDeleteSelection = new QAction("Delete selected instances", this);
|
||||||
|
@ -21,6 +28,342 @@ namespace CSVRender
|
||||||
connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection()));
|
connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InstanceSelectionMode::~InstanceSelectionMode()
|
||||||
|
{
|
||||||
|
mParentNode->removeChild(mBaseNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart)
|
||||||
|
{
|
||||||
|
mDragStart = dragStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
const osg::Vec3d& InstanceSelectionMode::getDragStart()
|
||||||
|
{
|
||||||
|
return mDragStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceSelectionMode::dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode)
|
||||||
|
{
|
||||||
|
float dragDistance = (mDragStart - dragEndPoint).length();
|
||||||
|
if (mBaseNode) mParentNode->removeChild (mBaseNode);
|
||||||
|
if (getCurrentId() == "cube-centre")
|
||||||
|
{
|
||||||
|
osg::Vec3d pointA(mDragStart[0] - dragDistance, mDragStart[1] - dragDistance, mDragStart[2] - dragDistance);
|
||||||
|
osg::Vec3d pointB(mDragStart[0] + dragDistance, mDragStart[1] + dragDistance, mDragStart[2] + dragDistance);
|
||||||
|
getWorldspaceWidget().selectInsideCube(pointA, pointB, dragMode);
|
||||||
|
}
|
||||||
|
else if (getCurrentId() == "cube-corner")
|
||||||
|
{
|
||||||
|
getWorldspaceWidget().selectInsideCube(mDragStart, dragEndPoint, dragMode);
|
||||||
|
}
|
||||||
|
else if (getCurrentId() == "sphere")
|
||||||
|
{
|
||||||
|
getWorldspaceWidget().selectWithinDistance(mDragStart, dragDistance, dragMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceSelectionMode::drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint)
|
||||||
|
{
|
||||||
|
float dragDistance = (mDragStart - mousePlanePoint).length();
|
||||||
|
drawSelectionCube(mDragStart, dragDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceSelectionMode::drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint)
|
||||||
|
{
|
||||||
|
drawSelectionBox(mDragStart, mousePlanePoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceSelectionMode::drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB)
|
||||||
|
{
|
||||||
|
if (mBaseNode) mParentNode->removeChild (mBaseNode);
|
||||||
|
mBaseNode = new osg::PositionAttitudeTransform;
|
||||||
|
mBaseNode->setPosition(pointA);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
||||||
|
|
||||||
|
osg::Vec3Array *vertices = new osg::Vec3Array;
|
||||||
|
vertices->push_back (osg::Vec3f (0.0f, 0.0f, 0.0f));
|
||||||
|
vertices->push_back (osg::Vec3f (0.0f, 0.0f, pointB[2] - pointA[2]));
|
||||||
|
vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], 0.0f));
|
||||||
|
vertices->push_back (osg::Vec3f (0.0f, pointB[1] - pointA[1], pointB[2] - pointA[2]));
|
||||||
|
|
||||||
|
vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, 0.0f));
|
||||||
|
vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], 0.0f, pointB[2] - pointA[2]));
|
||||||
|
vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], 0.0f));
|
||||||
|
vertices->push_back (osg::Vec3f (pointB[0] - pointA[0], pointB[1] - pointA[1], pointB[2] - pointA[2]));
|
||||||
|
|
||||||
|
geometry->setVertexArray (vertices);
|
||||||
|
|
||||||
|
osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0);
|
||||||
|
|
||||||
|
// top
|
||||||
|
primitives->push_back (2);
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (0);
|
||||||
|
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (2);
|
||||||
|
|
||||||
|
// bottom
|
||||||
|
primitives->push_back (4);
|
||||||
|
primitives->push_back (5);
|
||||||
|
primitives->push_back (6);
|
||||||
|
|
||||||
|
primitives->push_back (6);
|
||||||
|
primitives->push_back (5);
|
||||||
|
primitives->push_back (7);
|
||||||
|
|
||||||
|
// sides
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (4);
|
||||||
|
primitives->push_back (0);
|
||||||
|
|
||||||
|
primitives->push_back (4);
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (5);
|
||||||
|
|
||||||
|
primitives->push_back (4);
|
||||||
|
primitives->push_back (2);
|
||||||
|
primitives->push_back (0);
|
||||||
|
|
||||||
|
primitives->push_back (6);
|
||||||
|
primitives->push_back (2);
|
||||||
|
primitives->push_back (4);
|
||||||
|
|
||||||
|
primitives->push_back (6);
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (2);
|
||||||
|
|
||||||
|
primitives->push_back (7);
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (6);
|
||||||
|
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (5);
|
||||||
|
|
||||||
|
primitives->push_back (5);
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (7);
|
||||||
|
|
||||||
|
geometry->addPrimitiveSet (primitives);
|
||||||
|
|
||||||
|
osg::Vec4Array *colours = new osg::Vec4Array;
|
||||||
|
|
||||||
|
colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f));
|
||||||
|
|
||||||
|
geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX);
|
||||||
|
|
||||||
|
geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF);
|
||||||
|
geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON);
|
||||||
|
geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||||
|
geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
||||||
|
|
||||||
|
mBaseNode->addChild (geometry);
|
||||||
|
mParentNode->addChild(mBaseNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceSelectionMode::drawSelectionCube(const osg::Vec3d& point, float radius)
|
||||||
|
{
|
||||||
|
if (mBaseNode) mParentNode->removeChild (mBaseNode);
|
||||||
|
mBaseNode = new osg::PositionAttitudeTransform;
|
||||||
|
mBaseNode->setPosition(point);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
||||||
|
|
||||||
|
osg::Vec3Array *vertices = new osg::Vec3Array;
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
float height = i ? -radius : radius;
|
||||||
|
vertices->push_back (osg::Vec3f (height, -radius, -radius));
|
||||||
|
vertices->push_back (osg::Vec3f (height, -radius, radius));
|
||||||
|
vertices->push_back (osg::Vec3f (height, radius, -radius));
|
||||||
|
vertices->push_back (osg::Vec3f (height, radius, radius));
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry->setVertexArray (vertices);
|
||||||
|
|
||||||
|
osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0);
|
||||||
|
|
||||||
|
// top
|
||||||
|
primitives->push_back (2);
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (0);
|
||||||
|
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (2);
|
||||||
|
|
||||||
|
// bottom
|
||||||
|
primitives->push_back (4);
|
||||||
|
primitives->push_back (5);
|
||||||
|
primitives->push_back (6);
|
||||||
|
|
||||||
|
primitives->push_back (6);
|
||||||
|
primitives->push_back (5);
|
||||||
|
primitives->push_back (7);
|
||||||
|
|
||||||
|
// sides
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (4);
|
||||||
|
primitives->push_back (0);
|
||||||
|
|
||||||
|
primitives->push_back (4);
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (5);
|
||||||
|
|
||||||
|
primitives->push_back (4);
|
||||||
|
primitives->push_back (2);
|
||||||
|
primitives->push_back (0);
|
||||||
|
|
||||||
|
primitives->push_back (6);
|
||||||
|
primitives->push_back (2);
|
||||||
|
primitives->push_back (4);
|
||||||
|
|
||||||
|
primitives->push_back (6);
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (2);
|
||||||
|
|
||||||
|
primitives->push_back (7);
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (6);
|
||||||
|
|
||||||
|
primitives->push_back (1);
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (5);
|
||||||
|
|
||||||
|
primitives->push_back (5);
|
||||||
|
primitives->push_back (3);
|
||||||
|
primitives->push_back (7);
|
||||||
|
|
||||||
|
geometry->addPrimitiveSet (primitives);
|
||||||
|
|
||||||
|
osg::Vec4Array *colours = new osg::Vec4Array;
|
||||||
|
|
||||||
|
colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.5f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.3f, 0.3f, 0.4f, 0.2f));
|
||||||
|
colours->push_back (osg::Vec4f (0.9f, 0.9f, 1.0f, 0.2f));
|
||||||
|
|
||||||
|
geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX);
|
||||||
|
|
||||||
|
geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF);
|
||||||
|
geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON);
|
||||||
|
geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||||
|
geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
||||||
|
|
||||||
|
mBaseNode->addChild (geometry);
|
||||||
|
mParentNode->addChild(mBaseNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3f& mousePlanePoint)
|
||||||
|
{
|
||||||
|
float dragDistance = (mDragStart - mousePlanePoint).length();
|
||||||
|
drawSelectionSphere(mDragStart, dragDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceSelectionMode::drawSelectionSphere(const osg::Vec3d& point, float radius)
|
||||||
|
{
|
||||||
|
if (mBaseNode) mParentNode->removeChild (mBaseNode);
|
||||||
|
mBaseNode = new osg::PositionAttitudeTransform;
|
||||||
|
mBaseNode->setPosition(point);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
||||||
|
|
||||||
|
osg::Vec3Array *vertices = new osg::Vec3Array;
|
||||||
|
int resolution = 32;
|
||||||
|
float radiusPerResolution = radius / resolution;
|
||||||
|
float reciprocalResolution = 1.0f / resolution;
|
||||||
|
float doubleReciprocalRes = reciprocalResolution * 2;
|
||||||
|
|
||||||
|
osg::Vec4Array *colours = new osg::Vec4Array;
|
||||||
|
|
||||||
|
for (float i = 0.0; i <= resolution; i += 2)
|
||||||
|
{
|
||||||
|
float iShifted = (static_cast<float>(i) - resolution / 2.0f); // i - 16 = -16 ... 16
|
||||||
|
float xPercentile = iShifted * doubleReciprocalRes;
|
||||||
|
float x = xPercentile * radius;
|
||||||
|
float thisRadius = sqrt (radius * radius - x * x);
|
||||||
|
|
||||||
|
//the next row
|
||||||
|
float iShifted2 = (static_cast<float>(i + 1) - resolution / 2.0f);
|
||||||
|
float xPercentile2 = iShifted2 * doubleReciprocalRes;
|
||||||
|
float x2 = xPercentile2 * radius;
|
||||||
|
float thisRadius2 = sqrt (radius * radius - x2 * x2);
|
||||||
|
|
||||||
|
for (int j = 0; j < resolution; ++j)
|
||||||
|
{
|
||||||
|
float vertexX = thisRadius * sin(j * reciprocalResolution * osg::PI * 2);
|
||||||
|
float vertexY = i * radiusPerResolution * 2 - radius;
|
||||||
|
float vertexZ = thisRadius * cos(j * reciprocalResolution * osg::PI * 2);
|
||||||
|
float heightPercentage = (vertexZ + radius) / (radius * 2);
|
||||||
|
vertices->push_back (osg::Vec3f (vertexX, vertexY, vertexZ));
|
||||||
|
colours->push_back (osg::Vec4f (heightPercentage, heightPercentage, heightPercentage, 0.3f));
|
||||||
|
|
||||||
|
float vertexNextRowX = thisRadius2 * sin(j * reciprocalResolution * osg::PI * 2);
|
||||||
|
float vertexNextRowY = (i + 1) * radiusPerResolution * 2 - radius;
|
||||||
|
float vertexNextRowZ = thisRadius2 * cos(j * reciprocalResolution * osg::PI * 2);
|
||||||
|
float heightPercentageNextRow = (vertexZ + radius) / (radius * 2);
|
||||||
|
vertices->push_back (osg::Vec3f (vertexNextRowX, vertexNextRowY, vertexNextRowZ));
|
||||||
|
colours->push_back (osg::Vec4f (heightPercentageNextRow, heightPercentageNextRow, heightPercentageNextRow, 0.3f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry->setVertexArray (vertices);
|
||||||
|
|
||||||
|
osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLE_STRIP, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < resolution; ++i)
|
||||||
|
{
|
||||||
|
//Even
|
||||||
|
for (int j = 0; j < resolution * 2; ++j)
|
||||||
|
{
|
||||||
|
if (i * resolution * 2 + j > static_cast<int>(vertices->size()) - 1) continue;
|
||||||
|
primitives->push_back (i * resolution * 2 + j);
|
||||||
|
}
|
||||||
|
if (i * resolution * 2 > static_cast<int>(vertices->size()) - 1) continue;
|
||||||
|
primitives->push_back (i * resolution * 2);
|
||||||
|
primitives->push_back (i * resolution * 2 + 1);
|
||||||
|
|
||||||
|
//Odd
|
||||||
|
for (int j = 1; j < resolution * 2 - 2; j += 2)
|
||||||
|
{
|
||||||
|
if ((i + 1) * resolution * 2 + j - 1 > static_cast<int>(vertices->size()) - 1) continue;
|
||||||
|
primitives->push_back ((i + 1) * resolution * 2 + j - 1);
|
||||||
|
primitives->push_back (i * resolution * 2 + j + 2);
|
||||||
|
}
|
||||||
|
if ((i + 2) * resolution * 2 - 2 > static_cast<int>(vertices->size()) - 1) continue;
|
||||||
|
primitives->push_back ((i + 2) * resolution * 2 - 2);
|
||||||
|
primitives->push_back (i * resolution * 2 + 1);
|
||||||
|
primitives->push_back ((i + 1) * resolution * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry->addPrimitiveSet (primitives);
|
||||||
|
|
||||||
|
geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX);
|
||||||
|
|
||||||
|
geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF);
|
||||||
|
geometry->getOrCreateStateSet()->setMode (GL_BLEND, osg::StateAttribute::ON);
|
||||||
|
geometry->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||||
|
geometry->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
||||||
|
|
||||||
|
mBaseNode->addChild (geometry);
|
||||||
|
mParentNode->addChild(mBaseNode);
|
||||||
|
}
|
||||||
|
|
||||||
bool InstanceSelectionMode::createContextMenu(QMenu* menu)
|
bool InstanceSelectionMode::createContextMenu(QMenu* menu)
|
||||||
{
|
{
|
||||||
if (menu)
|
if (menu)
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
#ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H
|
#ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H
|
||||||
#define CSV_RENDER_INSTANCE_SELECTION_MODE_H
|
#define CSV_RENDER_INSTANCE_SELECTION_MODE_H
|
||||||
|
|
||||||
|
#include <QPoint>
|
||||||
|
|
||||||
|
#include <osg/PositionAttitudeTransform>
|
||||||
|
#include <osg/Vec3d>
|
||||||
|
|
||||||
#include "selectionmode.hpp"
|
#include "selectionmode.hpp"
|
||||||
|
#include "instancedragmodes.hpp"
|
||||||
|
|
||||||
namespace CSVRender
|
namespace CSVRender
|
||||||
{
|
{
|
||||||
|
@ -11,8 +17,25 @@ namespace CSVRender
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget);
|
InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode);
|
||||||
|
|
||||||
|
~InstanceSelectionMode();
|
||||||
|
|
||||||
|
/// Store the worldspace-coordinate when drag begins
|
||||||
|
void setDragStart(const osg::Vec3d& dragStart);
|
||||||
|
|
||||||
|
/// Store the worldspace-coordinate when drag begins
|
||||||
|
const osg::Vec3d& getDragStart();
|
||||||
|
|
||||||
|
/// Store the screen-coordinate when drag begins
|
||||||
|
void setScreenDragStart(const QPoint& dragStartPoint);
|
||||||
|
|
||||||
|
/// Apply instance selection changes
|
||||||
|
void dragEnded(const osg::Vec3d& dragEndPoint, DragMode dragMode);
|
||||||
|
|
||||||
|
void drawSelectionCubeCentre(const osg::Vec3f& mousePlanePoint );
|
||||||
|
void drawSelectionCubeCorner(const osg::Vec3f& mousePlanePoint );
|
||||||
|
void drawSelectionSphere(const osg::Vec3f& mousePlanePoint );
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/// Add context menu items to \a menu.
|
/// Add context menu items to \a menu.
|
||||||
|
@ -25,8 +48,15 @@ namespace CSVRender
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
void drawSelectionBox(const osg::Vec3d& pointA, const osg::Vec3d& pointB);
|
||||||
|
void drawSelectionCube(const osg::Vec3d& point, float radius);
|
||||||
|
void drawSelectionSphere(const osg::Vec3d& point, float radius);
|
||||||
|
|
||||||
QAction* mDeleteSelection;
|
QAction* mDeleteSelection;
|
||||||
QAction* mSelectSame;
|
QAction* mSelectSame;
|
||||||
|
osg::Vec3d mDragStart;
|
||||||
|
osg::Group* mParentNode;
|
||||||
|
osg::ref_ptr<osg::PositionAttitudeTransform> mBaseNode;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
|
|
|
@ -768,6 +768,22 @@ void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMas
|
||||||
flagAsModified();
|
flagAsModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSVRender::PagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode)
|
||||||
|
{
|
||||||
|
for (auto& cell : mCells)
|
||||||
|
{
|
||||||
|
cell.second->selectInsideCube (pointA, pointB, dragMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSVRender::PagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode)
|
||||||
|
{
|
||||||
|
for (auto& cell : mCells)
|
||||||
|
{
|
||||||
|
cell.second->selectWithinDistance (point, distance, dragMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const
|
std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const
|
||||||
{
|
{
|
||||||
CSMWorld::CellCoordinates cellCoordinates (
|
CSMWorld::CellCoordinates cellCoordinates (
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "worldspacewidget.hpp"
|
#include "worldspacewidget.hpp"
|
||||||
#include "cell.hpp"
|
#include "cell.hpp"
|
||||||
|
#include "instancedragmodes.hpp"
|
||||||
|
|
||||||
namespace CSVWidget
|
namespace CSVWidget
|
||||||
{
|
{
|
||||||
|
@ -120,6 +121,10 @@ namespace CSVRender
|
||||||
/// \param elementMask Elements to be affected by the select operation
|
/// \param elementMask Elements to be affected by the select operation
|
||||||
void selectAllWithSameParentId (int elementMask) override;
|
void selectAllWithSameParentId (int elementMask) override;
|
||||||
|
|
||||||
|
void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override;
|
||||||
|
|
||||||
|
void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override;
|
||||||
|
|
||||||
std::string getCellId (const osg::Vec3f& point) const override;
|
std::string getCellId (const osg::Vec3f& point) const override;
|
||||||
|
|
||||||
Cell* getCell(const osg::Vec3d& point) const override;
|
Cell* getCell(const osg::Vec3d& point) const override;
|
||||||
|
|
|
@ -15,30 +15,27 @@ namespace CSVRender
|
||||||
{
|
{
|
||||||
addButton(":scenetoolbar/selection-mode-cube", "cube-centre",
|
addButton(":scenetoolbar/selection-mode-cube", "cube-centre",
|
||||||
"Centred cube"
|
"Centred cube"
|
||||||
"<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} "
|
"<ul><li>Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select "
|
||||||
"(invert selection state) from the centre of the selection cube outwards</li>"
|
"from the centre of the selection cube outwards.</li>"
|
||||||
"<li>The selection cube is aligned to the word space axis</li>"
|
"<li>The selection cube is aligned to the word space axis</li>"
|
||||||
"<li>If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not "
|
"<li>If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not "
|
||||||
"starting on an instance will have the same effect</li>"
|
"starting on an instance will have the same effect</li>"
|
||||||
"</ul>"
|
"</ul>");
|
||||||
"<font color=Red>Not implemented yet</font color>");
|
|
||||||
addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner",
|
addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner",
|
||||||
"Cube corner to corner"
|
"Cube corner to corner"
|
||||||
"<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} "
|
"<ul><li>Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select "
|
||||||
"(invert selection state) from one corner of the selection cube to the opposite corner</li>"
|
"from one corner of the selection cube to the opposite corner</li>"
|
||||||
"<li>The selection cube is aligned to the word space axis</li>"
|
"<li>The selection cube is aligned to the word space axis</li>"
|
||||||
"<li>If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not "
|
"<li>If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not "
|
||||||
"starting on an instance will have the same effect</li>"
|
"starting on an instance will have the same effect</li>"
|
||||||
"</ul>"
|
"</ul>");
|
||||||
"<font color=Red>Not implemented yet</font color>");
|
|
||||||
addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere",
|
addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere",
|
||||||
"Centred sphere"
|
"Centred sphere"
|
||||||
"<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} "
|
"<ul><li>Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select "
|
||||||
"(invert selection state) from the centre of the selection sphere outwards</li>"
|
"from the centre of the selection sphere outwards</li>"
|
||||||
"<li>If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not "
|
"<li>If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not "
|
||||||
"starting on an instance will have the same effect</li>"
|
"starting on an instance will have the same effect</li>"
|
||||||
"</ul>"
|
"</ul>");
|
||||||
"<font color=Red>Not implemented yet</font color>");
|
|
||||||
|
|
||||||
mSelectAll = new QAction("Select all", this);
|
mSelectAll = new QAction("Select all", this);
|
||||||
mDeselectAll = new QAction("Clear selection", this);
|
mDeselectAll = new QAction("Clear selection", this);
|
||||||
|
|
|
@ -427,14 +427,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe
|
||||||
{
|
{
|
||||||
if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r)
|
if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r)
|
||||||
{
|
{
|
||||||
int distanceX(0);
|
int distanceX = abs(i-xHitInCell);
|
||||||
int distanceY(0);
|
int distanceY = abs(j-yHitInCell);
|
||||||
if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i;
|
|
||||||
if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j;
|
|
||||||
if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize* abs(i_cell-cellX) + i;
|
|
||||||
if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j;
|
|
||||||
if (i_cell == cellX) distanceX = abs(i-xHitInCell);
|
|
||||||
if (j_cell == cellY) distanceY = abs(j-yHitInCell);
|
|
||||||
float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2)));
|
float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2)));
|
||||||
float rf = static_cast<float>(mBrushSize) / 2;
|
float rf = static_cast<float>(mBrushSize) / 2;
|
||||||
if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt;
|
if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt;
|
||||||
|
|
|
@ -140,6 +140,16 @@ void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId (int elementM
|
||||||
flagAsModified();
|
flagAsModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSVRender::UnpagedWorldspaceWidget::selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode)
|
||||||
|
{
|
||||||
|
mCell->selectInsideCube (pointA, pointB, dragMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSVRender::UnpagedWorldspaceWidget::selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode)
|
||||||
|
{
|
||||||
|
mCell->selectWithinDistance (point, distance, dragMode);
|
||||||
|
}
|
||||||
|
|
||||||
std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const
|
std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const
|
||||||
{
|
{
|
||||||
return mCellId;
|
return mCellId;
|
||||||
|
|
|
@ -60,6 +60,10 @@ namespace CSVRender
|
||||||
/// \param elementMask Elements to be affected by the select operation
|
/// \param elementMask Elements to be affected by the select operation
|
||||||
void selectAllWithSameParentId (int elementMask) override;
|
void selectAllWithSameParentId (int elementMask) override;
|
||||||
|
|
||||||
|
void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) override;
|
||||||
|
|
||||||
|
void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) override;
|
||||||
|
|
||||||
std::string getCellId (const osg::Vec3f& point) const override;
|
std::string getCellId (const osg::Vec3f& point) const override;
|
||||||
|
|
||||||
Cell* getCell(const osg::Vec3d& point) const override;
|
Cell* getCell(const osg::Vec3d& point) const override;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "../../model/doc/document.hpp"
|
#include "../../model/doc/document.hpp"
|
||||||
#include "../../model/world/tablemimedata.hpp"
|
#include "../../model/world/tablemimedata.hpp"
|
||||||
|
|
||||||
|
#include "instancedragmodes.hpp"
|
||||||
#include "scenewidget.hpp"
|
#include "scenewidget.hpp"
|
||||||
#include "mask.hpp"
|
#include "mask.hpp"
|
||||||
|
|
||||||
|
@ -160,6 +161,10 @@ namespace CSVRender
|
||||||
/// \param elementMask Elements to be affected by the select operation
|
/// \param elementMask Elements to be affected by the select operation
|
||||||
virtual void selectAllWithSameParentId (int elementMask) = 0;
|
virtual void selectAllWithSameParentId (int elementMask) = 0;
|
||||||
|
|
||||||
|
virtual void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode) = 0;
|
||||||
|
|
||||||
|
virtual void selectWithinDistance(const osg::Vec3d& point, float distance, DragMode dragMode) = 0;
|
||||||
|
|
||||||
/// Return the next intersection with scene elements matched by
|
/// Return the next intersection with scene elements matched by
|
||||||
/// \a interactionMask based on \a localPos and the camera vector.
|
/// \a interactionMask based on \a localPos and the camera vector.
|
||||||
/// If there is no such intersection, instead a point "in front" of \a localPos will be
|
/// If there is no such intersection, instead a point "in front" of \a localPos will be
|
||||||
|
|
|
@ -15,12 +15,21 @@ bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::C
|
||||||
return data != nullptr && data->holdsType(type);
|
return data != nullptr && data->holdsType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CSVWorld::DragDropUtils::isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type)
|
||||||
|
{
|
||||||
|
const CSMWorld::TableMimeData *data = getTableMimeData(event);
|
||||||
|
return data != nullptr && (
|
||||||
|
data->holdsType(CSMWorld::UniversalId::Type_TopicInfo) ||
|
||||||
|
data->holdsType(CSMWorld::UniversalId::Type_JournalInfo) );
|
||||||
|
}
|
||||||
|
|
||||||
CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event,
|
CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event,
|
||||||
CSMWorld::ColumnBase::Display type)
|
CSMWorld::ColumnBase::Display type)
|
||||||
{
|
{
|
||||||
if (canAcceptData(event, type))
|
if (canAcceptData(event, type))
|
||||||
{
|
{
|
||||||
return getTableMimeData(event)->returnMatching(type);
|
if (const CSMWorld::TableMimeData *data = getTableMimeData(event))
|
||||||
|
return data->returnMatching(type);
|
||||||
}
|
}
|
||||||
return CSMWorld::UniversalId::Type_None;
|
return CSMWorld::UniversalId::Type_None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ namespace CSVWorld
|
||||||
bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type);
|
bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type);
|
||||||
///< Checks whether the \a event contains a valid CSMWorld::TableMimeData that holds the \a type
|
///< Checks whether the \a event contains a valid CSMWorld::TableMimeData that holds the \a type
|
||||||
|
|
||||||
|
bool isInfo(const QDropEvent &event, CSMWorld::ColumnBase::Display type);
|
||||||
|
///< Info types can be dragged to sort the info table
|
||||||
|
|
||||||
CSMWorld::UniversalId getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type);
|
CSMWorld::UniversalId getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type);
|
||||||
///< Gets the accepted data from the \a event using the \a type
|
///< Gets the accepted data from the \a event using the \a type
|
||||||
///< \return Type_None if the \a event data doesn't holds the \a type
|
///< \return Type_None if the \a event data doesn't holds the \a type
|
||||||
|
|
|
@ -19,15 +19,11 @@ void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTa
|
||||||
}
|
}
|
||||||
|
|
||||||
CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument);
|
CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument);
|
||||||
|
|
||||||
if (mime)
|
|
||||||
{
|
|
||||||
QDrag* drag = new QDrag (this);
|
QDrag* drag = new QDrag (this);
|
||||||
drag->setMimeData (mime);
|
drag->setMimeData (mime);
|
||||||
drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str()));
|
drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str()));
|
||||||
drag->exec (Qt::CopyAction);
|
drag->exec (Qt::CopyAction);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* parent) :
|
CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* parent) :
|
||||||
QTableView(parent),
|
QTableView(parent),
|
||||||
|
@ -50,7 +46,8 @@ void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event)
|
||||||
void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event)
|
void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event)
|
||||||
{
|
{
|
||||||
QModelIndex index = indexAt(event->pos());
|
QModelIndex index = indexAt(event->pos());
|
||||||
if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index)))
|
if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index)) ||
|
||||||
|
CSVWorld::DragDropUtils::isInfo(*event, getIndexDisplayType(index)) )
|
||||||
{
|
{
|
||||||
if (index.flags() & Qt::ItemIsEditable)
|
if (index.flags() & Qt::ItemIsEditable)
|
||||||
{
|
{
|
||||||
|
@ -79,6 +76,10 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (CSVWorld::DragDropUtils::isInfo(*event, display) && event->source() == this)
|
||||||
|
{
|
||||||
|
emit moveRecordsFromSameTable(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const
|
CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const
|
||||||
|
|
|
@ -23,6 +23,8 @@ namespace CSVWorld
|
||||||
{
|
{
|
||||||
class DragRecordTable : public QTableView
|
class DragRecordTable : public QTableView
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CSMDoc::Document& mDocument;
|
CSMDoc::Document& mDocument;
|
||||||
bool mEditLock;
|
bool mEditLock;
|
||||||
|
@ -45,6 +47,9 @@ namespace CSVWorld
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const;
|
CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void moveRecordsFromSameTable(QDropEvent *event);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,10 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com
|
||||||
{
|
{
|
||||||
CSMWorld::IdTable& table = dynamic_cast<CSMWorld::IdTable&> (*getData().getTableModel (getCollectionId()));
|
CSMWorld::IdTable& table = dynamic_cast<CSMWorld::IdTable&> (*getData().getTableModel (getCollectionId()));
|
||||||
|
|
||||||
|
CSMWorld::CloneCommand* cloneCommand = dynamic_cast<CSMWorld::CloneCommand*> (&command);
|
||||||
if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos)
|
if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos)
|
||||||
|
{
|
||||||
|
if (!cloneCommand)
|
||||||
{
|
{
|
||||||
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text());
|
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text());
|
||||||
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1);
|
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1);
|
||||||
|
@ -42,9 +45,19 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com
|
||||||
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1);
|
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!cloneCommand)
|
||||||
{
|
{
|
||||||
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text());
|
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack,
|
CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack,
|
||||||
|
|
|
@ -75,10 +75,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
|
||||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, JournalCreatorFactory>);
|
new CSVDoc::SubViewFactoryWithCreator<TableSubView, JournalCreatorFactory>);
|
||||||
|
|
||||||
manager.add (CSMWorld::UniversalId::Type_TopicInfos,
|
manager.add (CSMWorld::UniversalId::Type_TopicInfos,
|
||||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, InfoCreatorFactory>);
|
new CSVDoc::SubViewFactoryWithCreator<TableSubView, InfoCreatorFactory>(false));
|
||||||
|
|
||||||
manager.add (CSMWorld::UniversalId::Type_JournalInfos,
|
manager.add (CSMWorld::UniversalId::Type_JournalInfos,
|
||||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, InfoCreatorFactory>);
|
new CSVDoc::SubViewFactoryWithCreator<TableSubView, InfoCreatorFactory>(false));
|
||||||
|
|
||||||
manager.add (CSMWorld::UniversalId::Type_Pathgrids,
|
manager.add (CSMWorld::UniversalId::Type_Pathgrids,
|
||||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, PathgridCreatorFactory>);
|
new CSVDoc::SubViewFactoryWithCreator<TableSubView, PathgridCreatorFactory>);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QtCore/qnamespace.h>
|
#include <QtCore/qnamespace.h>
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/misc/helpviewer.hpp>
|
#include <components/misc/helpviewer.hpp>
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
|
@ -248,6 +249,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
||||||
if (isInfoTable)
|
if (isInfoTable)
|
||||||
{
|
{
|
||||||
mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this);
|
mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this);
|
||||||
|
connect (this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords);
|
||||||
}
|
}
|
||||||
else if (isLtexTable)
|
else if (isLtexTable)
|
||||||
{
|
{
|
||||||
|
@ -563,6 +565,77 @@ void CSVWorld::Table::moveDownRecord()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSVWorld::Table::moveRecords(QDropEvent *event)
|
||||||
|
{
|
||||||
|
if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))
|
||||||
|
return;
|
||||||
|
|
||||||
|
QModelIndex targedIndex = indexAt(event->pos());
|
||||||
|
|
||||||
|
QModelIndexList selectedRows = selectionModel()->selectedRows();
|
||||||
|
int targetRowRaw = targedIndex.row();
|
||||||
|
int targetRow = mProxyModel->mapToSource (mProxyModel->index (targetRowRaw, 0)).row();
|
||||||
|
int baseRowRaw = targedIndex.row() - 1;
|
||||||
|
int baseRow = mProxyModel->mapToSource (mProxyModel->index (baseRowRaw, 0)).row();
|
||||||
|
int highestDifference = 0;
|
||||||
|
|
||||||
|
for (const auto& thisRowData : selectedRows)
|
||||||
|
{
|
||||||
|
int thisRow = mProxyModel->mapToSource (mProxyModel->index (thisRowData.row(), 0)).row();
|
||||||
|
if (std::abs(targetRow - thisRow) > highestDifference) highestDifference = std::abs(targetRow - thisRow);
|
||||||
|
if (thisRow - 1 < baseRow) baseRow = thisRow - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> newOrder (highestDifference + 1);
|
||||||
|
|
||||||
|
for (long unsigned int i = 0; i < newOrder.size(); ++i)
|
||||||
|
{
|
||||||
|
newOrder[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedRows.size() > 1)
|
||||||
|
{
|
||||||
|
Log(Debug::Warning) << "Move operation failed: Moving multiple selections isn't implemented.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& thisRowData : selectedRows)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Moving algorithm description
|
||||||
|
a) Remove the (ORIGIN + 1)th list member.
|
||||||
|
b) Add (ORIGIN+1)th list member with value TARGET
|
||||||
|
c) If ORIGIN > TARGET,d_INC; ELSE d_DEC
|
||||||
|
d_INC) increase all members after (and including) the TARGET by one, stop before hitting ORIGINth address
|
||||||
|
d_DEC) decrease all members after the ORIGIN by one, stop after hitting address TARGET
|
||||||
|
*/
|
||||||
|
|
||||||
|
int originRowRaw = thisRowData.row();
|
||||||
|
int originRow = mProxyModel->mapToSource (mProxyModel->index (originRowRaw, 0)).row();
|
||||||
|
|
||||||
|
newOrder.erase(newOrder.begin() + originRow - baseRow - 1);
|
||||||
|
newOrder.emplace(newOrder.begin() + originRow - baseRow - 1, targetRow - baseRow - 1);
|
||||||
|
|
||||||
|
if (originRow > targetRow)
|
||||||
|
{
|
||||||
|
for (int i = targetRow - baseRow - 1; i < originRow - baseRow - 1; ++i)
|
||||||
|
{
|
||||||
|
++newOrder[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = originRow - baseRow; i <= targetRow - baseRow - 1; ++i)
|
||||||
|
{
|
||||||
|
--newOrder[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
mDocument.getUndoStack().push (new CSMWorld::ReorderRowsCommand (
|
||||||
|
dynamic_cast<CSMWorld::IdTable&> (*mModel), baseRow + 1, newOrder));
|
||||||
|
}
|
||||||
|
|
||||||
void CSVWorld::Table::editCell()
|
void CSVWorld::Table::editCell()
|
||||||
{
|
{
|
||||||
emit editRequest(mEditIdAction->getCurrentId(), "");
|
emit editRequest(mEditIdAction->getCurrentId(), "");
|
||||||
|
|
|
@ -141,6 +141,8 @@ namespace CSVWorld
|
||||||
|
|
||||||
void moveDownRecord();
|
void moveDownRecord();
|
||||||
|
|
||||||
|
void moveRecords(QDropEvent *event);
|
||||||
|
|
||||||
void viewRecord();
|
void viewRecord();
|
||||||
|
|
||||||
void previewRecord();
|
void previewRecord();
|
||||||
|
|
|
@ -261,16 +261,10 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
|
||||||
return dsb;
|
return dsb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \todo implement size limit. QPlainTextEdit does not support a size limit.
|
||||||
case CSMWorld::ColumnBase::Display_LongString:
|
case CSMWorld::ColumnBase::Display_LongString:
|
||||||
{
|
|
||||||
QPlainTextEdit *edit = new QPlainTextEdit(parent);
|
|
||||||
edit->setUndoRedoEnabled (false);
|
|
||||||
return edit;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CSMWorld::ColumnBase::Display_LongString256:
|
case CSMWorld::ColumnBase::Display_LongString256:
|
||||||
{
|
{
|
||||||
/// \todo implement size limit. QPlainTextEdit does not support a size limit.
|
|
||||||
QPlainTextEdit *edit = new QPlainTextEdit(parent);
|
QPlainTextEdit *edit = new QPlainTextEdit(parent);
|
||||||
edit->setUndoRedoEnabled (false);
|
edit->setUndoRedoEnabled (false);
|
||||||
return edit;
|
return edit;
|
||||||
|
|
|
@ -19,9 +19,9 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
|
||||||
|
|
||||||
add_openmw_dir (mwrender
|
add_openmw_dir (mwrender
|
||||||
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
|
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
|
||||||
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
|
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager
|
||||||
bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation
|
bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation
|
||||||
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging
|
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwinput
|
add_openmw_dir (mwinput
|
||||||
|
@ -73,7 +73,7 @@ add_openmw_dir (mwworld
|
||||||
add_openmw_dir (mwphysics
|
add_openmw_dir (mwphysics
|
||||||
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
|
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
|
||||||
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
|
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
|
||||||
closestnotmeconvexresultcallback raycasting mtphysics
|
actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwclass
|
add_openmw_dir (mwclass
|
||||||
|
|
|
@ -189,6 +189,8 @@ namespace
|
||||||
|
|
||||||
~ScopedProfile()
|
~ScopedProfile()
|
||||||
{
|
{
|
||||||
|
if (!mStats.collectStats("engine"))
|
||||||
|
return;
|
||||||
const osg::Timer_t end = mTimer.tick();
|
const osg::Timer_t end = mTimer.tick();
|
||||||
const UserStats& stats = UserStatsValue<sType>::sValue;
|
const UserStats& stats = UserStatsValue<sType>::sValue;
|
||||||
|
|
||||||
|
@ -477,6 +479,11 @@ void OMW::Engine::addContentFile(const std::string& file)
|
||||||
mContentFiles.push_back(file);
|
mContentFiles.push_back(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::addGroundcoverFile(const std::string& file)
|
||||||
|
{
|
||||||
|
mGroundcoverFiles.emplace_back(file);
|
||||||
|
}
|
||||||
|
|
||||||
void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
|
void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
|
||||||
{
|
{
|
||||||
mSkipMenu = skipMenu;
|
mSkipMenu = skipMenu;
|
||||||
|
@ -606,8 +613,8 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
|
||||||
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel.";
|
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel.";
|
||||||
if (traits->blue < 8)
|
if (traits->blue < 8)
|
||||||
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel.";
|
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel.";
|
||||||
if (traits->depth < 8)
|
if (traits->depth < 24)
|
||||||
Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->red << " bits of depth precision.";
|
Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->depth << " bits of depth precision.";
|
||||||
|
|
||||||
traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel
|
traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel
|
||||||
}
|
}
|
||||||
|
@ -790,7 +797,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
|
|
||||||
// Create the world
|
// Create the world
|
||||||
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, std::unique_ptr<MWRender::Camera>(camera), mResourceSystem.get(), mWorkQueue.get(),
|
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, std::unique_ptr<MWRender::Camera>(camera), mResourceSystem.get(), mWorkQueue.get(),
|
||||||
mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName,
|
mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName,
|
||||||
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
||||||
mEnvironment.getWorld()->setupPlayer();
|
mEnvironment.getWorld()->setupPlayer();
|
||||||
|
|
||||||
|
@ -834,6 +841,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
// Create dialog system
|
// Create dialog system
|
||||||
mEnvironment.setJournal (new MWDialogue::Journal);
|
mEnvironment.setJournal (new MWDialogue::Journal);
|
||||||
mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage));
|
mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage));
|
||||||
|
mEnvironment.setResourceSystem(mResourceSystem.get());
|
||||||
|
|
||||||
// scripts
|
// scripts
|
||||||
if (mCompileAll)
|
if (mCompileAll)
|
||||||
|
@ -963,16 +971,29 @@ void OMW::Engine::go()
|
||||||
|
|
||||||
prepareEngine (settings);
|
prepareEngine (settings);
|
||||||
|
|
||||||
|
std::ofstream stats;
|
||||||
|
if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE"))
|
||||||
|
{
|
||||||
|
stats.open(path, std::ios_base::out);
|
||||||
|
if (stats.is_open())
|
||||||
|
Log(Debug::Info) << "Stats will be written to: " << path;
|
||||||
|
else
|
||||||
|
Log(Debug::Warning) << "Failed to open file for stats: " << path;
|
||||||
|
}
|
||||||
|
|
||||||
// Setup profiler
|
// Setup profiler
|
||||||
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler;
|
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler(stats.is_open());
|
||||||
|
|
||||||
initStatsHandler(*statshandler);
|
initStatsHandler(*statshandler);
|
||||||
|
|
||||||
mViewer->addEventHandler(statshandler);
|
mViewer->addEventHandler(statshandler);
|
||||||
|
|
||||||
osg::ref_ptr<Resource::StatsHandler> resourceshandler = new Resource::StatsHandler;
|
osg::ref_ptr<Resource::StatsHandler> resourceshandler = new Resource::StatsHandler(stats.is_open());
|
||||||
mViewer->addEventHandler(resourceshandler);
|
mViewer->addEventHandler(resourceshandler);
|
||||||
|
|
||||||
|
if (stats.is_open())
|
||||||
|
Resource::CollectStatistics(mViewer);
|
||||||
|
|
||||||
// Start the game
|
// Start the game
|
||||||
if (!mSaveGameFile.empty())
|
if (!mSaveGameFile.empty())
|
||||||
{
|
{
|
||||||
|
@ -997,14 +1018,6 @@ void OMW::Engine::go()
|
||||||
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
|
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream stats;
|
|
||||||
if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE"))
|
|
||||||
{
|
|
||||||
stats.open(path, std::ios_base::out);
|
|
||||||
if (!stats)
|
|
||||||
Log(Debug::Warning) << "Failed to open file for stats: " << path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the main rendering loop
|
// Start the main rendering loop
|
||||||
osg::Timer frameTimer;
|
osg::Timer frameTimer;
|
||||||
double simulationTime = 0.0;
|
double simulationTime = 0.0;
|
||||||
|
|
|
@ -94,6 +94,7 @@ namespace OMW
|
||||||
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
|
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
|
||||||
std::string mCellName;
|
std::string mCellName;
|
||||||
std::vector<std::string> mContentFiles;
|
std::vector<std::string> mContentFiles;
|
||||||
|
std::vector<std::string> mGroundcoverFiles;
|
||||||
|
|
||||||
bool mStereoEnabled;
|
bool mStereoEnabled;
|
||||||
bool mStereoOverride;
|
bool mStereoOverride;
|
||||||
|
@ -169,6 +170,7 @@ namespace OMW
|
||||||
* @param file - filename (extension is required)
|
* @param file - filename (extension is required)
|
||||||
*/
|
*/
|
||||||
void addContentFile(const std::string& file);
|
void addContentFile(const std::string& file);
|
||||||
|
void addGroundcoverFile(const std::string& file);
|
||||||
|
|
||||||
/// Disable or enable all sounds
|
/// Disable or enable all sounds
|
||||||
void setSoundUsage(bool soundUsage);
|
void setSoundUsage(bool soundUsage);
|
||||||
|
|
|
@ -62,6 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||||
("content", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
|
("content", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
|
||||||
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
|
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
|
||||||
|
|
||||||
|
("groundcover", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
|
||||||
|
->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon")
|
||||||
|
|
||||||
("no-sound", bpo::value<bool>()->implicit_value(true)
|
("no-sound", bpo::value<bool>()->implicit_value(true)
|
||||||
->default_value(false), "disable all sounds")
|
->default_value(false), "disable all sounds")
|
||||||
|
|
||||||
|
@ -190,11 +193,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringsVector::const_iterator it(content.begin());
|
for (auto& file : content)
|
||||||
StringsVector::const_iterator end(content.end());
|
|
||||||
for (; it != end; ++it)
|
|
||||||
{
|
{
|
||||||
engine.addContentFile(*it);
|
engine.addContentFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringsVector groundcover = variables["groundcover"].as<Files::EscapeStringVector>().toStdStringVector();
|
||||||
|
for (auto& file : groundcover)
|
||||||
|
{
|
||||||
|
engine.addGroundcoverFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// startup-settings
|
// startup-settings
|
||||||
|
@ -254,7 +261,19 @@ namespace
|
||||||
level = Debug::Debug;
|
level = Debug::Debug;
|
||||||
}
|
}
|
||||||
std::string_view s(msgCopy);
|
std::string_view s(msgCopy);
|
||||||
|
if (s.size() < 1024)
|
||||||
Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s);
|
Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (!s.empty())
|
||||||
|
{
|
||||||
|
size_t lineSize = 1;
|
||||||
|
while (lineSize < s.size() && s[lineSize - 1] != '\n')
|
||||||
|
lineSize++;
|
||||||
|
Log(level) << s.substr(0, s[lineSize - 1] == '\n' ? lineSize - 1 : lineSize);
|
||||||
|
s = s.substr(lineSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include <components/resource/resourcesystem.hpp>
|
||||||
|
|
||||||
#include "world.hpp"
|
#include "world.hpp"
|
||||||
#include "scriptmanager.hpp"
|
#include "scriptmanager.hpp"
|
||||||
#include "dialoguemanager.hpp"
|
#include "dialoguemanager.hpp"
|
||||||
|
@ -18,8 +20,8 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr;
|
||||||
|
|
||||||
MWBase::Environment::Environment()
|
MWBase::Environment::Environment()
|
||||||
: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr),
|
: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr),
|
||||||
mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr),
|
mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr),
|
||||||
mFrameDuration (0), mFrameRateLimit(0.f), mVrMode(false)
|
mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f), mVrMode(false)
|
||||||
{
|
{
|
||||||
assert (!sThis);
|
assert (!sThis);
|
||||||
sThis = this;
|
sThis = this;
|
||||||
|
@ -76,6 +78,11 @@ void MWBase::Environment::setStateManager (StateManager *stateManager)
|
||||||
mStateManager = stateManager;
|
mStateManager = stateManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem)
|
||||||
|
{
|
||||||
|
mResourceSystem = resourceSystem;
|
||||||
|
}
|
||||||
|
|
||||||
void MWBase::Environment::setFrameDuration (float duration)
|
void MWBase::Environment::setFrameDuration (float duration)
|
||||||
{
|
{
|
||||||
mFrameDuration = duration;
|
mFrameDuration = duration;
|
||||||
|
@ -168,6 +175,11 @@ MWBase::StateManager *MWBase::Environment::getStateManager() const
|
||||||
return mStateManager;
|
return mStateManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const
|
||||||
|
{
|
||||||
|
return mResourceSystem;
|
||||||
|
}
|
||||||
|
|
||||||
float MWBase::Environment::getFrameDuration() const
|
float MWBase::Environment::getFrameDuration() const
|
||||||
{
|
{
|
||||||
return mFrameDuration;
|
return mFrameDuration;
|
||||||
|
|
|
@ -6,6 +6,11 @@ namespace osg
|
||||||
class Stats;
|
class Stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Resource
|
||||||
|
{
|
||||||
|
class ResourceSystem;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWBase
|
namespace MWBase
|
||||||
{
|
{
|
||||||
class World;
|
class World;
|
||||||
|
@ -37,6 +42,7 @@ namespace MWBase
|
||||||
Journal *mJournal;
|
Journal *mJournal;
|
||||||
InputManager *mInputManager;
|
InputManager *mInputManager;
|
||||||
StateManager *mStateManager;
|
StateManager *mStateManager;
|
||||||
|
Resource::ResourceSystem *mResourceSystem;
|
||||||
float mFrameDuration;
|
float mFrameDuration;
|
||||||
float mFrameRateLimit;
|
float mFrameRateLimit;
|
||||||
bool mVrMode;
|
bool mVrMode;
|
||||||
|
@ -71,6 +77,8 @@ namespace MWBase
|
||||||
|
|
||||||
void setStateManager (StateManager *stateManager);
|
void setStateManager (StateManager *stateManager);
|
||||||
|
|
||||||
|
void setResourceSystem (Resource::ResourceSystem *resourceSystem);
|
||||||
|
|
||||||
void setFrameDuration (float duration);
|
void setFrameDuration (float duration);
|
||||||
///< Set length of current frame in seconds.
|
///< Set length of current frame in seconds.
|
||||||
|
|
||||||
|
@ -99,6 +107,8 @@ namespace MWBase
|
||||||
|
|
||||||
StateManager *getStateManager() const;
|
StateManager *getStateManager() const;
|
||||||
|
|
||||||
|
Resource::ResourceSystem *getResourceSystem() const;
|
||||||
|
|
||||||
float getFrameDuration() const;
|
float getFrameDuration() const;
|
||||||
|
|
||||||
void cleanup();
|
void cleanup();
|
||||||
|
|
|
@ -199,6 +199,7 @@ namespace MWBase
|
||||||
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) = 0;
|
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) = 0;
|
||||||
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) = 0;
|
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) = 0;
|
||||||
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0;
|
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0;
|
||||||
|
virtual std::map<int, MWWorld::Ptr> getActorsFollowingByIndex(const MWWorld::Ptr& actor) = 0;
|
||||||
|
|
||||||
///Returns a list of actors who are fighting the given actor within the fAlarmDistance
|
///Returns a list of actors who are fighting the given actor within the fAlarmDistance
|
||||||
/** ie AiCombat is active and the target is the actor **/
|
/** ie AiCombat is active and the target is the actor **/
|
||||||
|
|
|
@ -510,7 +510,7 @@ namespace MWBase
|
||||||
|
|
||||||
/// \todo this does not belong here
|
/// \todo this does not belong here
|
||||||
virtual void screenshot (osg::Image* image, int w, int h) = 0;
|
virtual void screenshot (osg::Image* image, int w, int h) = 0;
|
||||||
virtual bool screenshot360 (osg::Image* image, std::string settingStr) = 0;
|
virtual bool screenshot360 (osg::Image* image) = 0;
|
||||||
|
|
||||||
/// Find default position inside exterior cell specified by name
|
/// Find default position inside exterior cell specified by name
|
||||||
/// \return false if exterior with given name not exists, true otherwise
|
/// \return false if exterior with given name not exists, true otherwise
|
||||||
|
@ -532,7 +532,7 @@ namespace MWBase
|
||||||
/// Returns true if levitation spell effect is allowed.
|
/// Returns true if levitation spell effect is allowed.
|
||||||
virtual bool isLevitationEnabled() const = 0;
|
virtual bool isLevitationEnabled() const = 0;
|
||||||
|
|
||||||
virtual bool getGodModeState() = 0;
|
virtual bool getGodModeState() const = 0;
|
||||||
|
|
||||||
virtual bool toggleGodMode() = 0;
|
virtual bool toggleGodMode() = 0;
|
||||||
|
|
||||||
|
|
|
@ -455,6 +455,7 @@ namespace MWGui
|
||||||
setTitle(mPtr.getClass().getName(mPtr));
|
setTitle(mPtr.getClass().getName(mPtr));
|
||||||
|
|
||||||
updateTopics();
|
updateTopics();
|
||||||
|
updateTopicsPane(); // force update for new services
|
||||||
|
|
||||||
updateDisposition();
|
updateDisposition();
|
||||||
restock();
|
restock();
|
||||||
|
@ -491,12 +492,14 @@ namespace MWGui
|
||||||
mHistoryContents.clear();
|
mHistoryContents.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogueWindow::setKeywords(std::list<std::string> keyWords)
|
bool DialogueWindow::setKeywords(std::list<std::string> keyWords)
|
||||||
{
|
{
|
||||||
if (mKeywords == keyWords && isCompanion() == mIsCompanion)
|
if (mKeywords == keyWords && isCompanion() == mIsCompanion)
|
||||||
return;
|
return false;
|
||||||
mIsCompanion = isCompanion();
|
mIsCompanion = isCompanion();
|
||||||
mKeywords = keyWords;
|
mKeywords = keyWords;
|
||||||
|
updateTopicsPane();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogueWindow::updateTopicsPane()
|
void DialogueWindow::updateTopicsPane()
|
||||||
|
@ -560,6 +563,8 @@ namespace MWGui
|
||||||
mTopicsList->adjustSize();
|
mTopicsList->adjustSize();
|
||||||
|
|
||||||
updateHistory();
|
updateHistory();
|
||||||
|
// The topics list has been regenerated so topic formatting needs to be updated
|
||||||
|
updateTopicFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogueWindow::updateHistory(bool scrollbar)
|
void DialogueWindow::updateHistory(bool scrollbar)
|
||||||
|
@ -762,8 +767,8 @@ namespace MWGui
|
||||||
|
|
||||||
void DialogueWindow::updateTopics()
|
void DialogueWindow::updateTopics()
|
||||||
{
|
{
|
||||||
setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics());
|
// Topic formatting needs to be updated regardless of whether the topic list has changed
|
||||||
updateTopicsPane();
|
if (!setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics()))
|
||||||
updateTopicFormat();
|
updateTopicFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,8 @@ namespace MWGui
|
||||||
|
|
||||||
void setPtr(const MWWorld::Ptr& actor) override;
|
void setPtr(const MWWorld::Ptr& actor) override;
|
||||||
|
|
||||||
void setKeywords(std::list<std::string> keyWord);
|
/// @return true if stale keywords were updated successfully
|
||||||
|
bool setKeywords(std::list<std::string> keyWord);
|
||||||
|
|
||||||
void addResponse (const std::string& title, const std::string& text, bool needMargin = true);
|
void addResponse (const std::string& title, const std::string& text, bool needMargin = true);
|
||||||
|
|
||||||
|
|
|
@ -30,14 +30,18 @@ namespace MWGui
|
||||||
|
|
||||||
// vanilla game does not show any text after the last EOL tag.
|
// vanilla game does not show any text after the last EOL tag.
|
||||||
const std::string lowerText = Misc::StringUtils::lowerCase(mText);
|
const std::string lowerText = Misc::StringUtils::lowerCase(mText);
|
||||||
int brIndex = lowerText.rfind("<br>");
|
size_t brIndex = lowerText.rfind("<br>");
|
||||||
int pIndex = lowerText.rfind("<p>");
|
size_t pIndex = lowerText.rfind("<p>");
|
||||||
if (brIndex == pIndex)
|
mPlainTextEnd = 0;
|
||||||
mText = "";
|
if (brIndex != pIndex)
|
||||||
else if (brIndex > pIndex)
|
{
|
||||||
mText = mText.substr(0, brIndex+4);
|
if (brIndex != std::string::npos && pIndex != std::string::npos)
|
||||||
|
mPlainTextEnd = std::max(brIndex, pIndex);
|
||||||
|
else if (brIndex != std::string::npos)
|
||||||
|
mPlainTextEnd = brIndex;
|
||||||
else
|
else
|
||||||
mText = mText.substr(0, pIndex+3);
|
mPlainTextEnd = pIndex;
|
||||||
|
}
|
||||||
|
|
||||||
registerTag("br", Event_BrTag);
|
registerTag("br", Event_BrTag);
|
||||||
registerTag("p", Event_PTag);
|
registerTag("p", Event_PTag);
|
||||||
|
@ -103,6 +107,7 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
if (!mIgnoreLineEndings || ch != '\n')
|
if (!mIgnoreLineEndings || ch != '\n')
|
||||||
{
|
{
|
||||||
|
if (mIndex < mPlainTextEnd)
|
||||||
mBuffer.push_back(ch);
|
mBuffer.push_back(ch);
|
||||||
mIgnoreLineEndings = false;
|
mIgnoreLineEndings = false;
|
||||||
mIgnoreNewlineTags = false;
|
mIgnoreNewlineTags = false;
|
||||||
|
|
|
@ -73,6 +73,8 @@ namespace MWGui
|
||||||
bool mClosingTag;
|
bool mClosingTag;
|
||||||
std::map<std::string, Events> mTagTypes;
|
std::map<std::string, Events> mTagTypes;
|
||||||
std::string mBuffer;
|
std::string mBuffer;
|
||||||
|
|
||||||
|
size_t mPlainTextEnd;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Paginator
|
class Paginator
|
||||||
|
|
|
@ -598,7 +598,7 @@ namespace MWGui
|
||||||
// effect box can have variable width -> variable left coordinate
|
// effect box can have variable width -> variable left coordinate
|
||||||
int effectsDx = 0;
|
int effectsDx = 0;
|
||||||
if (!mMinimapBox->getVisible ())
|
if (!mMinimapBox->getVisible ())
|
||||||
effectsDx = (viewSize.width - mMinimapBoxBaseRight) - (viewSize.width - mEffectBoxBaseRight);
|
effectsDx = mEffectBoxBaseRight - mMinimapBoxBaseRight;
|
||||||
|
|
||||||
mMapVisible = mMinimapBox->getVisible ();
|
mMapVisible = mMinimapBox->getVisible ();
|
||||||
if (!mMapVisible)
|
if (!mMapVisible)
|
||||||
|
|
|
@ -561,7 +561,7 @@ namespace
|
||||||
if (mAllQuests)
|
if (mAllQuests)
|
||||||
{
|
{
|
||||||
SetNamesInactive setInactive(list);
|
SetNamesInactive setInactive(list);
|
||||||
mModel->visitQuestNames(!mAllQuests, setInactive);
|
mModel->visitQuestNames(false, setInactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
MWBase::Environment::get().getWindowManager()->playSound("book page");
|
MWBase::Environment::get().getWindowManager()->playSound("book page");
|
||||||
|
|
|
@ -72,7 +72,7 @@ void KeyboardNavigation::saveFocus(int mode)
|
||||||
{
|
{
|
||||||
mKeyFocus[mode] = focus;
|
mKeyFocus[mode] = focus;
|
||||||
}
|
}
|
||||||
else
|
else if(shouldAcceptKeyFocus(mCurrentFocus))
|
||||||
{
|
{
|
||||||
mKeyFocus[mode] = mCurrentFocus;
|
mKeyFocus[mode] = mCurrentFocus;
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,7 @@ void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||||
void styleFocusedButton(MyGUI::Widget* w)
|
void styleFocusedButton(MyGUI::Widget* w)
|
||||||
{
|
{
|
||||||
if (w)
|
if (w)
|
||||||
|
@ -113,6 +114,7 @@ void styleFocusedButton(MyGUI::Widget* w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root)
|
bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root)
|
||||||
{
|
{
|
||||||
|
@ -136,7 +138,9 @@ void KeyboardNavigation::onFrame()
|
||||||
|
|
||||||
if (focus == mCurrentFocus)
|
if (focus == mCurrentFocus)
|
||||||
{
|
{
|
||||||
|
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||||
styleFocusedButton(mCurrentFocus);
|
styleFocusedButton(mCurrentFocus);
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,19 +151,21 @@ void KeyboardNavigation::onFrame()
|
||||||
focus = mCurrentFocus;
|
focus = mCurrentFocus;
|
||||||
}
|
}
|
||||||
|
|
||||||
// style highlighted button (won't be needed for MyGUI 3.2.3)
|
|
||||||
if (focus != mCurrentFocus)
|
if (focus != mCurrentFocus)
|
||||||
{
|
{
|
||||||
|
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||||
if (mCurrentFocus)
|
if (mCurrentFocus)
|
||||||
{
|
{
|
||||||
if (MyGUI::Button* b = mCurrentFocus->castType<MyGUI::Button>(false))
|
if (MyGUI::Button* b = mCurrentFocus->castType<MyGUI::Button>(false))
|
||||||
b->_setWidgetState("normal");
|
b->_setWidgetState("normal");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
mCurrentFocus = focus;
|
mCurrentFocus = focus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||||
styleFocusedButton(mCurrentFocus);
|
styleFocusedButton(mCurrentFocus);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus)
|
void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus)
|
||||||
|
|
|
@ -28,10 +28,7 @@ namespace MWGui
|
||||||
|
|
||||||
MessageBoxManager::~MessageBoxManager ()
|
MessageBoxManager::~MessageBoxManager ()
|
||||||
{
|
{
|
||||||
for (MessageBox* messageBox : mMessageBoxes)
|
MessageBoxManager::clear();
|
||||||
{
|
|
||||||
delete messageBox;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageBoxManager::getMessagesCount()
|
int MessageBoxManager::getMessagesCount()
|
||||||
|
|
|
@ -306,7 +306,7 @@ namespace MWGui
|
||||||
mWaterTextureSize->setIndexSelected(2);
|
mWaterTextureSize->setIndexSelected(2);
|
||||||
|
|
||||||
int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water");
|
int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water");
|
||||||
waterReflectionDetail = std::min(4, std::max(0, waterReflectionDetail));
|
waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail));
|
||||||
mWaterReflectionDetail->setIndexSelected(waterReflectionDetail);
|
mWaterReflectionDetail->setIndexSelected(waterReflectionDetail);
|
||||||
|
|
||||||
mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video"));
|
mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video"));
|
||||||
|
@ -404,7 +404,7 @@ namespace MWGui
|
||||||
|
|
||||||
void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos)
|
void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos)
|
||||||
{
|
{
|
||||||
unsigned int level = std::min((unsigned int)4, (unsigned int)pos);
|
unsigned int level = std::min((unsigned int)5, (unsigned int)pos);
|
||||||
Settings::Manager::setInt("reflection detail", "Water", level);
|
Settings::Manager::setInt("reflection detail", "Water", level);
|
||||||
apply();
|
apply();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <MyGUI_ProgressBar.h>
|
#include <MyGUI_ProgressBar.h>
|
||||||
#include <MyGUI_ImageBox.h>
|
#include <MyGUI_ImageBox.h>
|
||||||
#include <MyGUI_InputManager.h>
|
#include <MyGUI_InputManager.h>
|
||||||
|
#include <MyGUI_LanguageManager.h>
|
||||||
#include <MyGUI_Gui.h>
|
#include <MyGUI_Gui.h>
|
||||||
|
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
|
@ -340,19 +341,22 @@ namespace MWGui
|
||||||
int max = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iLevelUpTotal")->mValue.getInteger();
|
int max = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iLevelUpTotal")->mValue.getInteger();
|
||||||
getWidget(levelWidget, i==0 ? "Level_str" : "LevelText");
|
getWidget(levelWidget, i==0 ? "Level_str" : "LevelText");
|
||||||
|
|
||||||
std::string detail;
|
|
||||||
for (int i = 0; i < ESM::Attribute::Length; ++i)
|
|
||||||
{
|
|
||||||
if (auto increase = PCstats.getLevelUpAttributeIncrease(i))
|
|
||||||
detail += (detail.empty() ? "" : "\n") + ESM::Attribute::sAttributeNames[i] + " x" + MyGUI::utility::toString(increase);
|
|
||||||
}
|
|
||||||
if (!detail.empty())
|
|
||||||
levelWidget->setUserString("Caption_LevelDetailText", detail);
|
|
||||||
levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress()));
|
levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress()));
|
||||||
levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max));
|
levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max));
|
||||||
levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/"
|
levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/"
|
||||||
+ MyGUI::utility::toString(max));
|
+ MyGUI::utility::toString(max));
|
||||||
}
|
}
|
||||||
|
std::stringstream detail;
|
||||||
|
for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute)
|
||||||
|
{
|
||||||
|
float mult = PCstats.getLevelupAttributeMultiplier(attribute);
|
||||||
|
mult = std::min(mult, 100 - PCstats.getAttribute(attribute).getBase());
|
||||||
|
if (mult > 1)
|
||||||
|
detail << (detail.str().empty() ? "" : "\n") << "#{"
|
||||||
|
<< MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[attribute])
|
||||||
|
<< "} x" << MyGUI::utility::toString(mult);
|
||||||
|
}
|
||||||
|
levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str()));
|
||||||
|
|
||||||
setFactions(PCstats.getFactionRanks());
|
setFactions(PCstats.getFactionRanks());
|
||||||
setExpelled(PCstats.getExpelled ());
|
setExpelled(PCstats.getExpelled ());
|
||||||
|
|
|
@ -333,12 +333,8 @@ namespace MWInput
|
||||||
|
|
||||||
void ActionManager::screenshot()
|
void ActionManager::screenshot()
|
||||||
{
|
{
|
||||||
bool regularScreenshot = true;
|
const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video");
|
||||||
|
bool regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0;
|
||||||
std::string settingStr;
|
|
||||||
|
|
||||||
settingStr = Settings::Manager::getString("screenshot type","Video");
|
|
||||||
regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0;
|
|
||||||
|
|
||||||
if (regularScreenshot)
|
if (regularScreenshot)
|
||||||
{
|
{
|
||||||
|
@ -349,7 +345,7 @@ namespace MWInput
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Image> screenshot (new osg::Image);
|
osg::ref_ptr<osg::Image> screenshot (new osg::Image);
|
||||||
|
|
||||||
if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), settingStr))
|
if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get()))
|
||||||
{
|
{
|
||||||
(*mScreenCaptureOperation) (*(screenshot.get()), 0);
|
(*mScreenCaptureOperation) (*(screenshot.get()), 0);
|
||||||
// FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason
|
// FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason
|
||||||
|
|
|
@ -179,16 +179,16 @@ namespace MWInput
|
||||||
, mDragDrop(false)
|
, mDragDrop(false)
|
||||||
{
|
{
|
||||||
std::string file = userFileExists ? userFile : "";
|
std::string file = userFileExists ? userFile : "";
|
||||||
mInputBinder = new InputControlSystem(file);
|
mInputBinder = std::make_unique<InputControlSystem>(file);
|
||||||
mListener = new BindingsListener(mInputBinder, this);
|
mListener = std::make_unique<BindingsListener>(mInputBinder.get(), this);
|
||||||
mInputBinder->setDetectingBindingListener(mListener);
|
mInputBinder->setDetectingBindingListener(mListener.get());
|
||||||
|
|
||||||
loadKeyDefaults();
|
loadKeyDefaults();
|
||||||
loadControllerDefaults();
|
loadControllerDefaults();
|
||||||
|
|
||||||
for (int i = 0; i < A_Last; ++i)
|
for (int i = 0; i < A_Last; ++i)
|
||||||
{
|
{
|
||||||
mInputBinder->getChannel(i)->addListener(mListener);
|
mInputBinder->getChannel(i)->addListener(mListener.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +200,6 @@ namespace MWInput
|
||||||
BindingsManager::~BindingsManager()
|
BindingsManager::~BindingsManager()
|
||||||
{
|
{
|
||||||
mInputBinder->save(mUserFile);
|
mInputBinder->save(mUserFile);
|
||||||
delete mInputBinder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BindingsManager::update(float dt)
|
void BindingsManager::update(float dt)
|
||||||
|
@ -323,7 +322,7 @@ namespace MWInput
|
||||||
&& mInputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS
|
&& mInputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS
|
||||||
&& mInputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) == ICS::InputControlSystem::MouseWheelClick::UNASSIGNED))
|
&& mInputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) == ICS::InputControlSystem::MouseWheelClick::UNASSIGNED))
|
||||||
{
|
{
|
||||||
clearAllKeyBindings(mInputBinder, control);
|
clearAllKeyBindings(mInputBinder.get(), control);
|
||||||
|
|
||||||
if (defaultKeyBindings.find(i) != defaultKeyBindings.end()
|
if (defaultKeyBindings.find(i) != defaultKeyBindings.end()
|
||||||
&& (force || !mInputBinder->isKeyBound(defaultKeyBindings[i])))
|
&& (force || !mInputBinder->isKeyBound(defaultKeyBindings[i])))
|
||||||
|
@ -410,7 +409,7 @@ namespace MWInput
|
||||||
if (!controlExists || force || (mInputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS::InputControlSystem::UNASSIGNED &&
|
if (!controlExists || force || (mInputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS::InputControlSystem::UNASSIGNED &&
|
||||||
mInputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS))
|
mInputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS))
|
||||||
{
|
{
|
||||||
clearAllControllerBindings(mInputBinder, control);
|
clearAllControllerBindings(mInputBinder.get(), control);
|
||||||
|
|
||||||
if (defaultButtonBindings.find(i) != defaultButtonBindings.end()
|
if (defaultButtonBindings.find(i) != defaultButtonBindings.end()
|
||||||
&& (force || !mInputBinder->isJoystickButtonBound(sFakeDeviceId, defaultButtonBindings[i])))
|
&& (force || !mInputBinder->isJoystickButtonBound(sFakeDeviceId, defaultButtonBindings[i])))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef MWINPUT_MWBINDINGSMANAGER_H
|
#ifndef MWINPUT_MWBINDINGSMANAGER_H
|
||||||
#define MWINPUT_MWBINDINGSMANAGER_H
|
#define MWINPUT_MWBINDINGSMANAGER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -71,8 +72,8 @@ namespace MWInput
|
||||||
private:
|
private:
|
||||||
void setupSDLKeyMappings();
|
void setupSDLKeyMappings();
|
||||||
|
|
||||||
InputControlSystem* mInputBinder;
|
std::unique_ptr<InputControlSystem> mInputBinder;
|
||||||
BindingsListener* mListener;
|
std::unique_ptr<BindingsListener> mListener;
|
||||||
|
|
||||||
std::string mUserFile;
|
std::string mUserFile;
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,29 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
|
||||||
magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
|
magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void forEachFollowingPackage(MWMechanics::Actors::PtrActorMap& actors, const MWWorld::Ptr& actor, const MWWorld::Ptr& player, T&& func)
|
||||||
|
{
|
||||||
|
for(auto& iter : actors)
|
||||||
|
{
|
||||||
|
const MWWorld::Ptr &iteratedActor = iter.first;
|
||||||
|
if (iteratedActor == player || iteratedActor == actor)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const MWMechanics::CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
||||||
|
if (stats.isDead())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// An actor counts as following if AiFollow is the current AiPackage,
|
||||||
|
// or there are only Combat and Wander packages before the AiFollow package
|
||||||
|
for (const auto& package : stats.getAiSequence())
|
||||||
|
{
|
||||||
|
if(!func(iter, package))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
|
@ -1407,6 +1430,13 @@ namespace MWMechanics
|
||||||
if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name())
|
if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name())
|
||||||
inventoryStore.unequipItem(*heldIter, ptr);
|
inventoryStore.unequipItem(*heldIter, ptr);
|
||||||
}
|
}
|
||||||
|
else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name())
|
||||||
|
{
|
||||||
|
// For hostile NPCs, see if they have anything better to equip first
|
||||||
|
auto shield = inventoryStore.getPreferredShield(ptr);
|
||||||
|
if(shield != inventoryStore.end())
|
||||||
|
inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||||
|
|
||||||
|
@ -2512,26 +2542,14 @@ namespace MWMechanics
|
||||||
std::list<MWWorld::Ptr> Actors::getActorsFollowing(const MWWorld::Ptr& actor)
|
std::list<MWWorld::Ptr> Actors::getActorsFollowing(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
std::list<MWWorld::Ptr> list;
|
std::list<MWWorld::Ptr> list;
|
||||||
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
|
||||||
{
|
|
||||||
const MWWorld::Ptr &iteratedActor = iter->first;
|
|
||||||
if (iteratedActor == getPlayer() || iteratedActor == actor)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
|
||||||
if (stats.isDead())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// An actor counts as following if AiFollow is the current AiPackage,
|
|
||||||
// or there are only Combat and Wander packages before the AiFollow package
|
|
||||||
for (const auto& package : stats.getAiSequence())
|
|
||||||
{
|
{
|
||||||
if (package->followTargetThroughDoors() && package->getTarget() == actor)
|
if (package->followTargetThroughDoors() && package->getTarget() == actor)
|
||||||
list.push_back(iteratedActor);
|
list.push_back(iter.first);
|
||||||
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
|
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
|
||||||
break;
|
return false;
|
||||||
}
|
return true;
|
||||||
}
|
});
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2575,32 +2593,38 @@ namespace MWMechanics
|
||||||
std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
|
std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
|
||||||
{
|
{
|
||||||
std::list<int> list;
|
std::list<int> list;
|
||||||
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
|
||||||
{
|
|
||||||
const MWWorld::Ptr &iteratedActor = iter->first;
|
|
||||||
if (iteratedActor == getPlayer() || iteratedActor == actor)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
|
||||||
if (stats.isDead())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// An actor counts as following if AiFollow is the current AiPackage,
|
|
||||||
// or there are only Combat and Wander packages before the AiFollow package
|
|
||||||
for (const auto& package : stats.getAiSequence())
|
|
||||||
{
|
{
|
||||||
if (package->followTargetThroughDoors() && package->getTarget() == actor)
|
if (package->followTargetThroughDoors() && package->getTarget() == actor)
|
||||||
{
|
{
|
||||||
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
|
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
|
||||||
break;
|
return false;
|
||||||
}
|
}
|
||||||
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
|
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
|
||||||
break;
|
return false;
|
||||||
}
|
return true;
|
||||||
}
|
});
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::map<int, MWWorld::Ptr> Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor)
|
||||||
|
{
|
||||||
|
std::map<int, MWWorld::Ptr> map;
|
||||||
|
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
|
||||||
|
{
|
||||||
|
if (package->followTargetThroughDoors() && package->getTarget() == actor)
|
||||||
|
{
|
||||||
|
int index = static_cast<const AiFollow*>(package.get())->getFollowIndex();
|
||||||
|
map[index] = iter.first;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
std::list<MWWorld::Ptr> Actors::getActorsFighting(const MWWorld::Ptr& actor) {
|
std::list<MWWorld::Ptr> Actors::getActorsFighting(const MWWorld::Ptr& actor) {
|
||||||
std::list<MWWorld::Ptr> list;
|
std::list<MWWorld::Ptr> list;
|
||||||
std::vector<MWWorld::Ptr> neighbors;
|
std::vector<MWWorld::Ptr> neighbors;
|
||||||
|
|
|
@ -181,6 +181,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
/// Get the list of AiFollow::mFollowIndex for all actors following this target
|
/// Get the list of AiFollow::mFollowIndex for all actors following this target
|
||||||
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);
|
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);
|
||||||
|
std::map<int, MWWorld::Ptr> getActorsFollowingByIndex(const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
///Returns the list of actors which are fighting the given actor
|
///Returns the list of actors which are fighting the given actor
|
||||||
/**ie AiCombat is active and the target is the actor **/
|
/**ie AiCombat is active and the target is the actor **/
|
||||||
|
|
|
@ -14,6 +14,16 @@
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
#include "steering.hpp"
|
#include "steering.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
osg::Vec3f::value_type getHalfExtents(const MWWorld::ConstPtr& actor)
|
||||||
|
{
|
||||||
|
if(actor.getClass().isNpc())
|
||||||
|
return 64;
|
||||||
|
return MWBase::Environment::get().getWorld()->getHalfExtents(actor).y();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
int AiFollow::mFollowIndexCounter = 0;
|
int AiFollow::mFollowIndexCounter = 0;
|
||||||
|
@ -113,24 +123,23 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
||||||
if (!mActive)
|
if (!mActive)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// The distances below are approximations based on observations of the original engine.
|
// In the original engine the first follower stays closer to the player than any subsequent followers.
|
||||||
// If only one actor is following the target, it uses 186.
|
// Followers beyond the first usually attempt to stand inside each other.
|
||||||
// If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target.
|
osg::Vec3f::value_type floatingDistance = 0;
|
||||||
|
auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target);
|
||||||
short followDistance = 186;
|
if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex)
|
||||||
std::list<int> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target);
|
|
||||||
if (followers.size() >= 2)
|
|
||||||
{
|
{
|
||||||
followDistance = 313;
|
for(auto& follower : followers)
|
||||||
short i = 0;
|
|
||||||
followers.sort();
|
|
||||||
for (int followIndex : followers)
|
|
||||||
{
|
{
|
||||||
if (followIndex == mFollowIndex)
|
auto halfExtent = getHalfExtents(follower.second);
|
||||||
followDistance += 130 * i;
|
if(halfExtent > floatingDistance)
|
||||||
++i;
|
floatingDistance = halfExtent;
|
||||||
}
|
}
|
||||||
|
floatingDistance += 128;
|
||||||
}
|
}
|
||||||
|
floatingDistance += getHalfExtents(target) + 64;
|
||||||
|
floatingDistance += getHalfExtents(actor) * 2;
|
||||||
|
short followDistance = static_cast<short>(floatingDistance);
|
||||||
|
|
||||||
if (!mAlwaysFollow) //Update if you only follow for a bit
|
if (!mAlwaysFollow) //Update if you only follow for a bit
|
||||||
{
|
{
|
||||||
|
|
|
@ -96,6 +96,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
|
|
||||||
const float distToTarget = distance(position, dest);
|
const float distToTarget = distance(position, dest);
|
||||||
const bool isDestReached = (distToTarget <= destTolerance);
|
const bool isDestReached = (distToTarget <= destTolerance);
|
||||||
|
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
||||||
|
|
||||||
if (!isDestReached && mTimer > AI_REACTION_TIME)
|
if (!isDestReached && mTimer > AI_REACTION_TIME)
|
||||||
{
|
{
|
||||||
|
@ -104,7 +105,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
|
|
||||||
const bool wasShortcutting = mIsShortcutting;
|
const bool wasShortcutting = mIsShortcutting;
|
||||||
bool destInLOS = false;
|
bool destInLOS = false;
|
||||||
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
|
||||||
|
|
||||||
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
|
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
|
||||||
mIsShortcutting = actorCanMoveByZ
|
mIsShortcutting = actorCanMoveByZ
|
||||||
|
@ -150,7 +150,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
+ 1.2 * std::max(halfExtents.x(), halfExtents.y());
|
+ 1.2 * std::max(halfExtents.x(), halfExtents.y());
|
||||||
const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance);
|
const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance);
|
||||||
|
|
||||||
mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE);
|
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||||
|
mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE,
|
||||||
|
/*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ);
|
||||||
|
|
||||||
if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished
|
if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished
|
||||||
{
|
{
|
||||||
|
@ -160,6 +162,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
world->removeActorPath(actor);
|
world->removeActorPath(actor);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (mPathFinder.getPath().empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
||||||
|
|
||||||
|
@ -178,7 +182,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||||
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
|
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
|
||||||
mObstacleCheck.update(actor, destination, duration);
|
mObstacleCheck.update(actor, destination, duration);
|
||||||
|
|
||||||
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
|
||||||
if (smoothMovement)
|
if (smoothMovement)
|
||||||
{
|
{
|
||||||
const float smoothTurnReservedDist = 150;
|
const float smoothTurnReservedDist = 150;
|
||||||
|
|
|
@ -403,7 +403,7 @@ const AiPackage& MWMechanics::AiSequence::getActivePackage()
|
||||||
void AiSequence::fill(const ESM::AIPackageList &list)
|
void AiSequence::fill(const ESM::AIPackageList &list)
|
||||||
{
|
{
|
||||||
// If there is more than one package in the list, enable repeating
|
// If there is more than one package in the list, enable repeating
|
||||||
if (!list.mList.empty() && list.mList.begin() != (list.mList.end()-1))
|
if (list.mList.size() >= 2)
|
||||||
mRepeat = true;
|
mRepeat = true;
|
||||||
|
|
||||||
for (const auto& esmPackage : list.mList)
|
for (const auto& esmPackage : list.mList)
|
||||||
|
@ -459,8 +459,15 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (auto& container : sequence.mPackages)
|
for (auto& container : sequence.mPackages)
|
||||||
{
|
{
|
||||||
if (isActualAiPackage(static_cast<AiPackageTypeId>(container.mType)))
|
switch (container.mType)
|
||||||
count++;
|
{
|
||||||
|
case ESM::AiSequence::Ai_Wander:
|
||||||
|
case ESM::AiSequence::Ai_Travel:
|
||||||
|
case ESM::AiSequence::Ai_Escort:
|
||||||
|
case ESM::AiSequence::Ai_Follow:
|
||||||
|
case ESM::AiSequence::Ai_Activate:
|
||||||
|
++count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count > 1)
|
if (count > 1)
|
||||||
|
|
|
@ -99,7 +99,6 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
AiPackage::Options options;
|
AiPackage::Options options;
|
||||||
options.mUseVariableSpeed = true;
|
options.mUseVariableSpeed = true;
|
||||||
options.mRepeat = false;
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -452,9 +452,9 @@ std::string CharacterController::fallbackShortWeaponGroup(const std::string& bas
|
||||||
const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType);
|
const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType);
|
||||||
|
|
||||||
// For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones
|
// For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones
|
||||||
if (isRealWeapon && weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee)
|
if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee)
|
||||||
groupName += twoHandFallback;
|
groupName += twoHandFallback;
|
||||||
else if (isRealWeapon)
|
else
|
||||||
groupName += oneHandFallback;
|
groupName += oneHandFallback;
|
||||||
|
|
||||||
// Special case for crossbows - we shouls apply 1h animations a fallback only for lower body
|
// Special case for crossbows - we shouls apply 1h animations a fallback only for lower body
|
||||||
|
@ -889,7 +889,11 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!cls.getCreatureStats(mPtr).isDead())
|
if(!cls.getCreatureStats(mPtr).isDead())
|
||||||
|
{
|
||||||
mIdleState = CharState_Idle;
|
mIdleState = CharState_Idle;
|
||||||
|
if (cls.getCreatureStats(mPtr).getFallHeight() > 0)
|
||||||
|
mJumpState = JumpState_InAir;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
|
const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
|
||||||
|
@ -1909,7 +1913,7 @@ void CharacterController::updateAnimQueue()
|
||||||
mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
|
mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::update(float duration, bool animationOnly)
|
void CharacterController::update(float duration)
|
||||||
{
|
{
|
||||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||||
const MWWorld::Class &cls = mPtr.getClass();
|
const MWWorld::Class &cls = mPtr.getClass();
|
||||||
|
@ -1976,7 +1980,7 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
movementSettings.mSpeedFactor *= 2.f;
|
movementSettings.mSpeedFactor *= 2.f;
|
||||||
|
|
||||||
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||||
if (smoothMovement && !isFirstPersonPlayer)
|
if (smoothMovement)
|
||||||
{
|
{
|
||||||
static const float playerTurningCoef = 1.0 / std::max(0.01f, Settings::Manager::getFloat("smooth movement player turning delay", "Game"));
|
static const float playerTurningCoef = 1.0 / std::max(0.01f, Settings::Manager::getFloat("smooth movement player turning delay", "Game"));
|
||||||
float angle = mPtr.getRefData().getPosition().rot[2];
|
float angle = mPtr.getRefData().getPosition().rot[2];
|
||||||
|
@ -1986,7 +1990,9 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
float deltaLen = delta.length();
|
float deltaLen = delta.length();
|
||||||
|
|
||||||
float maxDelta;
|
float maxDelta;
|
||||||
if (std::abs(speedDelta) < deltaLen / 2)
|
if (isFirstPersonPlayer)
|
||||||
|
maxDelta = 1;
|
||||||
|
else if (std::abs(speedDelta) < deltaLen / 2)
|
||||||
// Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point).
|
// Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point).
|
||||||
maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f);
|
maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f);
|
||||||
else if (isPlayer && speedDelta < -deltaLen / 2)
|
else if (isPlayer && speedDelta < -deltaLen / 2)
|
||||||
|
@ -2024,7 +2030,10 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
bool canMove = cls.getMaxSpeed(mPtr) > 0;
|
bool canMove = cls.getMaxSpeed(mPtr) > 0;
|
||||||
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
|
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
|
||||||
if (!turnToMovementDirection || isFirstPersonPlayer)
|
if (!turnToMovementDirection || isFirstPersonPlayer)
|
||||||
|
{
|
||||||
movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
|
movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
|
||||||
|
stats.setSideMovementAngle(0);
|
||||||
|
}
|
||||||
else if (canMove)
|
else if (canMove)
|
||||||
{
|
{
|
||||||
float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y());
|
float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y());
|
||||||
|
@ -2276,18 +2285,19 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal);
|
sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (turnToMovementDirection)
|
if (turnToMovementDirection && !isFirstPersonPlayer &&
|
||||||
|
(movestate == CharState_SwimRunForward || movestate == CharState_SwimWalkForward ||
|
||||||
|
movestate == CharState_SwimRunBack || movestate == CharState_SwimWalkBack))
|
||||||
{
|
{
|
||||||
float targetSwimmingPitch;
|
|
||||||
if (inwater && vec.y() != 0 && !isFirstPersonPlayer && !movementSettings.mIsStrafing)
|
|
||||||
targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
|
|
||||||
else
|
|
||||||
targetSwimmingPitch = 0;
|
|
||||||
float maxSwimPitchDelta = 3.0f * duration;
|
|
||||||
float swimmingPitch = mAnimation->getBodyPitchRadians();
|
float swimmingPitch = mAnimation->getBodyPitchRadians();
|
||||||
|
float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
|
||||||
|
float maxSwimPitchDelta = 3.0f * duration;
|
||||||
swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
|
swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
|
||||||
mAnimation->setBodyPitchRadians(swimmingPitch);
|
mAnimation->setBodyPitchRadians(swimmingPitch);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
mAnimation->setBodyPitchRadians(0);
|
||||||
|
|
||||||
static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game");
|
static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game");
|
||||||
if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection)
|
if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection)
|
||||||
{
|
{
|
||||||
|
@ -2391,10 +2401,10 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true);
|
world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!animationOnly && !mMovementAnimationControlled)
|
if (!mMovementAnimationControlled)
|
||||||
world->queueMovement(mPtr, vec);
|
world->queueMovement(mPtr, vec);
|
||||||
}
|
}
|
||||||
else if (!animationOnly)
|
else
|
||||||
// We must always queue movement, even if there is none, to apply gravity.
|
// We must always queue movement, even if there is none, to apply gravity.
|
||||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||||
|
|
||||||
|
@ -2419,7 +2429,6 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
playDeath(1.f, mDeathState);
|
playDeath(1.f, mDeathState);
|
||||||
}
|
}
|
||||||
// We must always queue movement, even if there is none, to apply gravity.
|
// We must always queue movement, even if there is none, to apply gravity.
|
||||||
if (!animationOnly)
|
|
||||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2434,7 +2443,7 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
moved.y() *= scale;
|
moved.y() *= scale;
|
||||||
|
|
||||||
// Ensure we're moving in generally the right direction...
|
// Ensure we're moving in generally the right direction...
|
||||||
if(speed > 0.f)
|
if (speed > 0.f && moved != osg::Vec3f())
|
||||||
{
|
{
|
||||||
float l = moved.length();
|
float l = moved.length();
|
||||||
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 ||
|
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 ||
|
||||||
|
@ -2450,11 +2459,17 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr))
|
if (mFloatToSurface && cls.isActor())
|
||||||
|
{
|
||||||
|
if (cls.getCreatureStats(mPtr).isDead()
|
||||||
|
|| (!godmode && cls.getCreatureStats(mPtr).isParalyzed()))
|
||||||
|
{
|
||||||
moved.z() = 1.0;
|
moved.z() = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update movement
|
// Update movement
|
||||||
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())
|
if(mMovementAnimationControlled && mPtr.getClass().isActor())
|
||||||
world->queueMovement(mPtr, moved);
|
world->queueMovement(mPtr, moved);
|
||||||
|
|
||||||
mSkipAnim = false;
|
mSkipAnim = false;
|
||||||
|
|
|
@ -248,7 +248,7 @@ public:
|
||||||
|
|
||||||
void updatePtr(const MWWorld::Ptr &ptr);
|
void updatePtr(const MWWorld::Ptr &ptr);
|
||||||
|
|
||||||
void update(float duration, bool animationOnly=false);
|
void update(float duration);
|
||||||
|
|
||||||
bool onOpen();
|
bool onOpen();
|
||||||
void onClose();
|
void onClose();
|
||||||
|
|
|
@ -1654,6 +1654,11 @@ namespace MWMechanics
|
||||||
return mActors.getActorsFollowingIndices(actor);
|
return mActors.getActorsFollowingIndices(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::map<int, MWWorld::Ptr> MechanicsManager::getActorsFollowingByIndex(const MWWorld::Ptr& actor)
|
||||||
|
{
|
||||||
|
return mActors.getActorsFollowingByIndex(actor);
|
||||||
|
}
|
||||||
|
|
||||||
std::list<MWWorld::Ptr> MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) {
|
std::list<MWWorld::Ptr> MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) {
|
||||||
return mActors.getActorsFighting(actor);
|
return mActors.getActorsFighting(actor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,7 @@ namespace MWMechanics
|
||||||
std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) override;
|
std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) override;
|
||||||
std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) override;
|
std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) override;
|
||||||
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) override;
|
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) override;
|
||||||
|
std::map<int, MWWorld::Ptr> getActorsFollowingByIndex(const MWWorld::Ptr& actor) override;
|
||||||
|
|
||||||
std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor) override;
|
std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor) override;
|
||||||
std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor) override;
|
std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor) override;
|
||||||
|
|
|
@ -322,11 +322,6 @@ void MWMechanics::NpcStats::updateHealth()
|
||||||
setHealth(floor(0.5f * (strength + endurance)));
|
setHealth(floor(0.5f * (strength + endurance)));
|
||||||
}
|
}
|
||||||
|
|
||||||
int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const
|
|
||||||
{
|
|
||||||
return mSkillIncreases[attribute];
|
|
||||||
}
|
|
||||||
|
|
||||||
int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
|
int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
|
||||||
{
|
{
|
||||||
int num = mSkillIncreases[attribute];
|
int num = mSkillIncreases[attribute];
|
||||||
|
|
|
@ -87,8 +87,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
int getLevelProgress() const;
|
int getLevelProgress() const;
|
||||||
|
|
||||||
int getLevelUpAttributeIncrease(int attribute) const;
|
|
||||||
|
|
||||||
int getLevelupAttributeMultiplier(int attribute) const;
|
int getLevelupAttributeMultiplier(int attribute) const;
|
||||||
|
|
||||||
int getSkillIncreasesForSpecialization(int spec) const;
|
int getSkillIncreasesForSpecialization(int spec) const;
|
||||||
|
|
|
@ -97,12 +97,12 @@ namespace
|
||||||
float dotProduct = v1.x() * v3.x() + v1.y() * v3.y();
|
float dotProduct = v1.x() * v3.x() + v1.y() * v3.y();
|
||||||
float crossProduct = v1.x() * v3.y() - v1.y() * v3.x();
|
float crossProduct = v1.x() * v3.y() - v1.y() * v3.x();
|
||||||
|
|
||||||
// Check that the angle between v1 and v3 is less or equal than 10 degrees.
|
// Check that the angle between v1 and v3 is less or equal than 5 degrees.
|
||||||
static const float cos170 = std::cos(osg::PI / 180 * 170);
|
static const float cos175 = std::cos(osg::PI * (175.0 / 180));
|
||||||
bool checkAngle = dotProduct <= cos170 * v1.length() * v3.length();
|
bool checkAngle = dotProduct <= cos175 * v1.length() * v3.length();
|
||||||
|
|
||||||
// Check that distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`.
|
// Check that distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`.
|
||||||
bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length() * 2;
|
bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length();
|
||||||
|
|
||||||
return checkAngle && checkDist;
|
return checkAngle && checkDist;
|
||||||
}
|
}
|
||||||
|
@ -296,7 +296,8 @@ namespace MWMechanics
|
||||||
return getXAngleToDir(dir);
|
return getXAngleToDir(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance)
|
void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
||||||
|
bool shortenIfAlmostStraight, bool canMoveByZ)
|
||||||
{
|
{
|
||||||
if (mPath.empty())
|
if (mPath.empty())
|
||||||
return;
|
return;
|
||||||
|
@ -304,14 +305,25 @@ namespace MWMechanics
|
||||||
while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance)
|
while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance)
|
||||||
mPath.pop_front();
|
mPath.pop_front();
|
||||||
|
|
||||||
|
if (shortenIfAlmostStraight)
|
||||||
|
{
|
||||||
while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance))
|
while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance))
|
||||||
mPath.erase(mPath.begin() + 1);
|
mPath.erase(mPath.begin() + 1);
|
||||||
if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance))
|
if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance))
|
||||||
mPath.pop_front();
|
mPath.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance)
|
if (mPath.size() == 1)
|
||||||
|
{
|
||||||
|
float distSqr;
|
||||||
|
if (canMoveByZ)
|
||||||
|
distSqr = (mPath.front() - position).length2();
|
||||||
|
else
|
||||||
|
distSqr = sqrDistanceIgnoreZ(mPath.front(), position);
|
||||||
|
if (distSqr < destinationTolerance * destinationTolerance)
|
||||||
mPath.pop_front();
|
mPath.pop_front();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PathFinder::buildStraightPath(const osg::Vec3f& endPoint)
|
void PathFinder::buildStraightPath(const osg::Vec3f& endPoint)
|
||||||
{
|
{
|
||||||
|
@ -328,7 +340,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
||||||
|
|
||||||
mConstructed = true;
|
mConstructed = !mPath.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
|
@ -341,7 +353,7 @@ namespace MWMechanics
|
||||||
if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)))
|
if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)))
|
||||||
mPath.push_back(endPoint);
|
mPath.push_back(endPoint);
|
||||||
|
|
||||||
mConstructed = true;
|
mConstructed = !mPath.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||||
|
@ -366,7 +378,7 @@ namespace MWMechanics
|
||||||
if (!hasNavMesh && mPath.empty())
|
if (!hasNavMesh && mPath.empty())
|
||||||
mPath.push_back(endPoint);
|
mPath.push_back(endPoint);
|
||||||
|
|
||||||
mConstructed = true;
|
mConstructed = !mPath.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||||
|
|
|
@ -102,7 +102,8 @@ namespace MWMechanics
|
||||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts);
|
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts);
|
||||||
|
|
||||||
/// Remove front point if exist and within tolerance
|
/// Remove front point if exist and within tolerance
|
||||||
void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance);
|
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
||||||
|
bool shortenIfAlmostStraight, bool canMoveByZ);
|
||||||
|
|
||||||
bool checkPathCompleted() const
|
bool checkPathCompleted() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -60,7 +60,10 @@ namespace MWMechanics
|
||||||
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
||||||
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
|
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
|
||||||
{
|
{
|
||||||
if (!target.isEmpty() && target.getClass().isActor())
|
if (target.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (target.getClass().isActor())
|
||||||
{
|
{
|
||||||
// Early-out for characters that have departed.
|
// Early-out for characters that have departed.
|
||||||
const auto& stats = target.getClass().getCreatureStats(target);
|
const auto& stats = target.getClass().getCreatureStats(target);
|
||||||
|
@ -82,7 +85,7 @@ namespace MWMechanics
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
|
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
|
||||||
if (spell && !target.isEmpty() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
|
if (spell && target.getClass().isActor() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
|
||||||
{
|
{
|
||||||
int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ?
|
int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ?
|
||||||
ESM::MagicEffect::ResistCommonDisease
|
ESM::MagicEffect::ResistCommonDisease
|
||||||
|
@ -105,13 +108,13 @@ namespace MWMechanics
|
||||||
// This is required for Weakness effects in a spell to apply to any subsequent effects in the spell.
|
// This is required for Weakness effects in a spell to apply to any subsequent effects in the spell.
|
||||||
// Otherwise, they'd only apply after the whole spell was added.
|
// Otherwise, they'd only apply after the whole spell was added.
|
||||||
MagicEffects targetEffects;
|
MagicEffects targetEffects;
|
||||||
if (!target.isEmpty() && target.getClass().isActor())
|
if (target.getClass().isActor())
|
||||||
targetEffects += target.getClass().getCreatureStats(target).getMagicEffects();
|
targetEffects += target.getClass().getCreatureStats(target).getMagicEffects();
|
||||||
|
|
||||||
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
|
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
|
||||||
|
|
||||||
ActiveSpells targetSpells;
|
ActiveSpells targetSpells;
|
||||||
if (!target.isEmpty() && target.getClass().isActor())
|
if (target.getClass().isActor())
|
||||||
targetSpells = target.getClass().getCreatureStats(target).getActiveSpells();
|
targetSpells = target.getClass().getCreatureStats(target).getActiveSpells();
|
||||||
|
|
||||||
bool canCastAnEffect = false; // For bound equipment.If this remains false
|
bool canCastAnEffect = false; // For bound equipment.If this remains false
|
||||||
|
@ -123,7 +126,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
int currentEffectIndex = 0;
|
int currentEffectIndex = 0;
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
||||||
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
|
effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
|
||||||
{
|
{
|
||||||
if (effectIt->mRange != range)
|
if (effectIt->mRange != range)
|
||||||
continue;
|
continue;
|
||||||
|
@ -267,7 +270,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-casting a summon effect will remove the creature from previous castings of that effect.
|
// Re-casting a summon effect will remove the creature from previous castings of that effect.
|
||||||
if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor())
|
if (isSummoningEffect(effectIt->mEffectID) && target.getClass().isActor())
|
||||||
{
|
{
|
||||||
CreatureStats& targetStats = target.getClass().getCreatureStats(target);
|
CreatureStats& targetStats = target.getClass().getCreatureStats(target);
|
||||||
ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex);
|
ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex);
|
||||||
|
@ -310,7 +313,6 @@ namespace MWMechanics
|
||||||
if (!exploded)
|
if (!exploded)
|
||||||
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile);
|
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile);
|
||||||
|
|
||||||
if (!target.isEmpty()) {
|
|
||||||
if (!reflectedEffects.mList.empty())
|
if (!reflectedEffects.mList.empty())
|
||||||
inflict(caster, target, reflectedEffects, range, true, exploded);
|
inflict(caster, target, reflectedEffects, range, true, exploded);
|
||||||
|
|
||||||
|
@ -323,7 +325,6 @@ namespace MWMechanics
|
||||||
mSourceName, casterActorId);
|
mSourceName, casterActorId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude)
|
bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
static const ESM::WeaponType *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])];
|
|
||||||
|
|
||||||
MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype)
|
MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype)
|
||||||
{
|
{
|
||||||
MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor);
|
MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#include "actor.hpp"
|
#include "actor.hpp"
|
||||||
|
|
||||||
#include <BulletCollision/CollisionShapes/btCapsuleShape.h>
|
|
||||||
#include <BulletCollision/CollisionShapes/btBoxShape.h>
|
#include <BulletCollision/CollisionShapes/btBoxShape.h>
|
||||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
@ -14,6 +13,8 @@
|
||||||
#include "collisiontype.hpp"
|
#include "collisiontype.hpp"
|
||||||
#include "mtphysics.hpp"
|
#include "mtphysics.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -52,36 +53,29 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
|
||||||
Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\".";
|
Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\".";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it)
|
|
||||||
if (std::abs(mHalfExtents.x()-mHalfExtents.y())<mHalfExtents.x()*0.05 && mHalfExtents.z() >= mHalfExtents.x())
|
|
||||||
{
|
|
||||||
mShape.reset(new btCapsuleShapeZ(mHalfExtents.x(), 2*mHalfExtents.z() - 2*mHalfExtents.x()));
|
|
||||||
mRotationallyInvariant = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents)));
|
mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents)));
|
||||||
mRotationallyInvariant = false;
|
mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2;
|
||||||
}
|
|
||||||
|
|
||||||
mConvexShape = static_cast<btConvexShape*>(mShape.get());
|
mConvexShape = static_cast<btConvexShape*>(mShape.get());
|
||||||
|
|
||||||
mCollisionObject.reset(new btCollisionObject);
|
mCollisionObject = std::make_unique<btCollisionObject>();
|
||||||
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||||
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
|
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
|
||||||
mCollisionObject->setCollisionShape(mShape.get());
|
mCollisionObject->setCollisionShape(mShape.get());
|
||||||
mCollisionObject->setUserPointer(this);
|
mCollisionObject->setUserPointer(this);
|
||||||
|
|
||||||
updateRotation();
|
|
||||||
updateScale();
|
updateScale();
|
||||||
resetPosition();
|
|
||||||
|
if(!mRotationallyInvariant)
|
||||||
|
updateRotation();
|
||||||
|
|
||||||
|
updatePosition();
|
||||||
addCollisionMask(getCollisionMask());
|
addCollisionMask(getCollisionMask());
|
||||||
updateCollisionObjectPosition();
|
updateCollisionObjectPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
Actor::~Actor()
|
Actor::~Actor()
|
||||||
{
|
{
|
||||||
if (mCollisionObject)
|
|
||||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,30 +113,34 @@ int Actor::getCollisionMask() const
|
||||||
return collisionMask;
|
return collisionMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::updatePositionUnsafe()
|
void Actor::updatePosition()
|
||||||
{
|
{
|
||||||
if (!mWorldPositionChanged && mWorldPosition != mPtr.getRefData().getPosition().asVec3())
|
std::scoped_lock lock(mPositionMutex);
|
||||||
|
updateWorldPosition();
|
||||||
|
mPreviousPosition = mWorldPosition;
|
||||||
|
mPosition = mWorldPosition;
|
||||||
|
mSimulationPosition = mWorldPosition;
|
||||||
|
mStandingOnPtr = nullptr;
|
||||||
|
mSkipSimulation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Actor::updateWorldPosition()
|
||||||
|
{
|
||||||
|
if (mWorldPosition != mPtr.getRefData().getPosition().asVec3())
|
||||||
mWorldPositionChanged = true;
|
mWorldPositionChanged = true;
|
||||||
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
|
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::updatePosition()
|
|
||||||
{
|
|
||||||
std::scoped_lock lock(mPositionMutex);
|
|
||||||
updatePositionUnsafe();
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::Vec3f Actor::getWorldPosition() const
|
osg::Vec3f Actor::getWorldPosition() const
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mPositionMutex);
|
|
||||||
return mWorldPosition;
|
return mWorldPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::setSimulationPosition(const osg::Vec3f& position)
|
void Actor::setSimulationPosition(const osg::Vec3f& position)
|
||||||
{
|
{
|
||||||
if (!mResetSimulation)
|
if (!mSkipSimulation)
|
||||||
mSimulationPosition = position;
|
mSimulationPosition = position;
|
||||||
mResetSimulation = false;
|
mSkipSimulation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f Actor::getSimulationPosition() const
|
osg::Vec3f Actor::getSimulationPosition() const
|
||||||
|
@ -150,8 +148,14 @@ osg::Vec3f Actor::getSimulationPosition() const
|
||||||
return mSimulationPosition;
|
return mSimulationPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::updateCollisionObjectPositionUnsafe()
|
osg::Vec3f Actor::getScaledMeshTranslation() const
|
||||||
{
|
{
|
||||||
|
return mRotation * osg::componentMultiply(mMeshTranslation, mScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Actor::updateCollisionObjectPosition()
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mPositionMutex);
|
||||||
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
|
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
|
||||||
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
|
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
|
||||||
osg::Vec3f newPosition = scaledTranslation + mPosition;
|
osg::Vec3f newPosition = scaledTranslation + mPosition;
|
||||||
|
@ -161,12 +165,6 @@ void Actor::updateCollisionObjectPositionUnsafe()
|
||||||
mWorldPositionChanged = false;
|
mWorldPositionChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::updateCollisionObjectPosition()
|
|
||||||
{
|
|
||||||
std::scoped_lock lock(mPositionMutex);
|
|
||||||
updateCollisionObjectPositionUnsafe();
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::Vec3f Actor::getCollisionObjectPosition() const
|
osg::Vec3f Actor::getCollisionObjectPosition() const
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mPositionMutex);
|
std::scoped_lock lock(mPositionMutex);
|
||||||
|
@ -189,18 +187,6 @@ void Actor::adjustPosition(const osg::Vec3f& offset)
|
||||||
mPositionOffset += offset;
|
mPositionOffset += offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Actor::resetPosition()
|
|
||||||
{
|
|
||||||
std::scoped_lock lock(mPositionMutex);
|
|
||||||
updatePositionUnsafe();
|
|
||||||
mPreviousPosition = mWorldPosition;
|
|
||||||
mPosition = mWorldPosition;
|
|
||||||
mSimulationPosition = mWorldPosition;
|
|
||||||
mStandingOnPtr = nullptr;
|
|
||||||
mWorldPositionChanged = false;
|
|
||||||
mResetSimulation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::Vec3f Actor::getPosition() const
|
osg::Vec3f Actor::getPosition() const
|
||||||
{
|
{
|
||||||
return mPosition;
|
return mPosition;
|
||||||
|
@ -214,8 +200,6 @@ osg::Vec3f Actor::getPreviousPosition() const
|
||||||
void Actor::updateRotation ()
|
void Actor::updateRotation ()
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mPositionMutex);
|
std::scoped_lock lock(mPositionMutex);
|
||||||
if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude())
|
|
||||||
return;
|
|
||||||
mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
|
mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +230,6 @@ osg::Vec3f Actor::getHalfExtents() const
|
||||||
|
|
||||||
osg::Vec3f Actor::getOriginalHalfExtents() const
|
osg::Vec3f Actor::getOriginalHalfExtents() const
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mPositionMutex);
|
|
||||||
return mHalfExtents;
|
return mHalfExtents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,7 +266,6 @@ void Actor::setWalkingOnWater(bool walkingOnWater)
|
||||||
|
|
||||||
void Actor::setCanWaterWalk(bool waterWalk)
|
void Actor::setCanWaterWalk(bool waterWalk)
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mPositionMutex);
|
|
||||||
if (waterWalk != mCanWaterWalk)
|
if (waterWalk != mCanWaterWalk)
|
||||||
{
|
{
|
||||||
mCanWaterWalk = waterWalk;
|
mCanWaterWalk = waterWalk;
|
||||||
|
|
|
@ -60,7 +60,7 @@ namespace MWPhysics
|
||||||
* Set mWorldPosition to the position in the Ptr's RefData. This is used by the physics simulation to account for
|
* Set mWorldPosition to the position in the Ptr's RefData. This is used by the physics simulation to account for
|
||||||
* when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation.
|
* when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation.
|
||||||
*/
|
*/
|
||||||
void updatePosition();
|
void updateWorldPosition();
|
||||||
osg::Vec3f getWorldPosition() const;
|
osg::Vec3f getWorldPosition() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,6 +82,9 @@ namespace MWPhysics
|
||||||
*/
|
*/
|
||||||
osg::Vec3f getOriginalHalfExtents() const;
|
osg::Vec3f getOriginalHalfExtents() const;
|
||||||
|
|
||||||
|
/// Returns the mesh translation, scaled and rotated as necessary
|
||||||
|
osg::Vec3f getScaledMeshTranslation() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the position of the collision body
|
* Returns the position of the collision body
|
||||||
* @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space.
|
* @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space.
|
||||||
|
@ -93,7 +96,7 @@ namespace MWPhysics
|
||||||
* Returns true if the new position is different.
|
* Returns true if the new position is different.
|
||||||
*/
|
*/
|
||||||
bool setPosition(const osg::Vec3f& position);
|
bool setPosition(const osg::Vec3f& position);
|
||||||
void resetPosition();
|
void updatePosition();
|
||||||
void adjustPosition(const osg::Vec3f& offset);
|
void adjustPosition(const osg::Vec3f& offset);
|
||||||
|
|
||||||
osg::Vec3f getPosition() const;
|
osg::Vec3f getPosition() const;
|
||||||
|
@ -155,8 +158,6 @@ namespace MWPhysics
|
||||||
void updateCollisionMask();
|
void updateCollisionMask();
|
||||||
void addCollisionMask(int collisionMask);
|
void addCollisionMask(int collisionMask);
|
||||||
int getCollisionMask() const;
|
int getCollisionMask() const;
|
||||||
void updateCollisionObjectPositionUnsafe();
|
|
||||||
void updatePositionUnsafe();
|
|
||||||
|
|
||||||
bool mCanWaterWalk;
|
bool mCanWaterWalk;
|
||||||
std::atomic<bool> mWalkingOnWater;
|
std::atomic<bool> mWalkingOnWater;
|
||||||
|
@ -180,7 +181,7 @@ namespace MWPhysics
|
||||||
osg::Vec3f mPreviousPosition;
|
osg::Vec3f mPreviousPosition;
|
||||||
osg::Vec3f mPositionOffset;
|
osg::Vec3f mPositionOffset;
|
||||||
bool mWorldPositionChanged;
|
bool mWorldPositionChanged;
|
||||||
bool mResetSimulation;
|
bool mSkipSimulation;
|
||||||
btTransform mLocalTransform;
|
btTransform mLocalTransform;
|
||||||
mutable std::mutex mPositionMutex;
|
mutable std::mutex mPositionMutex;
|
||||||
|
|
||||||
|
|
106
apps/openmw/mwphysics/actorconvexcallback.cpp
Normal file
106
apps/openmw/mwphysics/actorconvexcallback.cpp
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "actorconvexcallback.hpp"
|
||||||
|
#include "collisiontype.hpp"
|
||||||
|
#include "contacttestwrapper.h"
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
#include <components/misc/convert.hpp>
|
||||||
|
|
||||||
|
#include "collisiontype.hpp"
|
||||||
|
#include "projectile.hpp"
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class ActorOverlapTester : public btCollisionWorld::ContactResultCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool overlapping = false;
|
||||||
|
|
||||||
|
btScalar addSingleResult(btManifoldPoint& cp,
|
||||||
|
const btCollisionObjectWrapper* colObj0Wrap,
|
||||||
|
int partId0,
|
||||||
|
int index0,
|
||||||
|
const btCollisionObjectWrapper* colObj1Wrap,
|
||||||
|
int partId1,
|
||||||
|
int index1) override
|
||||||
|
{
|
||||||
|
if(cp.getDistance() <= 0.0f)
|
||||||
|
overlapping = true;
|
||||||
|
return btScalar(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ActorConvexCallback::ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world)
|
||||||
|
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
|
||||||
|
mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
btScalar ActorConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
|
||||||
|
{
|
||||||
|
if (convexResult.m_hitCollisionObject == mMe)
|
||||||
|
return btScalar(1);
|
||||||
|
|
||||||
|
// override data for actor-actor collisions
|
||||||
|
// vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter of the distance between them
|
||||||
|
// For some reason this doesn't work as well as it should when using capsules, but it still helps a lot.
|
||||||
|
if(convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor)
|
||||||
|
{
|
||||||
|
ActorOverlapTester isOverlapping;
|
||||||
|
// FIXME: This is absolutely terrible and bullet should feel terrible for not making contactPairTest const-correct.
|
||||||
|
ContactTestWrapper::contactPairTest(const_cast<btCollisionWorld*>(mWorld), const_cast<btCollisionObject*>(mMe), const_cast<btCollisionObject*>(convexResult.m_hitCollisionObject), isOverlapping);
|
||||||
|
|
||||||
|
if(isOverlapping.overlapping)
|
||||||
|
{
|
||||||
|
auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin());
|
||||||
|
auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin());
|
||||||
|
osg::Vec3f motion = Misc::Convert::toOsg(mMotion);
|
||||||
|
osg::Vec3f normal = (originA-originB);
|
||||||
|
normal.z() = 0;
|
||||||
|
normal.normalize();
|
||||||
|
// only collide if horizontally moving towards the hit actor (note: the motion vector appears to be inverted)
|
||||||
|
// FIXME: This kinda screws with standing on actors that walk up slopes for some reason. Makes you fall through them.
|
||||||
|
// It happens in vanilla Morrowind too, but much less often.
|
||||||
|
// I tried hunting down why but couldn't figure it out. Possibly a stair stepping or ground ejection bug.
|
||||||
|
if(normal * motion > 0.0f)
|
||||||
|
{
|
||||||
|
convexResult.m_hitFraction = 0.0f;
|
||||||
|
convexResult.m_hitNormalLocal = Misc::Convert::toBullet(normal);
|
||||||
|
return ClosestConvexResultCallback::addSingleResult(convexResult, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return btScalar(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile)
|
||||||
|
{
|
||||||
|
auto* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer());
|
||||||
|
if (!projectileHolder->isActive())
|
||||||
|
return btScalar(1);
|
||||||
|
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
|
||||||
|
const MWWorld::Ptr target = targetHolder->getPtr();
|
||||||
|
if (projectileHolder->isValidTarget(target))
|
||||||
|
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
|
||||||
|
return btScalar(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
btVector3 hitNormalWorld;
|
||||||
|
if (normalInWorldSpace)
|
||||||
|
hitNormalWorld = convexResult.m_hitNormalLocal;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
///need to transform normal into worldspace
|
||||||
|
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dot product of the motion vector against the collision contact normal
|
||||||
|
btScalar dotCollision = mMotion.dot(hitNormalWorld);
|
||||||
|
if (dotCollision <= mMinCollisionDot)
|
||||||
|
return btScalar(1);
|
||||||
|
|
||||||
|
return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H
|
#ifndef OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H
|
||||||
#define OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H
|
#define OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H
|
||||||
|
|
||||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
@ -7,10 +7,10 @@ class btCollisionObject;
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
|
class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot);
|
ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world);
|
||||||
|
|
||||||
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override;
|
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override;
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ namespace MWPhysics
|
||||||
const btCollisionObject *mMe;
|
const btCollisionObject *mMe;
|
||||||
const btVector3 mMotion;
|
const btVector3 mMotion;
|
||||||
const btScalar mMinCollisionDot;
|
const btScalar mMinCollisionDot;
|
||||||
|
const btCollisionWorld * mWorld;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
#include "closestnotmeconvexresultcallback.hpp"
|
|
||||||
|
|
||||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
|
||||||
|
|
||||||
#include <components/misc/convert.hpp>
|
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
|
||||||
#include "../mwbase/environment.hpp"
|
|
||||||
|
|
||||||
#include "collisiontype.hpp"
|
|
||||||
#include "projectile.hpp"
|
|
||||||
|
|
||||||
namespace MWPhysics
|
|
||||||
{
|
|
||||||
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot)
|
|
||||||
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
|
|
||||||
mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
|
|
||||||
{
|
|
||||||
if (convexResult.m_hitCollisionObject == mMe)
|
|
||||||
return btScalar(1);
|
|
||||||
|
|
||||||
if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile)
|
|
||||||
{
|
|
||||||
auto* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer());
|
|
||||||
if (!projectileHolder->isActive())
|
|
||||||
return btScalar(1);
|
|
||||||
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
|
|
||||||
const MWWorld::Ptr target = targetHolder->getPtr();
|
|
||||||
if (projectileHolder->isValidTarget(target))
|
|
||||||
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
|
|
||||||
return btScalar(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
btVector3 hitNormalWorld;
|
|
||||||
if (normalInWorldSpace)
|
|
||||||
hitNormalWorld = convexResult.m_hitNormalLocal;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
///need to transform normal into worldspace
|
|
||||||
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dot product of the motion vector against the collision contact normal
|
|
||||||
btScalar dotCollision = mMotion.dot(hitNormalWorld);
|
|
||||||
if (dotCollision <= mMinCollisionDot)
|
|
||||||
return btScalar(1);
|
|
||||||
|
|
||||||
return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,16 +7,13 @@
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
#include "actor.hpp"
|
|
||||||
#include "collisiontype.hpp"
|
|
||||||
#include "projectile.hpp"
|
|
||||||
#include "ptrholder.hpp"
|
#include "ptrholder.hpp"
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj)
|
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to)
|
||||||
: btCollisionWorld::ClosestRayResultCallback(from, to)
|
: btCollisionWorld::ClosestRayResultCallback(from, to)
|
||||||
, mMe(me), mTargets(std::move(targets)), mProjectile(proj)
|
, mMe(me), mTargets(std::move(targets))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,9 +22,6 @@ namespace MWPhysics
|
||||||
if (rayResult.m_collisionObject == mMe)
|
if (rayResult.m_collisionObject == mMe)
|
||||||
return 1.f;
|
return 1.f;
|
||||||
|
|
||||||
if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject())
|
|
||||||
return 1.f;
|
|
||||||
|
|
||||||
if (!mTargets.empty())
|
if (!mTargets.empty())
|
||||||
{
|
{
|
||||||
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
|
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
|
||||||
|
@ -38,21 +32,6 @@ namespace MWPhysics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
||||||
if (mProjectile)
|
|
||||||
{
|
|
||||||
auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
|
|
||||||
if (auto* target = dynamic_cast<Actor*>(holder))
|
|
||||||
{
|
|
||||||
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
|
||||||
}
|
|
||||||
else if (auto* target = dynamic_cast<Projectile*>(holder))
|
|
||||||
{
|
|
||||||
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
|
||||||
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rayResult.m_hitFraction;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,13 @@ namespace MWPhysics
|
||||||
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
|
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr);
|
ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to);
|
||||||
|
|
||||||
btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
|
btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const btCollisionObject* mMe;
|
const btCollisionObject* mMe;
|
||||||
const std::vector<const btCollisionObject*> mTargets;
|
const std::vector<const btCollisionObject*> mTargets;
|
||||||
Projectile* mProjectile;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,22 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
static const float sStepSizeUp = 34.0f;
|
static const float sStepSizeUp = 34.0f;
|
||||||
static const float sStepSizeDown = 62.0f;
|
static const float sStepSizeDown = 62.0f;
|
||||||
static const float sMinStep = 10.f;
|
|
||||||
|
static const float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes
|
||||||
|
static const float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes
|
||||||
|
// whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance
|
||||||
|
static const bool sDoExtraStairHacks = true;
|
||||||
|
|
||||||
static const float sGroundOffset = 1.0f;
|
static const float sGroundOffset = 1.0f;
|
||||||
static const float sMaxSlope = 49.0f;
|
static const float sMaxSlope = 49.0f;
|
||||||
|
|
||||||
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
|
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
|
||||||
static const int sMaxIterations = 8;
|
static const int sMaxIterations = 8;
|
||||||
|
// Allows for more precise movement solving without getting stuck or snagging too easily.
|
||||||
|
static const float sCollisionMargin = 0.1;
|
||||||
|
// Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily
|
||||||
|
// Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues
|
||||||
|
static const float sAllowedPenetration = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
21
apps/openmw/mwphysics/contacttestwrapper.cpp
Normal file
21
apps/openmw/mwphysics/contacttestwrapper.cpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "contacttestwrapper.h"
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
// Concurrent calls to contactPairTest (and by extension contactTest) are forbidden.
|
||||||
|
static std::mutex contactMutex;
|
||||||
|
void ContactTestWrapper::contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(contactMutex);
|
||||||
|
collisionWorld->contactTest(colObj, resultCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContactTestWrapper::contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(contactMutex);
|
||||||
|
collisionWorld->contactPairTest(colObjA, colObjB, resultCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
apps/openmw/mwphysics/contacttestwrapper.h
Normal file
14
apps/openmw/mwphysics/contacttestwrapper.h
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H
|
||||||
|
#define OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
struct ContactTestWrapper
|
||||||
|
{
|
||||||
|
static void contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback);
|
||||||
|
static void contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -1,4 +1,5 @@
|
||||||
#include "heightfield.hpp"
|
#include "heightfield.hpp"
|
||||||
|
#include "mtphysics.hpp"
|
||||||
|
|
||||||
#include <osg/Object>
|
#include <osg/Object>
|
||||||
|
|
||||||
|
@ -42,10 +43,12 @@ namespace
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
|
HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler)
|
||||||
: mHeights(makeHeights(heights, sqrtVerts))
|
: mHoldObject(holdObject)
|
||||||
|
, mHeights(makeHeights(heights, sqrtVerts))
|
||||||
|
, mTaskScheduler(scheduler)
|
||||||
{
|
{
|
||||||
mShape = new btHeightfieldTerrainShape(
|
mShape = std::make_unique<btHeightfieldTerrainShape>(
|
||||||
sqrtVerts, sqrtVerts,
|
sqrtVerts, sqrtVerts,
|
||||||
getHeights(heights, mHeights),
|
getHeights(heights, mHeights),
|
||||||
1,
|
1,
|
||||||
|
@ -60,31 +63,29 @@ namespace MWPhysics
|
||||||
(y+0.5f) * triSize * (sqrtVerts-1),
|
(y+0.5f) * triSize * (sqrtVerts-1),
|
||||||
(maxH+minH)*0.5f));
|
(maxH+minH)*0.5f));
|
||||||
|
|
||||||
mCollisionObject = new btCollisionObject;
|
mCollisionObject = std::make_unique<btCollisionObject>();
|
||||||
mCollisionObject->setCollisionShape(mShape);
|
mCollisionObject->setCollisionShape(mShape.get());
|
||||||
mCollisionObject->setWorldTransform(transform);
|
mCollisionObject->setWorldTransform(transform);
|
||||||
|
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_HeightMap, CollisionType_Actor|CollisionType_Projectile);
|
||||||
mHoldObject = holdObject;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HeightField::~HeightField()
|
HeightField::~HeightField()
|
||||||
{
|
{
|
||||||
delete mCollisionObject;
|
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||||
delete mShape;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
btCollisionObject* HeightField::getCollisionObject()
|
btCollisionObject* HeightField::getCollisionObject()
|
||||||
{
|
{
|
||||||
return mCollisionObject;
|
return mCollisionObject.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
const btCollisionObject* HeightField::getCollisionObject() const
|
const btCollisionObject* HeightField::getCollisionObject() const
|
||||||
{
|
{
|
||||||
return mCollisionObject;
|
return mCollisionObject.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
const btHeightfieldTerrainShape* HeightField::getShape() const
|
const btHeightfieldTerrainShape* HeightField::getShape() const
|
||||||
{
|
{
|
||||||
return mShape;
|
return mShape.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <LinearMath/btScalar.h>
|
#include <LinearMath/btScalar.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class btCollisionObject;
|
class btCollisionObject;
|
||||||
|
@ -17,10 +18,12 @@ namespace osg
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
|
class PhysicsTaskScheduler;
|
||||||
|
|
||||||
class HeightField
|
class HeightField
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
|
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler);
|
||||||
~HeightField();
|
~HeightField();
|
||||||
|
|
||||||
btCollisionObject* getCollisionObject();
|
btCollisionObject* getCollisionObject();
|
||||||
|
@ -28,11 +31,13 @@ namespace MWPhysics
|
||||||
const btHeightfieldTerrainShape* getShape() const;
|
const btHeightfieldTerrainShape* getShape() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
btHeightfieldTerrainShape* mShape;
|
std::unique_ptr<btHeightfieldTerrainShape> mShape;
|
||||||
btCollisionObject* mCollisionObject;
|
std::unique_ptr<btCollisionObject> mCollisionObject;
|
||||||
osg::ref_ptr<const osg::Object> mHoldObject;
|
osg::ref_ptr<const osg::Object> mHoldObject;
|
||||||
std::vector<btScalar> mHeights;
|
std::vector<btScalar> mHeights;
|
||||||
|
|
||||||
|
PhysicsTaskScheduler* mTaskScheduler;
|
||||||
|
|
||||||
void operator=(const HeightField&);
|
void operator=(const HeightField&);
|
||||||
HeightField(const HeightField&);
|
HeightField(const HeightField&);
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,10 +26,13 @@
|
||||||
#include "actor.hpp"
|
#include "actor.hpp"
|
||||||
#include "collisiontype.hpp"
|
#include "collisiontype.hpp"
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
|
#include "contacttestwrapper.h"
|
||||||
#include "physicssystem.hpp"
|
#include "physicssystem.hpp"
|
||||||
#include "stepper.hpp"
|
#include "stepper.hpp"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
static bool isActor(const btCollisionObject *obj)
|
static bool isActor(const btCollisionObject *obj)
|
||||||
|
@ -38,12 +41,50 @@ namespace MWPhysics
|
||||||
return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor;
|
return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Vec3>
|
class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback
|
||||||
static bool isWalkableSlope(const Vec3 &normal)
|
|
||||||
{
|
{
|
||||||
static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
|
public:
|
||||||
return (normal.z() > sMaxSlopeCos);
|
ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me)
|
||||||
|
{
|
||||||
|
m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||||
|
m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask;
|
||||||
|
mVelocity = Misc::Convert::toBullet(velocity);
|
||||||
}
|
}
|
||||||
|
btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override
|
||||||
|
{
|
||||||
|
if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject()))
|
||||||
|
return 0.0;
|
||||||
|
// ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, that would break detection when not moving)
|
||||||
|
if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0)
|
||||||
|
return 0.0;
|
||||||
|
auto delta = contact.m_normalWorldOnB * -contact.m_distance1;
|
||||||
|
mContactSum += delta;
|
||||||
|
mMaxX = std::max(std::abs(delta.x()), mMaxX);
|
||||||
|
mMaxY = std::max(std::abs(delta.y()), mMaxY);
|
||||||
|
mMaxZ = std::max(std::abs(delta.z()), mMaxZ);
|
||||||
|
if (contact.m_distance1 < mDistance)
|
||||||
|
{
|
||||||
|
mDistance = contact.m_distance1;
|
||||||
|
mNormal = contact.m_normalWorldOnB;
|
||||||
|
mDelta = delta;
|
||||||
|
return mDistance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btScalar mMaxX = 0.0;
|
||||||
|
btScalar mMaxY = 0.0;
|
||||||
|
btScalar mMaxZ = 0.0;
|
||||||
|
btVector3 mContactSum{0.0, 0.0, 0.0};
|
||||||
|
btVector3 mNormal{0.0, 0.0, 0.0}; // points towards "me"
|
||||||
|
btVector3 mDelta{0.0, 0.0, 0.0}; // points towards "me"
|
||||||
|
btScalar mDistance = 0.0; // negative or zero
|
||||||
|
protected:
|
||||||
|
btVector3 mVelocity;
|
||||||
|
const btCollisionObject * mMe;
|
||||||
|
};
|
||||||
|
|
||||||
osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight)
|
osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight)
|
||||||
{
|
{
|
||||||
|
@ -87,15 +128,17 @@ namespace MWPhysics
|
||||||
WorldFrameData& worldData)
|
WorldFrameData& worldData)
|
||||||
{
|
{
|
||||||
auto* physicActor = actor.mActorRaw;
|
auto* physicActor = actor.mActorRaw;
|
||||||
auto ptr = actor.mPtr;
|
|
||||||
ESM::Position refpos = actor.mRefpos;
|
ESM::Position refpos = actor.mRefpos;
|
||||||
|
|
||||||
// Early-out for totally static creatures
|
// Early-out for totally static creatures
|
||||||
// (Not sure if gravity should still apply?)
|
// (Not sure if gravity should still apply?)
|
||||||
|
{
|
||||||
|
const auto ptr = physicActor->getPtr();
|
||||||
if (!ptr.getClass().isMobile(ptr))
|
if (!ptr.getClass().isMobile(ptr))
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const bool isPlayer = (ptr == MWMechanics::getPlayer());
|
const bool isPlayer = (physicActor->getPtr() == MWMechanics::getPlayer());
|
||||||
auto* world = MWBase::Environment::get().getWorld();
|
auto* world = MWBase::Environment::get().getWorld();
|
||||||
|
|
||||||
// In VR, player should move according to current direction of
|
// In VR, player should move according to current direction of
|
||||||
|
@ -141,13 +184,13 @@ namespace MWPhysics
|
||||||
}
|
}
|
||||||
|
|
||||||
const btCollisionObject *colobj = physicActor->getCollisionObject();
|
const btCollisionObject *colobj = physicActor->getCollisionObject();
|
||||||
osg::Vec3f halfExtents = physicActor->getHalfExtents();
|
|
||||||
|
|
||||||
// NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos).
|
// Adjust for collision mesh offset relative to actor's "location"
|
||||||
// That means the collision shape used for moving this actor is in a different spot than the collision shape
|
// (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own)
|
||||||
// other actors are using to collide against this actor.
|
// for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation
|
||||||
// While this is strictly speaking wrong, it's needed for MW compatibility.
|
// if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
|
||||||
actor.mPosition.z() += halfExtents.z();
|
osg::Vec3f halfExtents = physicActor->getHalfExtents();
|
||||||
|
actor.mPosition.z() += halfExtents.z(); // vanilla-accurate
|
||||||
|
|
||||||
static const float fSwimHeightScale = world->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
static const float fSwimHeightScale = world->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
||||||
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
|
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
|
||||||
|
@ -172,8 +215,9 @@ namespace MWPhysics
|
||||||
velocity = velocity + inertia;
|
velocity = velocity + inertia;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dead actors underwater will float to the surface, if the CharacterController tells us to do so
|
// Dead and paralyzed actors underwater will float to the surface,
|
||||||
if (actor.mMovement.z() > 0 && actor.mIsDead && actor.mPosition.z() < swimlevel)
|
// if the CharacterController tells us to do so
|
||||||
|
if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel)
|
||||||
velocity = osg::Vec3f(0,0,1) * 25;
|
velocity = osg::Vec3f(0,0,1) * 25;
|
||||||
|
|
||||||
if (actor.mWantJump)
|
if (actor.mWantJump)
|
||||||
|
@ -206,6 +250,7 @@ namespace MWPhysics
|
||||||
trackingOffset.z() = 0;
|
trackingOffset.z() = 0;
|
||||||
|
|
||||||
float remainingTime = time;
|
float remainingTime = time;
|
||||||
|
bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying;
|
||||||
float remainder = 1.f;
|
float remainder = 1.f;
|
||||||
|
|
||||||
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f && remainder > 0.01; ++iterations)
|
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f && remainder > 0.01; ++iterations)
|
||||||
|
@ -238,6 +283,10 @@ namespace MWPhysics
|
||||||
remainder = 0.f;
|
remainder = 0.f;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel)
|
||||||
|
seenGround = true;
|
||||||
|
|
||||||
// We are touching something.
|
// We are touching something.
|
||||||
if (tracer.mFraction < 1E-9f)
|
if (tracer.mFraction < 1E-9f)
|
||||||
{
|
{
|
||||||
|
@ -254,7 +303,7 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
// Try to step up onto it.
|
// Try to step up onto it.
|
||||||
// NOTE: stepMove does not allow stepping over, modifies newPosition if successful
|
// NOTE: stepMove does not allow stepping over, modifies newPosition if successful
|
||||||
result = stepper.step(newPosition, toMove, remainingTime);
|
result = stepper.step(newPosition, toMove, remainingTime, seenGround, iterations == 0);
|
||||||
remainder = remainingTime / time;
|
remainder = remainingTime / time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,6 +322,13 @@ namespace MWPhysics
|
||||||
* The initial velocity was set earlier (see above).
|
* The initial velocity was set earlier (see above).
|
||||||
*/
|
*/
|
||||||
float remainingTime = time;
|
float remainingTime = time;
|
||||||
|
bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying;
|
||||||
|
|
||||||
|
int numTimesSlid = 0;
|
||||||
|
osg::Vec3f lastSlideNormal(0,0,1);
|
||||||
|
osg::Vec3f lastSlideNormalFallback(0,0,1);
|
||||||
|
bool forceGroundTest = false;
|
||||||
|
|
||||||
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
|
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
|
||||||
{
|
{
|
||||||
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
|
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
|
||||||
|
@ -281,7 +337,7 @@ namespace MWPhysics
|
||||||
if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
|
if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
|
||||||
{
|
{
|
||||||
const osg::Vec3f down(0,0,-1);
|
const osg::Vec3f down(0,0,-1);
|
||||||
velocity = slide(velocity, down);
|
velocity = reject(velocity, down);
|
||||||
// NOTE: remainingTime is unchanged before the loop continues
|
// NOTE: remainingTime is unchanged before the loop continues
|
||||||
continue; // velocity updated, calculate nextpos again
|
continue; // velocity updated, calculate nextpos again
|
||||||
}
|
}
|
||||||
|
@ -310,58 +366,131 @@ namespace MWPhysics
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are touching something.
|
if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel)
|
||||||
if (tracer.mFraction < 1E-9f)
|
seenGround = true;
|
||||||
{
|
|
||||||
// Try to separate by backing off slighly to unstuck the solver
|
|
||||||
osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
|
|
||||||
newPosition += backOff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We hit something. Check if we can step up.
|
// We hit something. Check if we can step up.
|
||||||
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
|
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
|
||||||
osg::Vec3f oldPosition = newPosition;
|
osg::Vec3f oldPosition = newPosition;
|
||||||
bool result = false;
|
bool usedStepLogic = false;
|
||||||
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
|
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
|
||||||
{
|
{
|
||||||
// Try to step up onto it.
|
// Try to step up onto it.
|
||||||
// NOTE: stepMove does not allow stepping over, modifies newPosition if successful
|
// NOTE: this modifies newPosition and velocity on its own if successful
|
||||||
result = stepper.step(newPosition, velocity*remainingTime, remainingTime);
|
usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0);
|
||||||
}
|
}
|
||||||
if (result)
|
if (usedStepLogic)
|
||||||
{
|
{
|
||||||
// don't let pure water creatures move out of water after stepMove
|
// don't let pure water creatures move out of water after stepMove
|
||||||
|
const auto ptr = physicActor->getPtr();
|
||||||
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
|
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
|
||||||
newPosition = oldPosition;
|
newPosition = oldPosition;
|
||||||
|
else if(!actor.mFlying && actor.mPosition.z() >= swimlevel)
|
||||||
|
forceGroundTest = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Can't move this way, try to find another spot along the plane
|
// Can't step up, so slide against what we ran into
|
||||||
osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal);
|
remainingTime *= (1.0f-tracer.mFraction);
|
||||||
|
|
||||||
// Do not allow sliding upward if there is gravity.
|
auto planeNormal = tracer.mPlaneNormal;
|
||||||
// Stepping will have taken care of that.
|
|
||||||
if(!(newPosition.z() < swimlevel || actor.mFlying))
|
|
||||||
newVelocity.z() = std::min(newVelocity.z(), 0.0f);
|
|
||||||
|
|
||||||
if ((newVelocity-velocity).length2() < 0.01)
|
// If we touched the ground this frame, and whatever we ran into is a wall of some sort,
|
||||||
|
// pretend that its collision normal is pointing horizontally
|
||||||
|
// (fixes snagging on slightly downward-facing walls, and crawling up the bases of very steep walls because of the collision margin)
|
||||||
|
if (seenGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0)
|
||||||
|
{
|
||||||
|
planeNormal.z() = 0;
|
||||||
|
planeNormal.normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move up to what we ran into (with a bit of a collision margin)
|
||||||
|
if ((newPosition-tracer.mEndPos).length2() > sCollisionMargin*sCollisionMargin)
|
||||||
|
{
|
||||||
|
auto direction = velocity;
|
||||||
|
direction.normalize();
|
||||||
|
newPosition = tracer.mEndPos;
|
||||||
|
newPosition -= direction*sCollisionMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Vec3f newVelocity = (velocity * planeNormal <= 0.0) ? reject(velocity, planeNormal) : velocity;
|
||||||
|
bool usedSeamLogic = false;
|
||||||
|
|
||||||
|
// check for the current and previous collision planes forming an acute angle; slide along the seam if they do
|
||||||
|
if(numTimesSlid > 0)
|
||||||
|
{
|
||||||
|
auto dotA = lastSlideNormal * planeNormal;
|
||||||
|
auto dotB = lastSlideNormalFallback * planeNormal;
|
||||||
|
if(numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide
|
||||||
|
dotB = 1.0;
|
||||||
|
if(dotA <= 0.0 || dotB <= 0.0)
|
||||||
|
{
|
||||||
|
osg::Vec3f bestNormal = lastSlideNormal;
|
||||||
|
// use previous-to-previous collision plane if it's acute with current plane but actual previous plane isn't
|
||||||
|
if(dotB < dotA)
|
||||||
|
{
|
||||||
|
bestNormal = lastSlideNormalFallback;
|
||||||
|
lastSlideNormal = lastSlideNormalFallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto constraintVector = bestNormal ^ planeNormal; // cross product
|
||||||
|
if(constraintVector.length2() > 0) // only if it's not zero length
|
||||||
|
{
|
||||||
|
constraintVector.normalize();
|
||||||
|
newVelocity = project(velocity, constraintVector);
|
||||||
|
|
||||||
|
// version of surface rejection for acute crevices/seams
|
||||||
|
auto averageNormal = bestNormal + planeNormal;
|
||||||
|
averageNormal.normalize();
|
||||||
|
tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld);
|
||||||
|
newPosition = (newPosition + tracer.mEndPos)/2.0;
|
||||||
|
|
||||||
|
usedSeamLogic = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// otherwise just keep the normal vector rejection
|
||||||
|
|
||||||
|
// if this isn't the first iteration, or if the first iteration is also the last iteration,
|
||||||
|
// move away from the collision plane slightly, if possible
|
||||||
|
// this reduces getting stuck in some concave geometry, like the gaps above the railings in some ald'ruhn buildings
|
||||||
|
// this is different from the normal collision margin, because the normal collision margin is along the movement path,
|
||||||
|
// but this is along the collision normal
|
||||||
|
if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f))
|
||||||
|
{
|
||||||
|
tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld);
|
||||||
|
newPosition = (newPosition + tracer.mEndPos)/2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not allow sliding up steep slopes if there is gravity.
|
||||||
|
if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal))
|
||||||
|
newVelocity.z() = std::min(newVelocity.z(), velocity.z());
|
||||||
|
|
||||||
|
if (newVelocity * origVelocity <= 0.0f)
|
||||||
break;
|
break;
|
||||||
if ((newVelocity * origVelocity) <= 0.f)
|
|
||||||
break; // ^ dot product
|
|
||||||
|
|
||||||
|
numTimesSlid += 1;
|
||||||
|
lastSlideNormalFallback = lastSlideNormal;
|
||||||
|
lastSlideNormal = planeNormal;
|
||||||
velocity = newVelocity;
|
velocity = newVelocity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isOnGround = false;
|
bool isOnGround = false;
|
||||||
bool isOnSlope = false;
|
bool isOnSlope = false;
|
||||||
if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel))
|
if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel))
|
||||||
{
|
{
|
||||||
osg::Vec3f from = newPosition;
|
osg::Vec3f from = newPosition;
|
||||||
osg::Vec3f to = newPosition - (physicActor->getOnGround() ? osg::Vec3f(0,0,sStepSizeDown + 2*sGroundOffset) : osg::Vec3f(0,0,2*sGroundOffset));
|
auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0);
|
||||||
|
osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance);
|
||||||
tracer.doTrace(colobj, from, to, collisionWorld);
|
tracer.doTrace(colobj, from, to, collisionWorld);
|
||||||
if(tracer.mFraction < 1.0f && !isActor(tracer.mHitObject))
|
if(tracer.mFraction < 1.0f)
|
||||||
{
|
{
|
||||||
|
if (!isActor(tracer.mHitObject))
|
||||||
|
{
|
||||||
|
isOnGround = true;
|
||||||
|
isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
|
||||||
|
|
||||||
const btCollisionObject* standingOn = tracer.mHitObject;
|
const btCollisionObject* standingOn = tracer.mHitObject;
|
||||||
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
|
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
|
||||||
if (ptrHolder)
|
if (ptrHolder)
|
||||||
|
@ -369,34 +498,28 @@ namespace MWPhysics
|
||||||
|
|
||||||
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
|
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
|
||||||
physicActor->setWalkingOnWater(true);
|
physicActor->setWalkingOnWater(true);
|
||||||
if (!actor.mFlying)
|
if (!actor.mFlying && !isOnSlope)
|
||||||
|
{
|
||||||
|
if (tracer.mFraction*dropDistance > sGroundOffset)
|
||||||
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
|
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
|
||||||
|
else
|
||||||
isOnGround = true;
|
{
|
||||||
|
newPosition.z() = tracer.mEndPos.z();
|
||||||
isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
|
tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld);
|
||||||
|
newPosition = (newPosition+tracer.mEndPos)/2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// standing on actors is not allowed (see above).
|
// Vanilla allows actors to float on top of other actors. Do not push them off.
|
||||||
// in addition to that, apply a sliding effect away from the center of the actor,
|
if (!actor.mFlying && isWalkableSlope(tracer.mPlaneNormal) && tracer.mEndPos.z()+sGroundOffset <= newPosition.z())
|
||||||
// so that we do not stay suspended in air indefinitely.
|
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
|
||||||
if (tracer.mFraction < 1.0f && isActor(tracer.mHitObject))
|
|
||||||
{
|
|
||||||
if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f)
|
|
||||||
{
|
|
||||||
btVector3 aabbMin, aabbMax;
|
|
||||||
tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
|
|
||||||
btVector3 center = (aabbMin + aabbMax) / 2.f;
|
|
||||||
inertia = osg::Vec3f(actor.mPosition.x() - center.x(), actor.mPosition.y() - center.y(), 0);
|
|
||||||
inertia.normalize();
|
|
||||||
inertia *= 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isOnGround = false;
|
isOnGround = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
|
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
|
||||||
physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
|
physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
|
||||||
|
@ -414,7 +537,98 @@ namespace MWPhysics
|
||||||
physicActor->setOnGround(isOnGround);
|
physicActor->setOnGround(isOnGround);
|
||||||
physicActor->setOnSlope(isOnSlope);
|
physicActor->setOnSlope(isOnSlope);
|
||||||
|
|
||||||
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
|
|
||||||
actor.mPosition = newPosition;
|
actor.mPosition = newPosition;
|
||||||
|
// remove what was added earlier in compensating for doTrace not taking interior transformation into account
|
||||||
|
actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate
|
||||||
|
}
|
||||||
|
|
||||||
|
btVector3 addMarginToDelta(btVector3 delta)
|
||||||
|
{
|
||||||
|
if(delta.length2() == 0.0)
|
||||||
|
return delta;
|
||||||
|
return delta + delta.normalized() * sCollisionMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld)
|
||||||
|
{
|
||||||
|
const auto& ptr = actor.mActorRaw->getPtr();
|
||||||
|
if (!ptr.getClass().isMobile(ptr))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto* physicActor = actor.mActorRaw;
|
||||||
|
if(!physicActor->getCollisionMode()) // noclipping/tcl
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto* collisionObject = physicActor->getCollisionObject();
|
||||||
|
auto tempPosition = actor.mPosition;
|
||||||
|
|
||||||
|
// use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver)
|
||||||
|
// if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
|
||||||
|
const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z());
|
||||||
|
|
||||||
|
// use a 3d approximation of the movement vector to better judge player intent
|
||||||
|
const ESM::Position& refpos = ptr.getRefData().getPosition();
|
||||||
|
auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
|
||||||
|
// try to pop outside of the world before doing anything else if we're inside of it
|
||||||
|
if (!physicActor->getOnGround() || physicActor->getOnSlope())
|
||||||
|
velocity += physicActor->getInertialForce();
|
||||||
|
|
||||||
|
// because of the internal collision box offset hack, and the fact that we're moving the collision box manually,
|
||||||
|
// we need to replicate part of the collision box's transform process from scratch
|
||||||
|
osg::Vec3f refPosition = tempPosition + verticalHalfExtent;
|
||||||
|
osg::Vec3f goodPosition = refPosition;
|
||||||
|
const btTransform oldTransform = collisionObject->getWorldTransform();
|
||||||
|
btTransform newTransform = oldTransform;
|
||||||
|
|
||||||
|
auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback
|
||||||
|
{
|
||||||
|
goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset));
|
||||||
|
newTransform.setOrigin(Misc::Convert::toBullet(goodPosition));
|
||||||
|
collisionObject->setWorldTransform(newTransform);
|
||||||
|
|
||||||
|
ContactCollectionCallback callback{collisionObject, velocity};
|
||||||
|
ContactTestWrapper::contactTest(const_cast<btCollisionWorld*>(collisionWorld), collisionObject, callback);
|
||||||
|
return callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
// check whether we're inside the world with our collision box with manually-derived offset
|
||||||
|
auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
|
||||||
|
if(contactCallback.mDistance < -sAllowedPenetration)
|
||||||
|
{
|
||||||
|
// we are; try moving it out of the world
|
||||||
|
auto positionDelta = contactCallback.mContactSum;
|
||||||
|
// limit rejection delta to the largest known individual rejections
|
||||||
|
if(std::abs(positionDelta.x()) > contactCallback.mMaxX)
|
||||||
|
positionDelta *= contactCallback.mMaxX / std::abs(positionDelta.x());
|
||||||
|
if(std::abs(positionDelta.y()) > contactCallback.mMaxY)
|
||||||
|
positionDelta *= contactCallback.mMaxY / std::abs(positionDelta.y());
|
||||||
|
if(std::abs(positionDelta.z()) > contactCallback.mMaxZ)
|
||||||
|
positionDelta *= contactCallback.mMaxZ / std::abs(positionDelta.z());
|
||||||
|
|
||||||
|
auto contactCallback2 = gatherContacts(positionDelta);
|
||||||
|
// successfully moved further out from contact (does not have to be in open space, just less inside of things)
|
||||||
|
if(contactCallback2.mDistance > contactCallback.mDistance)
|
||||||
|
tempPosition = goodPosition - verticalHalfExtent;
|
||||||
|
// try again but only upwards (fixes some bad coc floors)
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// upwards-only offset
|
||||||
|
auto contactCallback3 = gatherContacts({0.0, 0.0, std::abs(positionDelta.z())});
|
||||||
|
// success
|
||||||
|
if(contactCallback3.mDistance > contactCallback.mDistance)
|
||||||
|
tempPosition = goodPosition - verticalHalfExtent;
|
||||||
|
else
|
||||||
|
// try again but fixed distance up
|
||||||
|
{
|
||||||
|
auto contactCallback4 = gatherContacts({0.0, 0.0, 10.0});
|
||||||
|
// success
|
||||||
|
if(contactCallback4.mDistance > contactCallback.mDistance)
|
||||||
|
tempPosition = goodPosition - verticalHalfExtent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collisionObject->setWorldTransform(oldTransform);
|
||||||
|
actor.mPosition = tempPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
|
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
|
#include "constants.hpp"
|
||||||
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
class btCollisionWorld;
|
class btCollisionWorld;
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
|
@ -14,29 +17,35 @@ namespace MWWorld
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
|
/// Vector projection
|
||||||
|
static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
|
||||||
|
{
|
||||||
|
return v * (u * v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vector rejection
|
||||||
|
static inline osg::Vec3f reject(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
|
||||||
|
{
|
||||||
|
return direction - project(direction, planeNormal);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Vec3>
|
||||||
|
static bool isWalkableSlope(const Vec3 &normal)
|
||||||
|
{
|
||||||
|
static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
|
||||||
|
return (normal.z() > sMaxSlopeCos);
|
||||||
|
}
|
||||||
|
|
||||||
class Actor;
|
class Actor;
|
||||||
struct ActorFrameData;
|
struct ActorFrameData;
|
||||||
struct WorldFrameData;
|
struct WorldFrameData;
|
||||||
|
|
||||||
class MovementSolver
|
class MovementSolver
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
///Project a vector u on another vector v
|
|
||||||
static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
|
|
||||||
{
|
|
||||||
return v * (u * v);
|
|
||||||
// ^ dot product
|
|
||||||
}
|
|
||||||
|
|
||||||
///Helper for computing the character sliding
|
|
||||||
static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
|
|
||||||
{
|
|
||||||
return direction - project(direction, planeNormal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
|
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
|
||||||
static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData);
|
static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData);
|
||||||
|
static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "../mwworld/player.hpp"
|
#include "../mwworld/player.hpp"
|
||||||
|
|
||||||
#include "actor.hpp"
|
#include "actor.hpp"
|
||||||
|
#include "contacttestwrapper.h"
|
||||||
#include "movementsolver.hpp"
|
#include "movementsolver.hpp"
|
||||||
#include "mtphysics.hpp"
|
#include "mtphysics.hpp"
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
|
@ -87,12 +88,13 @@ namespace
|
||||||
|
|
||||||
void updateMechanics(MWPhysics::ActorFrameData& actorData)
|
void updateMechanics(MWPhysics::ActorFrameData& actorData)
|
||||||
{
|
{
|
||||||
|
auto ptr = actorData.mActorRaw->getPtr();
|
||||||
if (actorData.mDidJump)
|
if (actorData.mDidJump)
|
||||||
handleJump(actorData.mPtr);
|
handleJump(ptr);
|
||||||
|
|
||||||
MWMechanics::CreatureStats& stats = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr);
|
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||||
if (actorData.mNeedLand)
|
if (actorData.mNeedLand)
|
||||||
stats.land(actorData.mPtr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
|
stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
|
||||||
else if (actorData.mFallHeight < 0)
|
else if (actorData.mFallHeight < 0)
|
||||||
stats.addToFallHeight(-actorData.mFallHeight);
|
stats.addToFallHeight(-actorData.mFallHeight);
|
||||||
}
|
}
|
||||||
|
@ -150,6 +152,9 @@ namespace MWPhysics
|
||||||
, mNextLOS(0)
|
, mNextLOS(0)
|
||||||
, mFrameNumber(0)
|
, mFrameNumber(0)
|
||||||
, mTimer(osg::Timer::instance())
|
, mTimer(osg::Timer::instance())
|
||||||
|
, mTimeBegin(0)
|
||||||
|
, mTimeEnd(0)
|
||||||
|
, mFrameStart(0)
|
||||||
{
|
{
|
||||||
mNumThreads = Config::computeNumThreads(mThreadSafeBullet);
|
mNumThreads = Config::computeNumThreads(mThreadSafeBullet);
|
||||||
|
|
||||||
|
@ -166,7 +171,16 @@ namespace MWPhysics
|
||||||
|
|
||||||
mPreStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
|
mPreStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
|
||||||
{
|
{
|
||||||
|
if (mDeferAabbUpdate)
|
||||||
updateAabbs();
|
updateAabbs();
|
||||||
|
if (!mRemainingSteps)
|
||||||
|
return;
|
||||||
|
for (auto& data : mActorsFrameData)
|
||||||
|
if (data.mActor.lock())
|
||||||
|
{
|
||||||
|
std::unique_lock lock(mCollisionWorldMutex);
|
||||||
|
MovementSolver::unstuck(data, mCollisionWorld.get());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mPostStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
|
mPostStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
|
||||||
|
@ -218,8 +232,13 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
for (auto& data : mActorsFrameData)
|
for (auto& data : mActorsFrameData)
|
||||||
{
|
{
|
||||||
|
const auto actorActive = [&data](const auto& newFrameData) -> bool
|
||||||
|
{
|
||||||
|
const auto actor = data.mActor.lock();
|
||||||
|
return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr();
|
||||||
|
};
|
||||||
// Only return actors that are still part of the scene
|
// Only return actors that are still part of the scene
|
||||||
if (std::any_of(actorsData.begin(), actorsData.end(), [&data](const auto& newFrameData) { return data.mActorRaw->getPtr() == newFrameData.mActorRaw->getPtr(); }))
|
if (std::any_of(actorsData.begin(), actorsData.end(), actorActive))
|
||||||
{
|
{
|
||||||
updateMechanics(data);
|
updateMechanics(data);
|
||||||
|
|
||||||
|
@ -230,16 +249,7 @@ namespace MWPhysics
|
||||||
mMovedActors.emplace_back(data.mActorRaw->getPtr());
|
mMovedActors.emplace_back(data.mActorRaw->getPtr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateStats(frameStart, frameNumber, stats);
|
||||||
if (mFrameNumber == frameNumber - 1)
|
|
||||||
{
|
|
||||||
stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin));
|
|
||||||
stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd));
|
|
||||||
stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd));
|
|
||||||
}
|
|
||||||
mFrameStart = frameStart;
|
|
||||||
mTimeBegin = mTimer->tick();
|
|
||||||
mFrameNumber = frameNumber;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
|
@ -275,8 +285,8 @@ namespace MWPhysics
|
||||||
mActorsFrameData.clear();
|
mActorsFrameData.clear();
|
||||||
for (const auto& [_, actor] : actors)
|
for (const auto& [_, actor] : actors)
|
||||||
{
|
{
|
||||||
actor->resetPosition();
|
actor->updatePosition();
|
||||||
actor->setSimulationPosition(actor->getWorldPosition()); // resetPosition skip next simulation, now we need to "consume" it
|
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());
|
||||||
}
|
}
|
||||||
|
@ -298,7 +308,7 @@ namespace MWPhysics
|
||||||
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
|
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mCollisionWorldMutex);
|
std::shared_lock lock(mCollisionWorldMutex);
|
||||||
mCollisionWorld->contactTest(colObj, resultCallback);
|
ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
|
std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
|
||||||
|
@ -352,7 +362,6 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
if (!mDeferAabbUpdate || immediate)
|
if (!mDeferAabbUpdate || immediate)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mCollisionWorldMutex);
|
|
||||||
updatePtrAabb(ptr);
|
updatePtrAabb(ptr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -405,7 +414,7 @@ namespace MWPhysics
|
||||||
|
|
||||||
void PhysicsTaskScheduler::updateAabbs()
|
void PhysicsTaskScheduler::updateAabbs()
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mCollisionWorldMutex, mUpdateAabbMutex);
|
std::scoped_lock lock(mUpdateAabbMutex);
|
||||||
std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(),
|
std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(),
|
||||||
[this](const std::weak_ptr<PtrHolder>& ptr) { updatePtrAabb(ptr); });
|
[this](const std::weak_ptr<PtrHolder>& ptr) { updatePtrAabb(ptr); });
|
||||||
mUpdateAabb.clear();
|
mUpdateAabb.clear();
|
||||||
|
@ -415,6 +424,7 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
if (const auto p = ptr.lock())
|
if (const auto p = ptr.lock())
|
||||||
{
|
{
|
||||||
|
std::scoped_lock lock(mCollisionWorldMutex);
|
||||||
if (const auto actor = std::dynamic_pointer_cast<Actor>(p))
|
if (const auto actor = std::dynamic_pointer_cast<Actor>(p))
|
||||||
{
|
{
|
||||||
actor->updateCollisionObjectPosition();
|
actor->updateCollisionObjectPosition();
|
||||||
|
@ -441,16 +451,17 @@ namespace MWPhysics
|
||||||
if (!mNewFrame)
|
if (!mNewFrame)
|
||||||
mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; });
|
mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; });
|
||||||
|
|
||||||
if (mDeferAabbUpdate)
|
|
||||||
mPreStepBarrier->wait();
|
mPreStepBarrier->wait();
|
||||||
|
|
||||||
int job = 0;
|
int job = 0;
|
||||||
while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
|
while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
|
||||||
{
|
{
|
||||||
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
|
|
||||||
if(const auto actor = mActorsFrameData[job].mActor.lock())
|
if(const auto actor = mActorsFrameData[job].mActor.lock())
|
||||||
|
{
|
||||||
|
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
|
||||||
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mPostStepBarrier->wait();
|
mPostStepBarrier->wait();
|
||||||
|
|
||||||
|
@ -474,13 +485,13 @@ namespace MWPhysics
|
||||||
|
|
||||||
void PhysicsTaskScheduler::updateActorsPositions()
|
void PhysicsTaskScheduler::updateActorsPositions()
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mCollisionWorldMutex);
|
|
||||||
for (auto& actorData : mActorsFrameData)
|
for (auto& actorData : mActorsFrameData)
|
||||||
{
|
{
|
||||||
if(const auto actor = actorData.mActor.lock())
|
if(const auto actor = actorData.mActor.lock())
|
||||||
{
|
{
|
||||||
if (actor->setPosition(actorData.mPosition))
|
if (actor->setPosition(actorData.mPosition))
|
||||||
{
|
{
|
||||||
|
std::scoped_lock lock(mCollisionWorldMutex);
|
||||||
actor->updateCollisionObjectPosition();
|
actor->updateCollisionObjectPosition();
|
||||||
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
|
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
|
||||||
}
|
}
|
||||||
|
@ -508,7 +519,10 @@ namespace MWPhysics
|
||||||
while (mRemainingSteps--)
|
while (mRemainingSteps--)
|
||||||
{
|
{
|
||||||
for (auto& actorData : mActorsFrameData)
|
for (auto& actorData : mActorsFrameData)
|
||||||
|
{
|
||||||
|
MovementSolver::unstuck(actorData, mCollisionWorld.get());
|
||||||
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
||||||
|
}
|
||||||
|
|
||||||
updateActorsPositions();
|
updateActorsPositions();
|
||||||
}
|
}
|
||||||
|
@ -523,4 +537,19 @@ namespace MWPhysics
|
||||||
actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn);
|
actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
||||||
|
{
|
||||||
|
if (!stats.collectStats("engine"))
|
||||||
|
return;
|
||||||
|
if (mFrameNumber == frameNumber - 1)
|
||||||
|
{
|
||||||
|
stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin));
|
||||||
|
stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd));
|
||||||
|
stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd));
|
||||||
|
}
|
||||||
|
mFrameStart = frameStart;
|
||||||
|
mTimeBegin = mTimer->tick();
|
||||||
|
mFrameNumber = frameNumber;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ namespace MWPhysics
|
||||||
void refreshLOSCache();
|
void refreshLOSCache();
|
||||||
void updateAabbs();
|
void updateAabbs();
|
||||||
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
|
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
|
||||||
|
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||||
|
|
||||||
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
||||||
std::vector<ActorFrameData> mActorsFrameData;
|
std::vector<ActorFrameData> mActorsFrameData;
|
||||||
|
|
|
@ -14,28 +14,28 @@
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, PhysicsTaskScheduler* scheduler)
|
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler)
|
||||||
: mShapeInstance(shapeInstance)
|
: mShapeInstance(shapeInstance)
|
||||||
, mSolid(true)
|
, mSolid(true)
|
||||||
, mTaskScheduler(scheduler)
|
, mTaskScheduler(scheduler)
|
||||||
{
|
{
|
||||||
mPtr = ptr;
|
mPtr = ptr;
|
||||||
|
|
||||||
mCollisionObject.reset(new btCollisionObject);
|
mCollisionObject = std::make_unique<btCollisionObject>();
|
||||||
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
||||||
|
|
||||||
mCollisionObject->setUserPointer(this);
|
mCollisionObject->setUserPointer(this);
|
||||||
|
|
||||||
setScale(ptr.getCellRef().getScale());
|
setScale(ptr.getCellRef().getScale());
|
||||||
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
||||||
const float* pos = ptr.getRefData().getPosition().pos;
|
setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3()));
|
||||||
setOrigin(btVector3(pos[0], pos[1], pos[2]));
|
|
||||||
commitPositionChange();
|
commitPositionChange();
|
||||||
|
|
||||||
|
mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object::~Object()
|
Object::~Object()
|
||||||
{
|
{
|
||||||
if (mCollisionObject)
|
|
||||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace MWPhysics
|
||||||
class Object final : public PtrHolder
|
class Object final : public PtrHolder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, PhysicsTaskScheduler* scheduler);
|
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler);
|
||||||
~Object() override;
|
~Object() override;
|
||||||
|
|
||||||
const Resource::BulletShapeInstance* getShapeInstance() const;
|
const Resource::BulletShapeInstance* getShapeInstance() const;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue