mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 15:29:55 +00:00
Merge remote-tracking branch 'upstream/master' into alpha-meddling
This commit is contained in:
commit
4f510d85ba
236 changed files with 4867 additions and 1923 deletions
|
@ -116,12 +116,13 @@ variables: &cs-targets
|
|||
- .\ActivateMSVC.ps1
|
||||
- cmake --build . --config $config --target ($targets.Split(','))
|
||||
- cd $config
|
||||
- echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt
|
||||
- |
|
||||
if (Get-ChildItem -Recurse *.pdb) {
|
||||
7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb'
|
||||
7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt
|
||||
Get-ChildItem -Recurse *.pdb | Remove-Item
|
||||
}
|
||||
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*'
|
||||
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*'
|
||||
after_script:
|
||||
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
|
||||
cache:
|
||||
|
@ -206,12 +207,13 @@ Windows_Ninja_CS_RelWithDebInfo:
|
|||
- cd MSVC2019_64
|
||||
- cmake --build . --config $config --target ($targets.Split(','))
|
||||
- cd $config
|
||||
- echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt
|
||||
- |
|
||||
if (Get-ChildItem -Recurse *.pdb) {
|
||||
7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb'
|
||||
7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt
|
||||
Get-ChildItem -Recurse *.pdb | Remove-Item
|
||||
}
|
||||
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*'
|
||||
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*'
|
||||
after_script:
|
||||
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
|
||||
cache:
|
||||
|
|
|
@ -215,6 +215,7 @@ Programmers
|
|||
Yohaulticetl
|
||||
Yuri Krupenin
|
||||
zelurker
|
||||
Noah Gooder
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
|
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -3,12 +3,14 @@
|
|||
|
||||
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 #1901: Actors colliding behaviour is different from vanilla
|
||||
Bug #1952: Incorrect particle lighting
|
||||
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
|
||||
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
|
||||
Bug #2473: Unable to overstock merchants
|
||||
Bug #2798: Mutable ESM records
|
||||
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 #3676: NiParticleColorModifier isn't applied properly
|
||||
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 #3929: Leveled list merchant containers respawn on barter
|
||||
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 #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 #4631: Setting MSAA level too high doesn't fall back to highest supported level
|
||||
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 #5101: Hostile followers travel with the player
|
||||
Bug #5108: Savegame bloating due to inefficient fog textures format
|
||||
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 #5363: Enchantment autocalc not always 0/1
|
||||
Bug #5364: Script fails/stops if trying to startscript an unknown script
|
||||
|
@ -33,6 +46,7 @@
|
|||
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 #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 #5400: Editor: Verifier checks race of non-skin bodyparts
|
||||
Bug #5403: Enchantment effect doesn't show on an enemy during death animation
|
||||
|
@ -47,6 +61,7 @@
|
|||
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 #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 #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
|
||||
|
@ -64,25 +79,39 @@
|
|||
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 #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 #5639: Tooltips cover Messageboxes
|
||||
Bug #5644: Summon effects running on the player during game initialization cause crashes
|
||||
Bug #5656: Sneaking characters block hits while standing
|
||||
Bug #5661: Region sounds don't play at the right interval
|
||||
Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx
|
||||
Bug #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 #5695: ExplodeSpell for actors doesn't target the ground
|
||||
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 #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
|
||||
Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee
|
||||
Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee
|
||||
Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod.
|
||||
Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior )
|
||||
Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0
|
||||
Feature #390: 3rd person look "over the shoulder"
|
||||
Feature #1536: Show more information about level on menu
|
||||
Feature #2386: Distant Statics in the form of Object Paging
|
||||
Feature #2404: Levelled List can not be placed into a container
|
||||
Feature #2686: Timestamps in openmw.log
|
||||
Feature #3171: OpenMW-CS: Instance drag selection
|
||||
Feature #4894: Consider actors as obstacles for pathfinding
|
||||
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
|
||||
Feature #4977: Use the "default icon.tga" when an item's icon is not found
|
||||
Feature #5043: Head Bobbing
|
||||
Feature #5199: Improve Scene Colors
|
||||
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
|
||||
|
@ -104,8 +133,11 @@
|
|||
Feature #5672: Make stretch menu background configuration more accessible
|
||||
Feature #5692: Improve spell/magic item search to factor in magic effect names
|
||||
Feature #5730: Add graphic herbalism option to the launcher and documents
|
||||
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 #5520: Improve cell name autocompleter implementation
|
||||
Task #5844: Update 'toggle sneak' documentation
|
||||
|
||||
0.46.0
|
||||
------
|
||||
|
|
|
@ -21,7 +21,7 @@ New Features:
|
|||
- Basics of Collada animations are now supported via osgAnimation plugin (#5456)
|
||||
|
||||
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:
|
||||
- NiParticleColorModifier in NIF files is now properly handled which solves issues regarding particle effects, e.g., smoke and fire (#1952, #3676)
|
||||
|
@ -35,10 +35,13 @@ Bug Fixes:
|
|||
|
||||
Editor Bug Fixes:
|
||||
- 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)
|
||||
- Loading mods now keeps the master index (#5675)
|
||||
- Flicker and crashing on XFCE4 fixed (#5703)
|
||||
- Collada models render properly in the Editor (#5713)
|
||||
|
||||
Miscellaneous:
|
||||
- Prevent save-game bloating by using an appropriate fog texture format (#5108)
|
||||
- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363)
|
||||
- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/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
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
#!/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
|
||||
command -v ccache >/dev/null 2>&1 || brew install ccache
|
||||
command -v cmake >/dev/null 2>&1 || brew install cmake
|
||||
|
|
|
@ -73,7 +73,7 @@ CONFIGURATIONS=()
|
|||
TEST_FRAMEWORK=""
|
||||
GOOGLE_INSTALL_ROOT=""
|
||||
INSTALL_PREFIX="."
|
||||
BULLET_DOUBLE=""
|
||||
BULLET_DOUBLE=true
|
||||
BULLET_DBL=""
|
||||
BULLET_DBL_DISPLAY="Single precision"
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.1.0)
|
|||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Detect OS
|
||||
include(cmake/OSIdentity.cmake)
|
||||
|
||||
# for link time optimization, remove if cmake version is >= 3.9
|
||||
if(POLICY CMP0069)
|
||||
cmake_policy(SET CMP0069 NEW)
|
||||
|
@ -21,7 +24,7 @@ option(BUILD_NIFTEST "Build nif file tester" ON)
|
|||
option(BUILD_DOCS "Build documentation." OFF )
|
||||
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
|
||||
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
|
||||
option(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.
|
||||
|
||||
|
@ -320,6 +323,7 @@ include_directories("."
|
|||
${Boost_INCLUDE_DIR}
|
||||
${MyGUI_INCLUDE_DIRS}
|
||||
${OPENAL_INCLUDE_DIR}
|
||||
${OPENGL_INCLUDE_DIR}
|
||||
${BULLET_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
|
|
@ -10,11 +10,9 @@
|
|||
|
||||
#include <cmath>
|
||||
|
||||
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
|
||||
Config::GameSettings &gameSettings,
|
||||
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
|
||||
Settings::Manager &engineSettings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, mCfgMgr(cfg)
|
||||
, mGameSettings(gameSettings)
|
||||
, mEngineSettings(engineSettings)
|
||||
{
|
||||
|
@ -64,12 +62,12 @@ namespace
|
|||
|
||||
double convertToCells(double unitRadius)
|
||||
{
|
||||
return std::round((unitRadius / 0.93 + 1024) / CellSizeInUnits);
|
||||
return std::round((unitRadius + 1024) / CellSizeInUnits);
|
||||
}
|
||||
|
||||
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");
|
||||
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
|
||||
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
|
||||
if (animSourcesCheckBox->checkState())
|
||||
if (animSourcesCheckBox->checkState() != Qt::Unchecked)
|
||||
{
|
||||
loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
|
||||
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
namespace Files { struct ConfigurationManager; }
|
||||
namespace Config { class GameSettings; }
|
||||
|
||||
namespace Launcher
|
||||
|
@ -19,7 +18,7 @@ namespace Launcher
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
|
||||
AdvancedPage(Config::GameSettings &gameSettings,
|
||||
Settings::Manager &engineSettings, QWidget *parent = nullptr);
|
||||
|
||||
bool loadSettings();
|
||||
|
@ -35,7 +34,6 @@ namespace Launcher
|
|||
void slotViewOverShoulderToggled(bool checked);
|
||||
|
||||
private:
|
||||
Files::ConfigurationManager &mCfgMgr;
|
||||
Config::GameSettings &mGameSettings;
|
||||
Settings::Manager &mEngineSettings;
|
||||
QCompleter mCellNameCompleter;
|
||||
|
|
|
@ -88,11 +88,7 @@ namespace Launcher
|
|||
QStringList previousSelectedFiles;
|
||||
QString mDataLocal;
|
||||
|
||||
void setPluginsCheckstates(Qt::CheckState state);
|
||||
|
||||
void buildView();
|
||||
void setupConfig();
|
||||
void readConfig();
|
||||
void setProfile (int index, bool savePrevious);
|
||||
void setProfile (const QString &previous, const QString ¤t, bool savePrevious);
|
||||
void removeProfile (const QString &profile);
|
||||
|
|
|
@ -29,9 +29,8 @@ QString getAspect(int x, int y)
|
|||
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)
|
||||
, mCfgMgr(cfg)
|
||||
, mEngineSettings(engineSettings)
|
||||
{
|
||||
setObjectName ("GraphicsPage");
|
||||
|
@ -207,7 +206,7 @@ void Launcher::GraphicsPage::saveSettings()
|
|||
if (cScreen != mEngineSettings.getInt("screen", "Video"))
|
||||
mEngineSettings.setInt("screen", "Video", cScreen);
|
||||
|
||||
if (framerateLimitCheckBox->checkState())
|
||||
if (framerateLimitCheckBox->checkState() != Qt::Unchecked)
|
||||
{
|
||||
float cFpsLimit = framerateLimitSpinBox->value();
|
||||
if (cFpsLimit != mEngineSettings.getFloat("framerate limit", "Video"))
|
||||
|
@ -218,7 +217,7 @@ void Launcher::GraphicsPage::saveSettings()
|
|||
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)
|
||||
mEngineSettings.setInt("maximum shadow map distance", "Shadows", cShadowDist);
|
||||
float cFadeStart = fadeStartSpinBox->value();
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace Launcher
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = nullptr);
|
||||
GraphicsPage(Settings::Manager &engineSettings, QWidget *parent = nullptr);
|
||||
|
||||
void saveSettings();
|
||||
bool loadSettings();
|
||||
|
@ -35,7 +35,6 @@ namespace Launcher
|
|||
void slotShadowDistLimitToggled(bool checked);
|
||||
|
||||
private:
|
||||
Files::ConfigurationManager &mCfgMgr;
|
||||
Settings::Manager &mEngineSettings;
|
||||
|
||||
QVector<QStringList> mResolutionsPerScreen;
|
||||
|
|
|
@ -126,9 +126,9 @@ void Launcher::MainDialog::createPages()
|
|||
|
||||
mPlayPage = new PlayPage(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);
|
||||
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
|
||||
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
|
||||
|
|
|
@ -45,4 +45,5 @@ QString CellNameLoader::getCellName(ESM::ESMReader &esmReader)
|
|||
cell.loadNameAndData(esmReader, isDeleted);
|
||||
|
||||
return QString::fromStdString(cell.mName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,9 @@ int runApplication(int argc, char *argv[])
|
|||
application.setWindowIcon (QIcon (":./openmw-cs.png"));
|
||||
|
||||
CS::Editor editor(argc, argv);
|
||||
#ifdef __linux__
|
||||
setlocale(LC_NUMERIC,"C");
|
||||
#endif
|
||||
|
||||
if(!editor.makeIPCServer())
|
||||
{
|
||||
|
|
|
@ -325,12 +325,6 @@ std::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseNAry (const Token& keyw
|
|||
break;
|
||||
}
|
||||
|
||||
if (nodes.empty())
|
||||
{
|
||||
error();
|
||||
return std::shared_ptr<Node>();
|
||||
}
|
||||
|
||||
switch (keyword.mType)
|
||||
{
|
||||
case Token::Type_Keyword_And: return std::shared_ptr<CSMFilter::Node> (new AndNode (nodes));
|
||||
|
|
|
@ -247,6 +247,15 @@ void CSMPrefs::State::declare()
|
|||
EnumValues landeditOutsideVisibleCell;
|
||||
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");
|
||||
declareInt ("distance", "Drop Distance", 50).
|
||||
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).
|
||||
setTooltip ("When opening a reference from the scene view, it will open the"
|
||||
" 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");
|
||||
|
||||
|
|
|
@ -397,6 +397,10 @@ void CSMWorld::CloneCommand::redo()
|
|||
{
|
||||
mModel.cloneRecord (mIdOrigin, mId, mType);
|
||||
applyModifications();
|
||||
for (auto& value : mOverrideValues)
|
||||
{
|
||||
mModel.setData(mModel.getModelIndex (mId, value.first), value.second);
|
||||
}
|
||||
}
|
||||
|
||||
void CSMWorld::CloneCommand::undo()
|
||||
|
@ -404,6 +408,11 @@ void CSMWorld::CloneCommand::undo()
|
|||
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)
|
||||
: CreateCommand(model, id, parent)
|
||||
{
|
||||
|
|
|
@ -183,6 +183,7 @@ namespace CSMWorld
|
|||
class CloneCommand : public CreateCommand
|
||||
{
|
||||
std::string mIdOrigin;
|
||||
std::vector<std::pair<int, QVariant>> mOverrideValues;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -194,6 +195,8 @@ namespace CSMWorld
|
|||
void redo() override;
|
||||
|
||||
void undo() override;
|
||||
|
||||
void setOverrideValue(int column, QVariant value);
|
||||
};
|
||||
|
||||
class RevertCommand : public QUndoCommand
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "nestedtablewrapper.hpp"
|
||||
|
||||
CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns)
|
||||
: InventoryColumns (columns) {}
|
||||
: InventoryColumns (columns), mEffects(nullptr) {}
|
||||
|
||||
CSMWorld::PotionRefIdAdapter::PotionRefIdAdapter (const PotionColumns& columns,
|
||||
const RefIdColumn *autoCalc)
|
||||
|
|
|
@ -115,7 +115,7 @@ namespace CSMWorld
|
|||
{
|
||||
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)
|
||||
|
@ -1858,18 +1858,18 @@ namespace CSMWorld
|
|||
break; // always save
|
||||
case 16:
|
||||
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)
|
||||
content.mTarget.mZ = value.toFloat();
|
||||
content.mTarget.mX = value.toFloat();
|
||||
else
|
||||
return; // return without saving
|
||||
|
||||
break; // always save
|
||||
case 17:
|
||||
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)
|
||||
content.mTarget.mZ = value.toFloat();
|
||||
content.mTarget.mY = value.toFloat();
|
||||
else
|
||||
return; // return without saving
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ void CSMWorld::ResourcesManager::addResources (const Resources& resources)
|
|||
const char * const * CSMWorld::ResourcesManager::getMeshExtensions()
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -438,8 +438,8 @@ void CSVDoc::View::updateActions()
|
|||
for (std::vector<QAction *>::iterator iter (mEditingActions.begin()); iter!=mEditingActions.end(); ++iter)
|
||||
(*iter)->setEnabled (editing);
|
||||
|
||||
mUndo->setEnabled (editing & mDocument->getUndoStack().canUndo());
|
||||
mRedo->setEnabled (editing & mDocument->getUndoStack().canRedo());
|
||||
mUndo->setEnabled (editing && mDocument->getUndoStack().canUndo());
|
||||
mRedo->setEnabled (editing && mDocument->getUndoStack().canRedo());
|
||||
|
||||
mSave->setEnabled (!(mDocument->getState() & CSMDoc::State_Saving) && !running);
|
||||
mVerify->setEnabled (!(mDocument->getState() & CSMDoc::State_Verifying));
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "cell.hpp"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <osg/PositionAttitudeTransform>
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
|
@ -25,6 +27,7 @@
|
|||
#include "pathgrid.hpp"
|
||||
#include "terrainstorage.hpp"
|
||||
#include "object.hpp"
|
||||
#include "instancedragmodes.hpp"
|
||||
|
||||
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)
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "../../model/world/cellcoordinates.hpp"
|
||||
#include "terrainstorage.hpp"
|
||||
#include "instancedragmodes.hpp"
|
||||
|
||||
class QModelIndex;
|
||||
|
||||
|
@ -152,6 +153,12 @@ namespace CSVRender
|
|||
// already selected
|
||||
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);
|
||||
|
||||
/// \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;
|
||||
}
|
||||
|
||||
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)
|
||||
: EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, "Instance editing",
|
||||
parent), mSubMode (nullptr), mSubModeId ("move"), mSelectionMode (nullptr), mDragMode (DragMode_None),
|
||||
|
@ -146,7 +173,7 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar)
|
|||
}
|
||||
|
||||
if (!mSelectionMode)
|
||||
mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget());
|
||||
mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget(), mParentNode);
|
||||
|
||||
mDragMode = DragMode_None;
|
||||
|
||||
|
@ -322,6 +349,42 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos)
|
|||
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)
|
||||
{
|
||||
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
|
||||
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
|
||||
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_Rotate: description = "Rotate 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;
|
||||
}
|
||||
|
@ -680,6 +777,13 @@ void CSVRender::InstanceMode::subModeChanged (const std::string& id)
|
|||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight;
|
||||
object->setPosition(position.pos);
|
||||
}
|
||||
object->setEdited(Object::Override_Position);
|
||||
ESM::Position position = object->getPosition();
|
||||
position.pos[2] -= dropHeight;
|
||||
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();
|
||||
|
||||
|
@ -744,9 +824,9 @@ float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Objec
|
|||
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT);
|
||||
osgUtil::IntersectionVisitor visitor(intersector);
|
||||
|
||||
if (dropMode == Terrain)
|
||||
if (dropMode & Terrain)
|
||||
visitor.setTraversalMask(Mask_Terrain);
|
||||
if (dropMode == Collision)
|
||||
if (dropMode & Collision)
|
||||
visitor.setTraversalMask(Mask_Terrain | Mask_Reference);
|
||||
|
||||
mParentNode->accept(visitor);
|
||||
|
@ -774,12 +854,12 @@ void CSVRender::InstanceMode::dropSelectedInstancesToTerrain()
|
|||
|
||||
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()
|
||||
{
|
||||
handleDropMethod(CollisionSep, "Drop instances to terrain level separately");
|
||||
handleDropMethod(TerrainSep, "Drop instances to terrain level separately");
|
||||
}
|
||||
|
||||
void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg)
|
||||
|
@ -793,52 +873,44 @@ void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString comman
|
|||
|
||||
CSMWorld::CommandMacro macro (undoStack, commandMsg);
|
||||
|
||||
DropObjectDataHandler dropObjectDataHandler(&getWorldspaceWidget());
|
||||
DropObjectHeightHandler dropObjectDataHandler(&getWorldspaceWidget());
|
||||
|
||||
switch (dropMode)
|
||||
if(dropMode & Separate)
|
||||
{
|
||||
case Terrain:
|
||||
case Collision:
|
||||
{
|
||||
float smallestDropHeight = std::numeric_limits<float>::max();
|
||||
int counter = 0;
|
||||
for(osg::ref_ptr<TagBase> tag: selection)
|
||||
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||
{
|
||||
float thisDrop = getDropHeight(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]);
|
||||
if (thisDrop < smallestDropHeight)
|
||||
smallestDropHeight = thisDrop;
|
||||
counter++;
|
||||
}
|
||||
for(osg::ref_ptr<TagBase> tag: selection)
|
||||
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||
{
|
||||
objectTag->mObject->setEdited (Object::Override_Position);
|
||||
ESM::Position position = objectTag->mObject->getPosition();
|
||||
position.pos[2] -= smallestDropHeight;
|
||||
objectTag->mObject->setPosition(position.pos);
|
||||
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;
|
||||
int counter = 0;
|
||||
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();
|
||||
int counter = 0;
|
||||
for (osg::ref_ptr<TagBase> tag : selection)
|
||||
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
||||
{
|
||||
float objectHeight = dropObjectDataHandler.mObjectHeights[counter];
|
||||
float thisDrop = calculateDropHeight(dropMode, objectTag->mObject, objectHeight);
|
||||
if (thisDrop < smallestDropHeight)
|
||||
smallestDropHeight = thisDrop;
|
||||
counter++;
|
||||
}
|
||||
for (osg::ref_ptr<TagBase> tag : selection)
|
||||
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
||||
{
|
||||
dropInstance(objectTag->mObject, smallestDropHeight);
|
||||
objectTag->mObject->apply(macro);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget)
|
||||
CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget)
|
||||
: mWorldspaceWidget(worldspacewidget)
|
||||
{
|
||||
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);
|
||||
int counter = 0;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <osg/Vec3f>
|
||||
|
||||
#include "editmode.hpp"
|
||||
#include "instancedragmodes.hpp"
|
||||
|
||||
namespace CSVWidget
|
||||
{
|
||||
|
@ -25,20 +26,15 @@ namespace CSVRender
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum DragMode
|
||||
{
|
||||
DragMode_None,
|
||||
DragMode_Move,
|
||||
DragMode_Rotate,
|
||||
DragMode_Scale
|
||||
};
|
||||
|
||||
enum DropMode
|
||||
{
|
||||
Collision,
|
||||
Terrain,
|
||||
CollisionSep,
|
||||
TerrainSep
|
||||
Separate = 0b1,
|
||||
|
||||
Collision = 0b10,
|
||||
Terrain = 0b100,
|
||||
|
||||
CollisionSep = Collision | Separate,
|
||||
TerrainSep = Terrain | Separate,
|
||||
};
|
||||
|
||||
CSVWidget::SceneToolMode *mSubMode;
|
||||
|
@ -57,8 +53,11 @@ namespace CSVRender
|
|||
|
||||
osg::Vec3f getSelectionCenter(const std::vector<osg::ref_ptr<TagBase> >& selection) const;
|
||||
osg::Vec3f getScreenCoords(const osg::Vec3f& pos);
|
||||
void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight);
|
||||
float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight);
|
||||
osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos);
|
||||
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:
|
||||
|
||||
|
@ -84,6 +83,10 @@ namespace CSVRender
|
|||
|
||||
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 dragCompleted(const QPoint& pos) override;
|
||||
|
@ -116,11 +119,11 @@ namespace CSVRender
|
|||
};
|
||||
|
||||
/// \brief Helper class to handle object mask data in safe way
|
||||
class DropObjectDataHandler
|
||||
class DropObjectHeightHandler
|
||||
{
|
||||
public:
|
||||
DropObjectDataHandler(WorldspaceWidget* worldspacewidget);
|
||||
~DropObjectDataHandler();
|
||||
DropObjectHeightHandler(WorldspaceWidget* worldspacewidget);
|
||||
~DropObjectHeightHandler();
|
||||
std::vector<float> mObjectHeights;
|
||||
|
||||
private:
|
||||
|
|
|
@ -2,17 +2,24 @@
|
|||
|
||||
#include <QMenu>
|
||||
#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/commands.hpp"
|
||||
|
||||
#include "instancedragmodes.hpp"
|
||||
#include "worldspacewidget.hpp"
|
||||
#include "object.hpp"
|
||||
|
||||
namespace CSVRender
|
||||
{
|
||||
InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget)
|
||||
: SelectionMode(parent, worldspaceWidget, Mask_Reference)
|
||||
InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, osg::Group *cellNode)
|
||||
: SelectionMode(parent, worldspaceWidget, Mask_Reference), mParentNode(cellNode)
|
||||
{
|
||||
mSelectSame = new QAction("Extend selection to instances with same object ID", this);
|
||||
mDeleteSelection = new QAction("Delete selected instances", this);
|
||||
|
@ -21,6 +28,342 @@ namespace CSVRender
|
|||
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)
|
||||
{
|
||||
if (menu)
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
#ifndef 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 "instancedragmodes.hpp"
|
||||
|
||||
namespace CSVRender
|
||||
{
|
||||
|
@ -11,8 +17,25 @@ namespace CSVRender
|
|||
|
||||
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:
|
||||
|
||||
/// Add context menu items to \a menu.
|
||||
|
@ -25,8 +48,15 @@ namespace CSVRender
|
|||
|
||||
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* mSelectSame;
|
||||
osg::Vec3d mDragStart;
|
||||
osg::Group* mParentNode;
|
||||
osg::ref_ptr<osg::PositionAttitudeTransform> mBaseNode;
|
||||
|
||||
private slots:
|
||||
|
||||
|
|
|
@ -768,6 +768,22 @@ void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMas
|
|||
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
|
||||
{
|
||||
CSMWorld::CellCoordinates cellCoordinates (
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "worldspacewidget.hpp"
|
||||
#include "cell.hpp"
|
||||
#include "instancedragmodes.hpp"
|
||||
|
||||
namespace CSVWidget
|
||||
{
|
||||
|
@ -120,6 +121,10 @@ namespace CSVRender
|
|||
/// \param elementMask Elements to be affected by the select operation
|
||||
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;
|
||||
|
||||
Cell* getCell(const osg::Vec3d& point) const override;
|
||||
|
|
|
@ -15,30 +15,27 @@ namespace CSVRender
|
|||
{
|
||||
addButton(":scenetoolbar/selection-mode-cube", "cube-centre",
|
||||
"Centred cube"
|
||||
"<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} "
|
||||
"(invert selection state) from the centre of the selection cube outwards</li>"
|
||||
"<ul><li>Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select "
|
||||
"from the centre of the selection cube outwards.</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 "
|
||||
"starting on an instance will have the same effect</li>"
|
||||
"</ul>"
|
||||
"<font color=Red>Not implemented yet</font color>");
|
||||
"</ul>");
|
||||
addButton(":scenetoolbar/selection-mode-cube-corner", "cube-corner",
|
||||
"Cube corner to corner"
|
||||
"<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} "
|
||||
"(invert selection state) from one corner of the selection cube to the opposite corner</li>"
|
||||
"<ul><li>Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select "
|
||||
"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>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>"
|
||||
"</ul>"
|
||||
"<font color=Red>Not implemented yet</font color>");
|
||||
"</ul>");
|
||||
addButton(":scenetoolbar/selection-mode-cube-sphere", "sphere",
|
||||
"Centred sphere"
|
||||
"<ul><li>Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} "
|
||||
"(invert selection state) from the centre of the selection sphere outwards</li>"
|
||||
"<ul><li>Drag with {scene-select-primary} for primary select or {scene-select-secondary} for secondary select "
|
||||
"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 "
|
||||
"starting on an instance will have the same effect</li>"
|
||||
"</ul>"
|
||||
"<font color=Red>Not implemented yet</font color>");
|
||||
"</ul>");
|
||||
|
||||
mSelectAll = new QAction("Select all", 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)
|
||||
{
|
||||
int distanceX(0);
|
||||
int distanceY(0);
|
||||
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);
|
||||
int distanceX = abs(i-xHitInCell);
|
||||
int distanceY = abs(j-yHitInCell);
|
||||
float distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2)));
|
||||
float rf = static_cast<float>(mBrushSize) / 2;
|
||||
if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt;
|
||||
|
|
|
@ -140,6 +140,16 @@ void CSVRender::UnpagedWorldspaceWidget::selectAllWithSameParentId (int elementM
|
|||
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
|
||||
{
|
||||
return mCellId;
|
||||
|
|
|
@ -60,6 +60,10 @@ namespace CSVRender
|
|||
/// \param elementMask Elements to be affected by the select operation
|
||||
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;
|
||||
|
||||
Cell* getCell(const osg::Vec3d& point) const override;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "../../model/doc/document.hpp"
|
||||
#include "../../model/world/tablemimedata.hpp"
|
||||
|
||||
#include "instancedragmodes.hpp"
|
||||
#include "scenewidget.hpp"
|
||||
#include "mask.hpp"
|
||||
|
||||
|
@ -160,6 +161,10 @@ namespace CSVRender
|
|||
/// \param elementMask Elements to be affected by the select operation
|
||||
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
|
||||
/// \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
|
||||
|
|
|
@ -15,12 +15,21 @@ bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::C
|
|||
return data != nullptr && data->holdsType(type);
|
||||
}
|
||||
|
||||
CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event,
|
||||
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::ColumnBase::Display 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;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ namespace CSVWorld
|
|||
bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display 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);
|
||||
///< 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
|
||||
|
|
|
@ -19,14 +19,10 @@ void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTa
|
|||
}
|
||||
|
||||
CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument);
|
||||
|
||||
if (mime)
|
||||
{
|
||||
QDrag* drag = new QDrag (this);
|
||||
drag->setMimeData (mime);
|
||||
drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str()));
|
||||
drag->exec (Qt::CopyAction);
|
||||
}
|
||||
QDrag* drag = new QDrag (this);
|
||||
drag->setMimeData (mime);
|
||||
drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str()));
|
||||
drag->exec (Qt::CopyAction);
|
||||
}
|
||||
|
||||
CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* parent) :
|
||||
|
@ -50,7 +46,8 @@ void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event)
|
|||
void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -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
|
||||
|
|
|
@ -23,6 +23,8 @@ namespace CSVWorld
|
|||
{
|
||||
class DragRecordTable : public QTableView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
CSMDoc::Document& mDocument;
|
||||
bool mEditLock;
|
||||
|
@ -45,6 +47,9 @@ namespace CSVWorld
|
|||
|
||||
private:
|
||||
CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const;
|
||||
|
||||
signals:
|
||||
void moveRecordsFromSameTable(QDropEvent *event);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -34,16 +34,29 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com
|
|||
{
|
||||
CSMWorld::IdTable& table = dynamic_cast<CSMWorld::IdTable&> (*getData().getTableModel (getCollectionId()));
|
||||
|
||||
CSMWorld::CloneCommand* cloneCommand = dynamic_cast<CSMWorld::CloneCommand*> (&command);
|
||||
if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos)
|
||||
{
|
||||
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_Gender), -1);
|
||||
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1);
|
||||
if (!cloneCommand)
|
||||
{
|
||||
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_Gender), -1);
|
||||
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text());
|
||||
if (!cloneCommand)
|
||||
{
|
||||
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text());
|
||||
}
|
||||
else
|
||||
cloneCommand->setOverrideValue(table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,10 +75,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
|
|||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, JournalCreatorFactory>);
|
||||
|
||||
manager.add (CSMWorld::UniversalId::Type_TopicInfos,
|
||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, InfoCreatorFactory>);
|
||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, InfoCreatorFactory>(false));
|
||||
|
||||
manager.add (CSMWorld::UniversalId::Type_JournalInfos,
|
||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, InfoCreatorFactory>);
|
||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, InfoCreatorFactory>(false));
|
||||
|
||||
manager.add (CSMWorld::UniversalId::Type_Pathgrids,
|
||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, PathgridCreatorFactory>);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <QString>
|
||||
#include <QtCore/qnamespace.h>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/helpviewer.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
|
@ -248,6 +249,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
|||
if (isInfoTable)
|
||||
{
|
||||
mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this);
|
||||
connect (this, &CSVWorld::DragRecordTable::moveRecordsFromSameTable, this, &CSVWorld::Table::moveRecords);
|
||||
}
|
||||
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()
|
||||
{
|
||||
emit editRequest(mEditIdAction->getCurrentId(), "");
|
||||
|
|
|
@ -141,6 +141,8 @@ namespace CSVWorld
|
|||
|
||||
void moveDownRecord();
|
||||
|
||||
void moveRecords(QDropEvent *event);
|
||||
|
||||
void viewRecord();
|
||||
|
||||
void previewRecord();
|
||||
|
|
|
@ -261,16 +261,10 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
|
|||
return dsb;
|
||||
}
|
||||
|
||||
/// \todo implement size limit. QPlainTextEdit does not support a size limit.
|
||||
case CSMWorld::ColumnBase::Display_LongString:
|
||||
{
|
||||
QPlainTextEdit *edit = new QPlainTextEdit(parent);
|
||||
edit->setUndoRedoEnabled (false);
|
||||
return edit;
|
||||
}
|
||||
|
||||
case CSMWorld::ColumnBase::Display_LongString256:
|
||||
{
|
||||
/// \todo implement size limit. QPlainTextEdit does not support a size limit.
|
||||
QPlainTextEdit *edit = new QPlainTextEdit(parent);
|
||||
edit->setUndoRedoEnabled (false);
|
||||
return edit;
|
||||
|
|
|
@ -19,9 +19,9 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
|
|||
|
||||
add_openmw_dir (mwrender
|
||||
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
|
||||
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging
|
||||
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover
|
||||
)
|
||||
|
||||
add_openmw_dir (mwinput
|
||||
|
@ -73,7 +73,7 @@ add_openmw_dir (mwworld
|
|||
add_openmw_dir (mwphysics
|
||||
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
|
||||
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
|
||||
closestnotmeconvexresultcallback raycasting mtphysics
|
||||
actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback
|
||||
)
|
||||
|
||||
add_openmw_dir (mwclass
|
||||
|
|
|
@ -177,6 +177,8 @@ namespace
|
|||
|
||||
~ScopedProfile()
|
||||
{
|
||||
if (!mStats.collectStats("engine"))
|
||||
return;
|
||||
const osg::Timer_t end = mTimer.tick();
|
||||
const UserStats& stats = UserStatsValue<sType>::sValue;
|
||||
|
||||
|
@ -460,6 +462,11 @@ void OMW::Engine::addContentFile(const std::string& 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)
|
||||
{
|
||||
mSkipMenu = skipMenu;
|
||||
|
@ -589,8 +596,8 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
|
|||
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel.";
|
||||
if (traits->blue < 8)
|
||||
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel.";
|
||||
if (traits->depth < 8)
|
||||
Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->red << " bits of depth precision.";
|
||||
if (traits->depth < 24)
|
||||
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
|
||||
}
|
||||
|
@ -721,7 +728,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
|
||||
// Create the world
|
||||
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
|
||||
mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName,
|
||||
mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName,
|
||||
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
||||
mEnvironment.getWorld()->setupPlayer();
|
||||
|
||||
|
@ -749,6 +756,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
// Create dialog system
|
||||
mEnvironment.setJournal (new MWDialogue::Journal);
|
||||
mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage));
|
||||
mEnvironment.setResourceSystem(mResourceSystem.get());
|
||||
|
||||
// scripts
|
||||
if (mCompileAll)
|
||||
|
@ -862,16 +870,29 @@ void OMW::Engine::go()
|
|||
|
||||
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
|
||||
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler;
|
||||
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler(stats.is_open());
|
||||
|
||||
initStatsHandler(*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);
|
||||
|
||||
if (stats.is_open())
|
||||
Resource::CollectStatistics(mViewer);
|
||||
|
||||
// Start the game
|
||||
if (!mSaveGameFile.empty())
|
||||
{
|
||||
|
@ -896,14 +917,6 @@ void OMW::Engine::go()
|
|||
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
|
||||
osg::Timer frameTimer;
|
||||
double simulationTime = 0.0;
|
||||
|
|
|
@ -85,6 +85,7 @@ namespace OMW
|
|||
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
|
||||
std::string mCellName;
|
||||
std::vector<std::string> mContentFiles;
|
||||
std::vector<std::string> mGroundcoverFiles;
|
||||
bool mSkipMenu;
|
||||
bool mUseSound;
|
||||
bool mCompileAll;
|
||||
|
@ -155,6 +156,7 @@ namespace OMW
|
|||
* @param file - filename (extension is required)
|
||||
*/
|
||||
void addContentFile(const std::string& file);
|
||||
void addGroundcoverFile(const std::string& file);
|
||||
|
||||
/// Disable or enable all sounds
|
||||
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(), "")
|
||||
->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)
|
||||
->default_value(false), "disable all sounds")
|
||||
|
||||
|
@ -190,11 +193,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
return false;
|
||||
}
|
||||
|
||||
StringsVector::const_iterator it(content.begin());
|
||||
StringsVector::const_iterator end(content.end());
|
||||
for (; it != end; ++it)
|
||||
for (auto& file : content)
|
||||
{
|
||||
engine.addContentFile(*it);
|
||||
engine.addContentFile(file);
|
||||
}
|
||||
|
||||
StringsVector groundcover = variables["groundcover"].as<Files::EscapeStringVector>().toStdStringVector();
|
||||
for (auto& file : groundcover)
|
||||
{
|
||||
engine.addGroundcoverFile(file);
|
||||
}
|
||||
|
||||
// startup-settings
|
||||
|
@ -254,7 +261,19 @@ namespace
|
|||
level = Debug::Debug;
|
||||
}
|
||||
std::string_view s(msgCopy);
|
||||
Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s);
|
||||
if (s.size() < 1024)
|
||||
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 <thread>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
|
||||
#include "world.hpp"
|
||||
#include "scriptmanager.hpp"
|
||||
#include "dialoguemanager.hpp"
|
||||
|
@ -18,8 +20,8 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr;
|
|||
|
||||
MWBase::Environment::Environment()
|
||||
: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr),
|
||||
mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr), mStateManager (nullptr),
|
||||
mFrameDuration (0), mFrameRateLimit(0.f)
|
||||
mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr),
|
||||
mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f)
|
||||
{
|
||||
assert (!sThis);
|
||||
sThis = this;
|
||||
|
@ -76,6 +78,11 @@ void MWBase::Environment::setStateManager (StateManager *stateManager)
|
|||
mStateManager = stateManager;
|
||||
}
|
||||
|
||||
void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem)
|
||||
{
|
||||
mResourceSystem = resourceSystem;
|
||||
}
|
||||
|
||||
void MWBase::Environment::setFrameDuration (float duration)
|
||||
{
|
||||
mFrameDuration = duration;
|
||||
|
@ -158,6 +165,11 @@ MWBase::StateManager *MWBase::Environment::getStateManager() const
|
|||
return mStateManager;
|
||||
}
|
||||
|
||||
Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const
|
||||
{
|
||||
return mResourceSystem;
|
||||
}
|
||||
|
||||
float MWBase::Environment::getFrameDuration() const
|
||||
{
|
||||
return mFrameDuration;
|
||||
|
|
|
@ -6,6 +6,11 @@ namespace osg
|
|||
class Stats;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class ResourceSystem;
|
||||
}
|
||||
|
||||
namespace MWBase
|
||||
{
|
||||
class World;
|
||||
|
@ -37,6 +42,7 @@ namespace MWBase
|
|||
Journal *mJournal;
|
||||
InputManager *mInputManager;
|
||||
StateManager *mStateManager;
|
||||
Resource::ResourceSystem *mResourceSystem;
|
||||
float mFrameDuration;
|
||||
float mFrameRateLimit;
|
||||
|
||||
|
@ -70,6 +76,8 @@ namespace MWBase
|
|||
|
||||
void setStateManager (StateManager *stateManager);
|
||||
|
||||
void setResourceSystem (Resource::ResourceSystem *resourceSystem);
|
||||
|
||||
void setFrameDuration (float duration);
|
||||
///< Set length of current frame in seconds.
|
||||
|
||||
|
@ -95,6 +103,8 @@ namespace MWBase
|
|||
|
||||
StateManager *getStateManager() const;
|
||||
|
||||
Resource::ResourceSystem *getResourceSystem() const;
|
||||
|
||||
float getFrameDuration() const;
|
||||
|
||||
void cleanup();
|
||||
|
|
|
@ -185,6 +185,7 @@ namespace MWBase
|
|||
///
|
||||
/// \note If cell==0, the cell the player is currently in will be used instead to
|
||||
/// generate a name.
|
||||
virtual std::string getCellName(const ESM::Cell* cell) const = 0;
|
||||
|
||||
virtual void removeRefScript (MWWorld::RefData *ref) = 0;
|
||||
//< Remove the script attached to ref from mLocalScripts
|
||||
|
@ -497,7 +498,7 @@ namespace MWBase
|
|||
|
||||
/// \todo this does not belong here
|
||||
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
|
||||
/// \return false if exterior with given name not exists, true otherwise
|
||||
|
|
|
@ -302,30 +302,15 @@ namespace MWClass
|
|||
|
||||
std::string Door::getDestination (const MWWorld::LiveCellRef<ESM::Door>& door)
|
||||
{
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
std::string dest;
|
||||
if (door.mRef.getDestCell() != "")
|
||||
{
|
||||
// door leads to an interior, use interior name as tooltip
|
||||
dest = door.mRef.getDestCell();
|
||||
}
|
||||
else
|
||||
std::string dest = door.mRef.getDestCell();
|
||||
if (dest.empty())
|
||||
{
|
||||
// door leads to exterior, use cell name (if any), otherwise translated region name
|
||||
int x,y;
|
||||
MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y);
|
||||
const ESM::Cell* cell = store.get<ESM::Cell>().find(x,y);
|
||||
if (cell->mName != "")
|
||||
dest = cell->mName;
|
||||
else
|
||||
{
|
||||
const ESM::Region* region =
|
||||
store.get<ESM::Region>().find(cell->mRegion);
|
||||
|
||||
//name as is, not a token
|
||||
return MyGUI::TextIterator::toTagsString(region->mName);
|
||||
}
|
||||
auto world = MWBase::Environment::get().getWorld();
|
||||
world->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y);
|
||||
const ESM::Cell* cell = world->getStore().get<ESM::Cell>().search(x,y);
|
||||
dest = world->getCellName(cell);
|
||||
}
|
||||
|
||||
return "#{sCell=" + dest + "}";
|
||||
|
|
|
@ -418,10 +418,10 @@ namespace MWClass
|
|||
{
|
||||
const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
|
||||
|
||||
std::string model = "meshes\\base_anim.nif";
|
||||
std::string model = Settings::Manager::getString("baseanim", "Models");
|
||||
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
|
||||
if(race->mData.mFlags & ESM::Race::Beast)
|
||||
model = "meshes\\base_animkna.nif";
|
||||
model = Settings::Manager::getString("baseanimkna", "Models");
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -431,12 +431,12 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::NPC> *npc = ptr.get<ESM::NPC>();
|
||||
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().search(npc->mBase->mRace);
|
||||
if(race && race->mData.mFlags & ESM::Race::Beast)
|
||||
models.emplace_back("meshes\\base_animkna.nif");
|
||||
models.emplace_back(Settings::Manager::getString("baseanimkna", "Models"));
|
||||
|
||||
// keep these always loaded just in case
|
||||
models.emplace_back("meshes/xargonian_swimkna.nif");
|
||||
models.emplace_back("meshes/xbase_anim_female.nif");
|
||||
models.emplace_back("meshes/xbase_anim.nif");
|
||||
models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("xbaseanim", "Models"));
|
||||
|
||||
if (!npc->mBase->mModel.empty())
|
||||
models.push_back("meshes/"+npc->mBase->mModel);
|
||||
|
|
|
@ -316,7 +316,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con
|
|||
case SelectWrapper::Function_AiSetting:
|
||||
|
||||
return mActor.getClass().getCreatureStats (mActor).getAiSetting (
|
||||
(MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified();
|
||||
(MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(false);
|
||||
|
||||
case SelectWrapper::Function_PcAttribute:
|
||||
|
||||
|
|
|
@ -30,14 +30,18 @@ namespace MWGui
|
|||
|
||||
// vanilla game does not show any text after the last EOL tag.
|
||||
const std::string lowerText = Misc::StringUtils::lowerCase(mText);
|
||||
int brIndex = lowerText.rfind("<br>");
|
||||
int pIndex = lowerText.rfind("<p>");
|
||||
if (brIndex == pIndex)
|
||||
mText = "";
|
||||
else if (brIndex > pIndex)
|
||||
mText = mText.substr(0, brIndex+4);
|
||||
else
|
||||
mText = mText.substr(0, pIndex+3);
|
||||
size_t brIndex = lowerText.rfind("<br>");
|
||||
size_t pIndex = lowerText.rfind("<p>");
|
||||
mPlainTextEnd = 0;
|
||||
if (brIndex != pIndex)
|
||||
{
|
||||
if (brIndex != std::string::npos && pIndex != std::string::npos)
|
||||
mPlainTextEnd = std::max(brIndex, pIndex);
|
||||
else if (brIndex != std::string::npos)
|
||||
mPlainTextEnd = brIndex;
|
||||
else
|
||||
mPlainTextEnd = pIndex;
|
||||
}
|
||||
|
||||
registerTag("br", Event_BrTag);
|
||||
registerTag("p", Event_PTag);
|
||||
|
@ -103,7 +107,8 @@ namespace MWGui
|
|||
{
|
||||
if (!mIgnoreLineEndings || ch != '\n')
|
||||
{
|
||||
mBuffer.push_back(ch);
|
||||
if (mIndex < mPlainTextEnd)
|
||||
mBuffer.push_back(ch);
|
||||
mIgnoreLineEndings = false;
|
||||
mIgnoreNewlineTags = false;
|
||||
}
|
||||
|
|
|
@ -73,6 +73,8 @@ namespace MWGui
|
|||
bool mClosingTag;
|
||||
std::map<std::string, Events> mTagTypes;
|
||||
std::string mBuffer;
|
||||
|
||||
size_t mPlainTextEnd;
|
||||
};
|
||||
|
||||
class Paginator
|
||||
|
|
|
@ -587,7 +587,7 @@ namespace MWGui
|
|||
// effect box can have variable width -> variable left coordinate
|
||||
int effectsDx = 0;
|
||||
if (!mMinimapBox->getVisible ())
|
||||
effectsDx = (viewSize.width - mMinimapBoxBaseRight) - (viewSize.width - mEffectBoxBaseRight);
|
||||
effectsDx = mEffectBoxBaseRight - mMinimapBoxBaseRight;
|
||||
|
||||
mMapVisible = mMinimapBox->getVisible ();
|
||||
if (!mMapVisible)
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
#include <MyGUI_TextBox.h>
|
||||
|
||||
// correctIconPath
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
|
@ -106,7 +109,10 @@ namespace MWGui
|
|||
std::string invIcon = ptr.getClass().getInventoryIcon(ptr);
|
||||
if (invIcon.empty())
|
||||
invIcon = "default icon.tga";
|
||||
setIcon(MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon));
|
||||
invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon);
|
||||
if (!MWBase::Environment::get().getResourceSystem()->getVFS()->exists(invIcon))
|
||||
invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath("default icon.tga");
|
||||
setIcon(invIcon);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -561,7 +561,7 @@ namespace
|
|||
if (mAllQuests)
|
||||
{
|
||||
SetNamesInactive setInactive(list);
|
||||
mModel->visitQuestNames(!mAllQuests, setInactive);
|
||||
mModel->visitQuestNames(false, setInactive);
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->playSound("book page");
|
||||
|
|
|
@ -28,10 +28,7 @@ namespace MWGui
|
|||
|
||||
MessageBoxManager::~MessageBoxManager ()
|
||||
{
|
||||
for (MessageBox* messageBox : mMessageBoxes)
|
||||
{
|
||||
delete messageBox;
|
||||
}
|
||||
MessageBoxManager::clear();
|
||||
}
|
||||
|
||||
int MessageBoxManager::getMessagesCount()
|
||||
|
|
|
@ -269,7 +269,7 @@ namespace MWGui
|
|||
mWaterTextureSize->setIndexSelected(2);
|
||||
|
||||
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);
|
||||
|
||||
mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video"));
|
||||
|
@ -353,7 +353,7 @@ namespace MWGui
|
|||
|
||||
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);
|
||||
apply();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <MyGUI_ProgressBar.h>
|
||||
#include <MyGUI_ImageBox.h>
|
||||
#include <MyGUI_InputManager.h>
|
||||
#include <MyGUI_LanguageManager.h>
|
||||
#include <MyGUI_Gui.h>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
@ -336,19 +337,22 @@ namespace MWGui
|
|||
int max = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iLevelUpTotal")->mValue.getInteger();
|
||||
getWidget(levelWidget, i==0 ? "Level_str" : "LevelText");
|
||||
|
||||
std::string detail;
|
||||
for (int i = 0; i < ESM::Attribute::Length; ++i)
|
||||
{
|
||||
if (auto increase = PCstats.getLevelUpAttributeIncrease(i))
|
||||
detail += (detail.empty() ? "" : "\n") + ESM::Attribute::sAttributeNames[i] + " x" + MyGUI::utility::toString(increase);
|
||||
}
|
||||
if (!detail.empty())
|
||||
levelWidget->setUserString("Caption_LevelDetailText", detail);
|
||||
levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress()));
|
||||
levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max));
|
||||
levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/"
|
||||
+ 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());
|
||||
setExpelled(PCstats.getExpelled ());
|
||||
|
|
|
@ -333,12 +333,8 @@ namespace MWInput
|
|||
|
||||
void ActionManager::screenshot()
|
||||
{
|
||||
bool regularScreenshot = true;
|
||||
|
||||
std::string settingStr;
|
||||
|
||||
settingStr = Settings::Manager::getString("screenshot type","Video");
|
||||
regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0;
|
||||
const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video");
|
||||
bool regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0;
|
||||
|
||||
if (regularScreenshot)
|
||||
{
|
||||
|
@ -349,7 +345,7 @@ namespace MWInput
|
|||
{
|
||||
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);
|
||||
// FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason
|
||||
|
|
|
@ -171,16 +171,16 @@ namespace MWInput
|
|||
, mDragDrop(false)
|
||||
{
|
||||
std::string file = userFileExists ? userFile : "";
|
||||
mInputBinder = new InputControlSystem(file);
|
||||
mListener = new BindingsListener(mInputBinder, this);
|
||||
mInputBinder->setDetectingBindingListener(mListener);
|
||||
mInputBinder = std::make_unique<InputControlSystem>(file);
|
||||
mListener = std::make_unique<BindingsListener>(mInputBinder.get(), this);
|
||||
mInputBinder->setDetectingBindingListener(mListener.get());
|
||||
|
||||
loadKeyDefaults();
|
||||
loadControllerDefaults();
|
||||
|
||||
for (int i = 0; i < A_Last; ++i)
|
||||
{
|
||||
mInputBinder->getChannel(i)->addListener(mListener);
|
||||
mInputBinder->getChannel(i)->addListener(mListener.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,7 +192,6 @@ namespace MWInput
|
|||
BindingsManager::~BindingsManager()
|
||||
{
|
||||
mInputBinder->save(mUserFile);
|
||||
delete mInputBinder;
|
||||
}
|
||||
|
||||
void BindingsManager::update(float dt)
|
||||
|
@ -315,7 +314,7 @@ namespace MWInput
|
|||
&& mInputBinder->getMouseButtonBinding(control, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS
|
||||
&& mInputBinder->getMouseWheelBinding(control, ICS::Control::INCREASE) == ICS::InputControlSystem::MouseWheelClick::UNASSIGNED))
|
||||
{
|
||||
clearAllKeyBindings(mInputBinder, control);
|
||||
clearAllKeyBindings(mInputBinder.get(), control);
|
||||
|
||||
if (defaultKeyBindings.find(i) != defaultKeyBindings.end()
|
||||
&& (force || !mInputBinder->isKeyBound(defaultKeyBindings[i])))
|
||||
|
@ -402,7 +401,7 @@ namespace MWInput
|
|||
if (!controlExists || force || (mInputBinder->getJoystickAxisBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS::InputControlSystem::UNASSIGNED &&
|
||||
mInputBinder->getJoystickButtonBinding(control, sFakeDeviceId, ICS::Control::INCREASE) == ICS_MAX_DEVICE_BUTTONS))
|
||||
{
|
||||
clearAllControllerBindings(mInputBinder, control);
|
||||
clearAllControllerBindings(mInputBinder.get(), control);
|
||||
|
||||
if (defaultButtonBindings.find(i) != defaultButtonBindings.end()
|
||||
&& (force || !mInputBinder->isJoystickButtonBound(sFakeDeviceId, defaultButtonBindings[i])))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef MWINPUT_MWBINDINGSMANAGER_H
|
||||
#define MWINPUT_MWBINDINGSMANAGER_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -64,8 +65,8 @@ namespace MWInput
|
|||
private:
|
||||
void setupSDLKeyMappings();
|
||||
|
||||
InputControlSystem* mInputBinder;
|
||||
BindingsListener* mListener;
|
||||
std::unique_ptr<InputControlSystem> mInputBinder;
|
||||
std::unique_ptr<BindingsListener> mListener;
|
||||
|
||||
std::string mUserFile;
|
||||
|
||||
|
|
|
@ -951,29 +951,29 @@ namespace MWMechanics
|
|||
if (actor.getClass().hasInventoryStore(actor))
|
||||
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Poison);
|
||||
}
|
||||
else if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0)
|
||||
if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0)
|
||||
{
|
||||
creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||
creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||
if (actor.getClass().hasInventoryStore(actor))
|
||||
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Paralyze);
|
||||
}
|
||||
else if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0)
|
||||
if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0)
|
||||
{
|
||||
creatureStats.getSpells().purgeCommonDisease();
|
||||
}
|
||||
else if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0)
|
||||
if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0)
|
||||
{
|
||||
creatureStats.getSpells().purgeBlightDisease();
|
||||
}
|
||||
else if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0)
|
||||
if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0)
|
||||
{
|
||||
creatureStats.getActiveSpells().purgeCorprusDisease();
|
||||
creatureStats.getSpells().purgeCorprusDisease();
|
||||
if (actor.getClass().hasInventoryStore(actor))
|
||||
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Corprus, true);
|
||||
}
|
||||
else if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0)
|
||||
if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0)
|
||||
{
|
||||
creatureStats.getSpells().purgeCurses();
|
||||
}
|
||||
|
@ -1430,6 +1430,13 @@ namespace MWMechanics
|
|||
if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name())
|
||||
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);
|
||||
|
||||
|
|
|
@ -14,6 +14,16 @@
|
|||
#include "movement.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
|
||||
{
|
||||
int AiFollow::mFollowIndexCounter = 0;
|
||||
|
@ -119,16 +129,16 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target);
|
||||
if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex)
|
||||
{
|
||||
osg::Vec3f::value_type maxSize = 0;
|
||||
for(auto& follower : followers)
|
||||
{
|
||||
auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y();
|
||||
auto halfExtent = getHalfExtents(follower.second);
|
||||
if(halfExtent > floatingDistance)
|
||||
floatingDistance = halfExtent;
|
||||
}
|
||||
floatingDistance += 128;
|
||||
}
|
||||
floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y();
|
||||
floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2;
|
||||
floatingDistance += getHalfExtents(target) + 64;
|
||||
floatingDistance += getHalfExtents(actor) * 2;
|
||||
short followDistance = static_cast<short>(floatingDistance);
|
||||
|
||||
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 bool isDestReached = (distToTarget <= destTolerance);
|
||||
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
||||
|
||||
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;
|
||||
bool destInLOS = false;
|
||||
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
||||
|
||||
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
|
||||
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());
|
||||
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
|
||||
{
|
||||
|
@ -158,8 +160,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
|||
zTurn(actor, getZAngleToPoint(position, dest));
|
||||
smoothTurn(actor, getXAngleToPoint(position, dest), 0);
|
||||
world->removeActorPath(actor);
|
||||
return isDestReached;
|
||||
return true;
|
||||
}
|
||||
else if (mPathFinder.getPath().empty())
|
||||
return false;
|
||||
|
||||
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();
|
||||
mObstacleCheck.update(actor, destination, duration);
|
||||
|
||||
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
|
||||
if (smoothMovement)
|
||||
{
|
||||
const float smoothTurnReservedDist = 150;
|
||||
|
|
|
@ -99,7 +99,6 @@ namespace MWMechanics
|
|||
{
|
||||
AiPackage::Options options;
|
||||
options.mUseVariableSpeed = true;
|
||||
options.mRepeat = false;
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
|
@ -446,9 +446,9 @@ std::string CharacterController::fallbackShortWeaponGroup(const std::string& bas
|
|||
const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType);
|
||||
|
||||
// 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;
|
||||
else if (isRealWeapon)
|
||||
else
|
||||
groupName += oneHandFallback;
|
||||
|
||||
// Special case for crossbows - we shouls apply 1h animations a fallback only for lower body
|
||||
|
@ -883,7 +883,11 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
|||
}
|
||||
|
||||
if(!cls.getCreatureStats(mPtr).isDead())
|
||||
{
|
||||
mIdleState = CharState_Idle;
|
||||
if (cls.getCreatureStats(mPtr).getFallHeight() > 0)
|
||||
mJumpState = JumpState_InAir;
|
||||
}
|
||||
else
|
||||
{
|
||||
const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
|
||||
|
@ -1898,7 +1902,7 @@ void CharacterController::updateAnimQueue()
|
|||
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();
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
|
@ -2386,10 +2390,10 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true);
|
||||
}
|
||||
|
||||
if (!animationOnly && !mMovementAnimationControlled)
|
||||
if (!mMovementAnimationControlled)
|
||||
world->queueMovement(mPtr, vec);
|
||||
}
|
||||
else if (!animationOnly)
|
||||
else
|
||||
// We must always queue movement, even if there is none, to apply gravity.
|
||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||
|
||||
|
@ -2414,8 +2418,7 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
playDeath(1.f, mDeathState);
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
|
||||
bool isPersist = isPersistentAnimPlaying();
|
||||
|
@ -2429,7 +2432,7 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
moved.y() *= scale;
|
||||
|
||||
// Ensure we're moving in generally the right direction...
|
||||
if(speed > 0.f)
|
||||
if (speed > 0.f && moved != osg::Vec3f())
|
||||
{
|
||||
float l = moved.length();
|
||||
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 ||
|
||||
|
@ -2445,17 +2448,17 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
}
|
||||
}
|
||||
|
||||
if (mFloatToSurface && cls.isActor() && cls.canSwim(mPtr))
|
||||
if (mFloatToSurface && cls.isActor())
|
||||
{
|
||||
if (cls.getCreatureStats(mPtr).isDead()
|
||||
|| (!godmode && cls.getCreatureStats(mPtr).isParalyzed()))
|
||||
{
|
||||
moved.z() = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update movement
|
||||
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())
|
||||
if(mMovementAnimationControlled && mPtr.getClass().isActor())
|
||||
world->queueMovement(mPtr, moved);
|
||||
|
||||
mSkipAnim = false;
|
||||
|
|
|
@ -248,7 +248,7 @@ public:
|
|||
|
||||
void updatePtr(const MWWorld::Ptr &ptr);
|
||||
|
||||
void update(float duration, bool animationOnly=false);
|
||||
void update(float duration);
|
||||
|
||||
bool onOpen();
|
||||
void onClose();
|
||||
|
|
|
@ -345,6 +345,8 @@ namespace MWMechanics
|
|||
MWMechanics::DynamicStat<float> health = attackerStats.getHealth();
|
||||
health.setCurrent(health.getCurrent() - x);
|
||||
attackerStats.setHealth(health);
|
||||
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(attacker, "Health Damage", 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -322,11 +322,6 @@ void MWMechanics::NpcStats::updateHealth()
|
|||
setHealth(floor(0.5f * (strength + endurance)));
|
||||
}
|
||||
|
||||
int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const
|
||||
{
|
||||
return mSkillIncreases[attribute];
|
||||
}
|
||||
|
||||
int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
|
||||
{
|
||||
int num = mSkillIncreases[attribute];
|
||||
|
|
|
@ -87,8 +87,6 @@ namespace MWMechanics
|
|||
|
||||
int getLevelProgress() const;
|
||||
|
||||
int getLevelUpAttributeIncrease(int attribute) const;
|
||||
|
||||
int getLevelupAttributeMultiplier(int attribute) const;
|
||||
|
||||
int getSkillIncreasesForSpecialization(int spec) const;
|
||||
|
|
|
@ -97,12 +97,12 @@ namespace
|
|||
float dotProduct = v1.x() * v3.x() + v1.y() * v3.y();
|
||||
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.
|
||||
static const float cos170 = std::cos(osg::PI / 180 * 170);
|
||||
bool checkAngle = dotProduct <= cos170 * v1.length() * v3.length();
|
||||
// Check that the angle between v1 and v3 is less or equal than 5 degrees.
|
||||
static const float cos175 = std::cos(osg::PI * (175.0 / 180));
|
||||
bool checkAngle = dotProduct <= cos175 * v1.length() * v3.length();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
@ -296,7 +296,8 @@ namespace MWMechanics
|
|||
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())
|
||||
return;
|
||||
|
@ -304,13 +305,24 @@ namespace MWMechanics
|
|||
while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance)
|
||||
mPath.pop_front();
|
||||
|
||||
while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance))
|
||||
mPath.erase(mPath.begin() + 1);
|
||||
if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance))
|
||||
mPath.pop_front();
|
||||
if (shortenIfAlmostStraight)
|
||||
{
|
||||
while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance))
|
||||
mPath.erase(mPath.begin() + 1);
|
||||
if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance))
|
||||
mPath.pop_front();
|
||||
}
|
||||
|
||||
if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance)
|
||||
mPath.pop_front();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void PathFinder::buildStraightPath(const osg::Vec3f& endPoint)
|
||||
|
@ -328,7 +340,7 @@ namespace MWMechanics
|
|||
|
||||
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
||||
|
||||
mConstructed = true;
|
||||
mConstructed = !mPath.empty();
|
||||
}
|
||||
|
||||
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)))
|
||||
mPath.push_back(endPoint);
|
||||
|
||||
mConstructed = true;
|
||||
mConstructed = !mPath.empty();
|
||||
}
|
||||
|
||||
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())
|
||||
mPath.push_back(endPoint);
|
||||
|
||||
mConstructed = true;
|
||||
mConstructed = !mPath.empty();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/// 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
|
||||
{
|
||||
|
|
|
@ -60,7 +60,10 @@ namespace MWMechanics
|
|||
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
||||
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.
|
||||
const auto& stats = target.getClass().getCreatureStats(target);
|
||||
|
@ -82,7 +85,7 @@ namespace MWMechanics
|
|||
return;
|
||||
|
||||
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) ?
|
||||
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.
|
||||
// Otherwise, they'd only apply after the whole spell was added.
|
||||
MagicEffects targetEffects;
|
||||
if (!target.isEmpty() && target.getClass().isActor())
|
||||
if (target.getClass().isActor())
|
||||
targetEffects += target.getClass().getCreatureStats(target).getMagicEffects();
|
||||
|
||||
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
|
||||
|
||||
ActiveSpells targetSpells;
|
||||
if (!target.isEmpty() && target.getClass().isActor())
|
||||
if (target.getClass().isActor())
|
||||
targetSpells = target.getClass().getCreatureStats(target).getActiveSpells();
|
||||
|
||||
bool canCastAnEffect = false; // For bound equipment.If this remains false
|
||||
|
@ -123,7 +126,7 @@ namespace MWMechanics
|
|||
|
||||
int currentEffectIndex = 0;
|
||||
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)
|
||||
continue;
|
||||
|
@ -267,7 +270,7 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
// 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);
|
||||
ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex);
|
||||
|
@ -310,18 +313,16 @@ namespace MWMechanics
|
|||
if (!exploded)
|
||||
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile);
|
||||
|
||||
if (!target.isEmpty()) {
|
||||
if (!reflectedEffects.mList.empty())
|
||||
inflict(caster, target, reflectedEffects, range, true, exploded);
|
||||
if (!reflectedEffects.mList.empty())
|
||||
inflict(caster, target, reflectedEffects, range, true, exploded);
|
||||
|
||||
if (!appliedLastingEffects.empty())
|
||||
{
|
||||
int casterActorId = -1;
|
||||
if (!caster.isEmpty() && caster.getClass().isActor())
|
||||
casterActorId = caster.getClass().getCreatureStats(caster).getActorId();
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
|
||||
mSourceName, casterActorId);
|
||||
}
|
||||
if (!appliedLastingEffects.empty())
|
||||
{
|
||||
int casterActorId = -1;
|
||||
if (!caster.isEmpty() && caster.getClass().isActor())
|
||||
casterActorId = caster.getClass().getCreatureStats(caster).getActorId();
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
|
||||
mSourceName, casterActorId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace MWMechanics
|
|||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
return 100;
|
||||
|
||||
if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost)
|
||||
if (checkMagicka && spell->mData.mCost > 0 && stats.getMagicka().getCurrent() < spell->mData.mCost)
|
||||
return 0;
|
||||
|
||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||
|
|
|
@ -18,8 +18,10 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
T Stat<T>::getModified() const
|
||||
T Stat<T>::getModified(bool capped) const
|
||||
{
|
||||
if(!capped)
|
||||
return mModified;
|
||||
return std::max(static_cast<T>(0), mModified);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace MWMechanics
|
|||
|
||||
const T& getBase() const;
|
||||
|
||||
T getModified() const;
|
||||
T getModified(bool capped = true) const;
|
||||
T getCurrentModified() const;
|
||||
T getModifier() const;
|
||||
T getCurrentModifier() const;
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
static const ESM::WeaponType *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])];
|
||||
|
||||
MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype)
|
||||
{
|
||||
MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "actor.hpp"
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btCapsuleShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btBoxShape.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
|
@ -14,6 +13,8 @@
|
|||
#include "collisiontype.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
|
||||
|
@ -52,28 +53,22 @@ 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() << "\".";
|
||||
}
|
||||
|
||||
// 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)));
|
||||
mRotationallyInvariant = false;
|
||||
}
|
||||
mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents)));
|
||||
mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2;
|
||||
|
||||
mConvexShape = static_cast<btConvexShape*>(mShape.get());
|
||||
|
||||
mCollisionObject.reset(new btCollisionObject);
|
||||
mCollisionObject = std::make_unique<btCollisionObject>();
|
||||
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
|
||||
mCollisionObject->setCollisionShape(mShape.get());
|
||||
mCollisionObject->setUserPointer(this);
|
||||
|
||||
updateRotation();
|
||||
updateScale();
|
||||
|
||||
if(!mRotationallyInvariant)
|
||||
updateRotation();
|
||||
|
||||
updatePosition();
|
||||
addCollisionMask(getCollisionMask());
|
||||
updateCollisionObjectPosition();
|
||||
|
@ -81,8 +76,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
|
|||
|
||||
Actor::~Actor()
|
||||
{
|
||||
if (mCollisionObject)
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
}
|
||||
|
||||
void Actor::enableCollisionMode(bool collision)
|
||||
|
@ -154,6 +148,11 @@ osg::Vec3f Actor::getSimulationPosition() const
|
|||
return mSimulationPosition;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getScaledMeshTranslation() const
|
||||
{
|
||||
return mRotation * osg::componentMultiply(mMeshTranslation, mScale);
|
||||
}
|
||||
|
||||
void Actor::updateCollisionObjectPosition()
|
||||
{
|
||||
std::scoped_lock lock(mPositionMutex);
|
||||
|
|
|
@ -82,6 +82,9 @@ namespace MWPhysics
|
|||
*/
|
||||
osg::Vec3f getOriginalHalfExtents() const;
|
||||
|
||||
/// Returns the mesh translation, scaled and rotated as necessary
|
||||
osg::Vec3f getScaledMeshTranslation() const;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
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
|
||||
#define OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H
|
||||
#ifndef OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H
|
||||
#define OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H
|
||||
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
|
@ -7,10 +7,10 @@ class btCollisionObject;
|
|||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
|
||||
class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback
|
||||
{
|
||||
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;
|
||||
|
||||
|
@ -18,6 +18,7 @@ namespace MWPhysics
|
|||
const btCollisionObject *mMe;
|
||||
const btVector3 mMotion;
|
||||
const btScalar mMinCollisionDot;
|
||||
const btCollisionWorld * mWorld;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
#include "closestnotmeconvexresultcallback.hpp"
|
||||
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||
|
||||
#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 "actor.hpp"
|
||||
#include "collisiontype.hpp"
|
||||
#include "projectile.hpp"
|
||||
#include "ptrholder.hpp"
|
||||
|
||||
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)
|
||||
, 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)
|
||||
return 1.f;
|
||||
|
||||
if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject())
|
||||
return 1.f;
|
||||
|
||||
if (!mTargets.empty())
|
||||
{
|
||||
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
|
||||
|
@ -38,27 +32,6 @@ namespace MWPhysics
|
|||
}
|
||||
}
|
||||
|
||||
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
||||
if (mProjectile)
|
||||
{
|
||||
switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup)
|
||||
{
|
||||
case CollisionType_Actor:
|
||||
{
|
||||
auto* target = static_cast<Actor*>(rayResult.m_collisionObject->getUserPointer());
|
||||
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||
break;
|
||||
}
|
||||
case CollisionType_Projectile:
|
||||
{
|
||||
auto* target = static_cast<Projectile*>(rayResult.m_collisionObject->getUserPointer());
|
||||
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rayResult.m_hitFraction;
|
||||
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,13 @@ namespace MWPhysics
|
|||
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
|
||||
{
|
||||
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;
|
||||
|
||||
private:
|
||||
const btCollisionObject* mMe;
|
||||
const std::vector<const btCollisionObject*> mTargets;
|
||||
Projectile* mProjectile;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,22 @@ namespace MWPhysics
|
|||
{
|
||||
static const float sStepSizeUp = 34.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 sMaxSlope = 49.0f;
|
||||
|
||||
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
|
||||
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
|
||||
|
|
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 "mtphysics.hpp"
|
||||
|
||||
#include <osg/Object>
|
||||
|
||||
|
@ -42,10 +43,12 @@ namespace
|
|||
|
||||
namespace MWPhysics
|
||||
{
|
||||
HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
|
||||
: mHeights(makeHeights(heights, sqrtVerts))
|
||||
HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler)
|
||||
: mHoldObject(holdObject)
|
||||
, mHeights(makeHeights(heights, sqrtVerts))
|
||||
, mTaskScheduler(scheduler)
|
||||
{
|
||||
mShape = new btHeightfieldTerrainShape(
|
||||
mShape = std::make_unique<btHeightfieldTerrainShape>(
|
||||
sqrtVerts, sqrtVerts,
|
||||
getHeights(heights, mHeights),
|
||||
1,
|
||||
|
@ -60,31 +63,29 @@ namespace MWPhysics
|
|||
(y+0.5f) * triSize * (sqrtVerts-1),
|
||||
(maxH+minH)*0.5f));
|
||||
|
||||
mCollisionObject = new btCollisionObject;
|
||||
mCollisionObject->setCollisionShape(mShape);
|
||||
mCollisionObject = std::make_unique<btCollisionObject>();
|
||||
mCollisionObject->setCollisionShape(mShape.get());
|
||||
mCollisionObject->setWorldTransform(transform);
|
||||
|
||||
mHoldObject = holdObject;
|
||||
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_HeightMap, CollisionType_Actor|CollisionType_Projectile);
|
||||
}
|
||||
|
||||
HeightField::~HeightField()
|
||||
{
|
||||
delete mCollisionObject;
|
||||
delete mShape;
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
}
|
||||
|
||||
btCollisionObject* HeightField::getCollisionObject()
|
||||
{
|
||||
return mCollisionObject;
|
||||
return mCollisionObject.get();
|
||||
}
|
||||
|
||||
const btCollisionObject* HeightField::getCollisionObject() const
|
||||
{
|
||||
return mCollisionObject;
|
||||
return mCollisionObject.get();
|
||||
}
|
||||
|
||||
const btHeightfieldTerrainShape* HeightField::getShape() const
|
||||
{
|
||||
return mShape;
|
||||
return mShape.get();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <LinearMath/btScalar.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class btCollisionObject;
|
||||
|
@ -17,10 +18,12 @@ namespace osg
|
|||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class PhysicsTaskScheduler;
|
||||
|
||||
class HeightField
|
||||
{
|
||||
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();
|
||||
|
||||
btCollisionObject* getCollisionObject();
|
||||
|
@ -28,11 +31,13 @@ namespace MWPhysics
|
|||
const btHeightfieldTerrainShape* getShape() const;
|
||||
|
||||
private:
|
||||
btHeightfieldTerrainShape* mShape;
|
||||
btCollisionObject* mCollisionObject;
|
||||
std::unique_ptr<btHeightfieldTerrainShape> mShape;
|
||||
std::unique_ptr<btCollisionObject> mCollisionObject;
|
||||
osg::ref_ptr<const osg::Object> mHoldObject;
|
||||
std::vector<btScalar> mHeights;
|
||||
|
||||
PhysicsTaskScheduler* mTaskScheduler;
|
||||
|
||||
void operator=(const HeightField&);
|
||||
HeightField(const HeightField&);
|
||||
};
|
||||
|
|
|
@ -17,10 +17,13 @@
|
|||
#include "actor.hpp"
|
||||
#include "collisiontype.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "contacttestwrapper.h"
|
||||
#include "physicssystem.hpp"
|
||||
#include "stepper.hpp"
|
||||
#include "trace.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
static bool isActor(const btCollisionObject *obj)
|
||||
|
@ -29,12 +32,50 @@ namespace MWPhysics
|
|||
return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor;
|
||||
}
|
||||
|
||||
template <class Vec3>
|
||||
static bool isWalkableSlope(const Vec3 &normal)
|
||||
class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback
|
||||
{
|
||||
static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
|
||||
return (normal.z() > sMaxSlopeCos);
|
||||
}
|
||||
public:
|
||||
ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me)
|
||||
{
|
||||
m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile;
|
||||
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)
|
||||
{
|
||||
|
@ -99,13 +140,13 @@ namespace MWPhysics
|
|||
}
|
||||
|
||||
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).
|
||||
// That means the collision shape used for moving this actor is in a different spot than the collision shape
|
||||
// other actors are using to collide against this actor.
|
||||
// While this is strictly speaking wrong, it's needed for MW compatibility.
|
||||
actor.mPosition.z() += halfExtents.z();
|
||||
// Adjust for collision mesh offset relative to actor's "location"
|
||||
// (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own)
|
||||
// for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation
|
||||
// if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
|
||||
osg::Vec3f halfExtents = physicActor->getHalfExtents();
|
||||
actor.mPosition.z() += halfExtents.z(); // vanilla-accurate
|
||||
|
||||
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
||||
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
|
||||
|
@ -156,6 +197,13 @@ namespace MWPhysics
|
|||
* The initial velocity was set earlier (see above).
|
||||
*/
|
||||
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)
|
||||
{
|
||||
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
|
||||
|
@ -164,7 +212,7 @@ namespace MWPhysics
|
|||
if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
|
||||
{
|
||||
const osg::Vec3f down(0,0,-1);
|
||||
velocity = slide(velocity, down);
|
||||
velocity = reject(velocity, down);
|
||||
// NOTE: remainingTime is unchanged before the loop continues
|
||||
continue; // velocity updated, calculate nextpos again
|
||||
}
|
||||
|
@ -193,92 +241,158 @@ namespace MWPhysics
|
|||
break;
|
||||
}
|
||||
|
||||
// We are touching something.
|
||||
if (tracer.mFraction < 1E-9f)
|
||||
{
|
||||
// Try to separate by backing off slighly to unstuck the solver
|
||||
osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
|
||||
newPosition += backOff;
|
||||
}
|
||||
if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel)
|
||||
seenGround = true;
|
||||
|
||||
// We hit something. Check if we can step up.
|
||||
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
|
||||
osg::Vec3f oldPosition = newPosition;
|
||||
bool result = false;
|
||||
bool usedStepLogic = false;
|
||||
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
|
||||
{
|
||||
// Try to step up onto it.
|
||||
// NOTE: stepMove does not allow stepping over, modifies newPosition if successful
|
||||
result = stepper.step(newPosition, velocity*remainingTime, remainingTime);
|
||||
// NOTE: this modifies newPosition and velocity on its own if successful
|
||||
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
|
||||
const auto ptr = physicActor->getPtr();
|
||||
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
|
||||
newPosition = oldPosition;
|
||||
else if(!actor.mFlying && actor.mPosition.z() >= swimlevel)
|
||||
forceGroundTest = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can't move this way, try to find another spot along the plane
|
||||
osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal);
|
||||
// Can't step up, so slide against what we ran into
|
||||
remainingTime *= (1.0f-tracer.mFraction);
|
||||
|
||||
// Do not allow sliding upward if there is gravity.
|
||||
// Stepping will have taken care of that.
|
||||
if(!(newPosition.z() < swimlevel || actor.mFlying))
|
||||
newVelocity.z() = std::min(newVelocity.z(), 0.0f);
|
||||
auto planeNormal = tracer.mPlaneNormal;
|
||||
|
||||
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;
|
||||
if ((newVelocity * origVelocity) <= 0.f)
|
||||
break; // ^ dot product
|
||||
|
||||
numTimesSlid += 1;
|
||||
lastSlideNormalFallback = lastSlideNormal;
|
||||
lastSlideNormal = planeNormal;
|
||||
velocity = newVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
bool isOnGround = 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 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);
|
||||
if(tracer.mFraction < 1.0f && !isActor(tracer.mHitObject))
|
||||
if(tracer.mFraction < 1.0f)
|
||||
{
|
||||
const btCollisionObject* standingOn = tracer.mHitObject;
|
||||
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
|
||||
if (ptrHolder)
|
||||
actor.mStandingOn = ptrHolder->getPtr();
|
||||
|
||||
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
|
||||
physicActor->setWalkingOnWater(true);
|
||||
if (!actor.mFlying)
|
||||
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
|
||||
|
||||
isOnGround = true;
|
||||
|
||||
isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
|
||||
}
|
||||
else
|
||||
{
|
||||
// standing on actors is not allowed (see above).
|
||||
// in addition to that, apply a sliding effect away from the center of the actor,
|
||||
// so that we do not stay suspended in air indefinitely.
|
||||
if (tracer.mFraction < 1.0f && isActor(tracer.mHitObject))
|
||||
if (!isActor(tracer.mHitObject))
|
||||
{
|
||||
if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f)
|
||||
isOnGround = true;
|
||||
isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
|
||||
|
||||
const btCollisionObject* standingOn = tracer.mHitObject;
|
||||
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
|
||||
if (ptrHolder)
|
||||
actor.mStandingOn = ptrHolder->getPtr();
|
||||
|
||||
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
|
||||
physicActor->setWalkingOnWater(true);
|
||||
if (!actor.mFlying && !isOnSlope)
|
||||
{
|
||||
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;
|
||||
if (tracer.mFraction*dropDistance > sGroundOffset)
|
||||
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
|
||||
else
|
||||
{
|
||||
newPosition.z() = tracer.mEndPos.z();
|
||||
tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld);
|
||||
newPosition = (newPosition+tracer.mEndPos)/2.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vanilla allows actors to float on top of other actors. Do not push them off.
|
||||
if (!actor.mFlying && isWalkableSlope(tracer.mPlaneNormal) && tracer.mEndPos.z()+sGroundOffset <= newPosition.z())
|
||||
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
|
||||
|
||||
isOnGround = false;
|
||||
isOnGround = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -298,7 +412,98 @@ namespace MWPhysics
|
|||
physicActor->setOnGround(isOnGround);
|
||||
physicActor->setOnSlope(isOnSlope);
|
||||
|
||||
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
|
||||
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 "constants.hpp"
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
class btCollisionWorld;
|
||||
|
||||
namespace MWWorld
|
||||
|
@ -14,29 +17,35 @@ namespace MWWorld
|
|||
|
||||
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;
|
||||
struct ActorFrameData;
|
||||
struct WorldFrameData;
|
||||
|
||||
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:
|
||||
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 unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "../mwworld/player.hpp"
|
||||
|
||||
#include "actor.hpp"
|
||||
#include "contacttestwrapper.h"
|
||||
#include "movementsolver.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
#include "object.hpp"
|
||||
|
@ -151,6 +152,9 @@ namespace MWPhysics
|
|||
, mNextLOS(0)
|
||||
, mFrameNumber(0)
|
||||
, mTimer(osg::Timer::instance())
|
||||
, mTimeBegin(0)
|
||||
, mTimeEnd(0)
|
||||
, mFrameStart(0)
|
||||
{
|
||||
mNumThreads = Config::computeNumThreads(mThreadSafeBullet);
|
||||
|
||||
|
@ -167,7 +171,16 @@ namespace MWPhysics
|
|||
|
||||
mPreStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
|
||||
{
|
||||
if (mDeferAabbUpdate)
|
||||
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, [&]()
|
||||
|
@ -295,7 +308,7 @@ namespace MWPhysics
|
|||
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
|
||||
{
|
||||
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)
|
||||
|
@ -349,7 +362,6 @@ namespace MWPhysics
|
|||
{
|
||||
if (!mDeferAabbUpdate || immediate)
|
||||
{
|
||||
std::unique_lock lock(mCollisionWorldMutex);
|
||||
updatePtrAabb(ptr);
|
||||
}
|
||||
else
|
||||
|
@ -402,7 +414,7 @@ namespace MWPhysics
|
|||
|
||||
void PhysicsTaskScheduler::updateAabbs()
|
||||
{
|
||||
std::scoped_lock lock(mCollisionWorldMutex, mUpdateAabbMutex);
|
||||
std::scoped_lock lock(mUpdateAabbMutex);
|
||||
std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(),
|
||||
[this](const std::weak_ptr<PtrHolder>& ptr) { updatePtrAabb(ptr); });
|
||||
mUpdateAabb.clear();
|
||||
|
@ -412,6 +424,7 @@ namespace MWPhysics
|
|||
{
|
||||
if (const auto p = ptr.lock())
|
||||
{
|
||||
std::scoped_lock lock(mCollisionWorldMutex);
|
||||
if (const auto actor = std::dynamic_pointer_cast<Actor>(p))
|
||||
{
|
||||
actor->updateCollisionObjectPosition();
|
||||
|
@ -438,15 +451,16 @@ namespace MWPhysics
|
|||
if (!mNewFrame)
|
||||
mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; });
|
||||
|
||||
if (mDeferAabbUpdate)
|
||||
mPreStepBarrier->wait();
|
||||
mPreStepBarrier->wait();
|
||||
|
||||
int job = 0;
|
||||
while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
|
||||
{
|
||||
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
|
||||
if(const auto actor = mActorsFrameData[job].mActor.lock())
|
||||
{
|
||||
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
|
||||
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
||||
}
|
||||
}
|
||||
|
||||
mPostStepBarrier->wait();
|
||||
|
@ -471,13 +485,13 @@ namespace MWPhysics
|
|||
|
||||
void PhysicsTaskScheduler::updateActorsPositions()
|
||||
{
|
||||
std::unique_lock lock(mCollisionWorldMutex);
|
||||
for (auto& actorData : mActorsFrameData)
|
||||
{
|
||||
if(const auto actor = actorData.mActor.lock())
|
||||
{
|
||||
if (actor->setPosition(actorData.mPosition))
|
||||
{
|
||||
std::scoped_lock lock(mCollisionWorldMutex);
|
||||
actor->updateCollisionObjectPosition();
|
||||
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
|
||||
}
|
||||
|
@ -505,7 +519,10 @@ namespace MWPhysics
|
|||
while (mRemainingSteps--)
|
||||
{
|
||||
for (auto& actorData : mActorsFrameData)
|
||||
{
|
||||
MovementSolver::unstuck(actorData, mCollisionWorld.get());
|
||||
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
||||
}
|
||||
|
||||
updateActorsPositions();
|
||||
}
|
||||
|
@ -523,6 +540,8 @@ namespace MWPhysics
|
|||
|
||||
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));
|
||||
|
|
|
@ -14,29 +14,29 @@
|
|||
|
||||
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)
|
||||
, mSolid(true)
|
||||
, mTaskScheduler(scheduler)
|
||||
{
|
||||
mPtr = ptr;
|
||||
|
||||
mCollisionObject.reset(new btCollisionObject);
|
||||
mCollisionObject = std::make_unique<btCollisionObject>();
|
||||
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
||||
|
||||
mCollisionObject->setUserPointer(this);
|
||||
|
||||
setScale(ptr.getCellRef().getScale());
|
||||
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
||||
const float* pos = ptr.getRefData().getPosition().pos;
|
||||
setOrigin(btVector3(pos[0], pos[1], pos[2]));
|
||||
setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3()));
|
||||
commitPositionChange();
|
||||
|
||||
mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile);
|
||||
}
|
||||
|
||||
Object::~Object()
|
||||
{
|
||||
if (mCollisionObject)
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
}
|
||||
|
||||
const Resource::BulletShapeInstance* Object::getShapeInstance() const
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue