mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-03-04 09:49:41 +00:00
Add OpenMW commits up to 4 Feb 2021
# Conflicts: # apps/openmw/engine.cpp # apps/openmw/mwmechanics/npcstats.hpp # apps/openmw/mwrender/globalmap.cpp
This commit is contained in:
commit
e1259fdc41
179 changed files with 3674 additions and 1509 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -21,7 +21,10 @@
|
|||
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
|
||||
|
@ -29,6 +32,7 @@
|
|||
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
|
||||
|
@ -42,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
|
||||
|
@ -56,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
|
||||
|
@ -81,18 +87,23 @@
|
|||
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
|
||||
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 #5043: Head Bobbing
|
||||
Feature #5199: Improve Scene Colors
|
||||
|
@ -115,6 +126,8 @@
|
|||
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
|
||||
|
||||
|
|
|
@ -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,9 +35,12 @@ 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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -338,6 +338,7 @@ include_directories("."
|
|||
${Boost_INCLUDE_DIR}
|
||||
${MyGUI_INCLUDE_DIRS}
|
||||
${OPENAL_INCLUDE_DIR}
|
||||
${OPENGL_INCLUDE_DIR}
|
||||
${BULLET_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
|
|
@ -62,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,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");
|
||||
|
|
|
@ -206,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"))
|
||||
|
@ -217,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();
|
||||
|
|
|
@ -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 contacttestwrapper
|
||||
actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback
|
||||
)
|
||||
|
||||
add_openmw_dir (mwclass
|
||||
|
|
|
@ -189,6 +189,8 @@ namespace
|
|||
|
||||
~ScopedProfile()
|
||||
{
|
||||
if (!mStats.collectStats("engine"))
|
||||
return;
|
||||
const osg::Timer_t end = mTimer.tick();
|
||||
const UserStats& stats = UserStatsValue<sType>::sValue;
|
||||
|
||||
|
@ -583,6 +585,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;
|
||||
|
@ -712,8 +719,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
|
||||
}
|
||||
|
@ -844,7 +851,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();
|
||||
|
||||
|
@ -872,6 +879,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)
|
||||
|
@ -996,6 +1004,16 @@ 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;
|
||||
}
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
|
@ -1017,15 +1035,18 @@ void OMW::Engine::go()
|
|||
*/
|
||||
|
||||
// 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())
|
||||
{
|
||||
|
@ -1050,14 +1071,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);
|
||||
|
|
|
@ -85,6 +85,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")
|
||||
|
||||
|
@ -271,11 +274,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
|
||||
|
@ -345,7 +352,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();
|
||||
|
|
|
@ -691,7 +691,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
|
||||
|
|
|
@ -646,7 +646,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)
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -40,10 +40,7 @@ namespace MWGui
|
|||
|
||||
MessageBoxManager::~MessageBoxManager ()
|
||||
{
|
||||
for (MessageBox* messageBox : mMessageBoxes)
|
||||
{
|
||||
delete messageBox;
|
||||
}
|
||||
MessageBoxManager::clear();
|
||||
}
|
||||
|
||||
int MessageBoxManager::getMessagesCount()
|
||||
|
|
|
@ -284,7 +284,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"));
|
||||
|
@ -368,7 +368,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();
|
||||
}
|
||||
|
|
|
@ -337,21 +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::stringstream detail;
|
||||
for (int i = 0; i < ESM::Attribute::Length; ++i)
|
||||
{
|
||||
if (auto increase = PCstats.getLevelUpAttributeIncrease(i))
|
||||
detail << (detail.str().empty() ? "" : "\n") << "#{"
|
||||
<< MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[i])
|
||||
<< "} x" << MyGUI::utility::toString(increase);
|
||||
}
|
||||
if (!detail.str().empty())
|
||||
levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str()));
|
||||
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 ());
|
||||
|
|
|
@ -344,12 +344,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)
|
||||
{
|
||||
|
@ -360,7 +356,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
|
||||
|
|
|
@ -182,16 +182,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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,7 +203,6 @@ namespace MWInput
|
|||
BindingsManager::~BindingsManager()
|
||||
{
|
||||
mInputBinder->save(mUserFile);
|
||||
delete mInputBinder;
|
||||
}
|
||||
|
||||
void BindingsManager::update(float dt)
|
||||
|
@ -326,7 +325,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])))
|
||||
|
@ -413,7 +412,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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -132,13 +142,14 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -463,9 +463,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
|
||||
|
@ -954,7 +954,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);
|
||||
|
@ -2056,7 +2060,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();
|
||||
|
@ -2578,10 +2582,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));
|
||||
|
||||
|
@ -2606,8 +2610,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();
|
||||
|
@ -2637,17 +2640,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();
|
||||
|
|
|
@ -383,11 +383,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];
|
||||
|
|
|
@ -131,8 +131,6 @@ namespace MWMechanics
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
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
|
||||
{
|
||||
|
|
|
@ -77,7 +77,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);
|
||||
|
@ -99,7 +102,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
|
||||
|
@ -122,13 +125,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
|
||||
|
@ -140,7 +143,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;
|
||||
|
@ -326,7 +329,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);
|
||||
|
@ -382,18 +385,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -71,7 +71,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
|
|||
|
||||
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());
|
||||
|
@ -111,8 +111,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)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include <mutex>
|
||||
|
||||
#include "closestnotmeconvexresultcallback.hpp"
|
||||
#include "actorconvexcallback.hpp"
|
||||
#include "collisiontype.hpp"
|
||||
#include "contacttestwrapper.h"
|
||||
|
||||
|
@ -31,13 +31,13 @@ namespace MWPhysics
|
|||
}
|
||||
};
|
||||
|
||||
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world)
|
||||
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 ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
|
||||
btScalar ActorConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
|
||||
{
|
||||
if (convexResult.m_hitCollisionObject == mMe)
|
||||
return btScalar(1);
|
|
@ -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, const btCollisionWorld * world);
|
||||
ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world);
|
||||
|
||||
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override;
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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&);
|
||||
};
|
||||
|
|
|
@ -152,6 +152,9 @@ namespace MWPhysics
|
|||
, mNextLOS(0)
|
||||
, mFrameNumber(0)
|
||||
, mTimer(osg::Timer::instance())
|
||||
, mTimeBegin(0)
|
||||
, mTimeEnd(0)
|
||||
, mFrameStart(0)
|
||||
{
|
||||
mNumThreads = Config::computeNumThreads(mThreadSafeBullet);
|
||||
|
||||
|
@ -170,6 +173,8 @@ namespace MWPhysics
|
|||
{
|
||||
if (mDeferAabbUpdate)
|
||||
updateAabbs();
|
||||
if (!mRemainingSteps)
|
||||
return;
|
||||
for (auto& data : mActorsFrameData)
|
||||
if (data.mActor.lock())
|
||||
{
|
||||
|
@ -357,7 +362,6 @@ namespace MWPhysics
|
|||
{
|
||||
if (!mDeferAabbUpdate || immediate)
|
||||
{
|
||||
std::unique_lock lock(mCollisionWorldMutex);
|
||||
updatePtrAabb(ptr);
|
||||
}
|
||||
else
|
||||
|
@ -410,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();
|
||||
|
@ -420,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();
|
||||
|
@ -451,9 +456,11 @@ namespace MWPhysics
|
|||
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();
|
||||
|
@ -478,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());
|
||||
}
|
||||
|
@ -533,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
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace MWPhysics
|
|||
class Object final : public PtrHolder
|
||||
{
|
||||
public:
|
||||
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, PhysicsTaskScheduler* scheduler);
|
||||
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler);
|
||||
~Object() override;
|
||||
|
||||
const Resource::BulletShapeInstance* getShapeInstance() const;
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
#include "deepestnotmecontacttestresultcallback.hpp"
|
||||
#include "closestnotmerayresultcallback.hpp"
|
||||
#include "contacttestresultcallback.hpp"
|
||||
#include "projectileconvexcallback.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "movementsolver.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
|
@ -107,12 +108,7 @@ namespace MWPhysics
|
|||
if (mWaterCollisionObject)
|
||||
mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get());
|
||||
|
||||
for (auto& heightField : mHeightFields)
|
||||
{
|
||||
mTaskScheduler->removeCollisionObject(heightField.second->getCollisionObject());
|
||||
delete heightField.second;
|
||||
}
|
||||
|
||||
mHeightFields.clear();
|
||||
mObjects.clear();
|
||||
mActors.clear();
|
||||
mProjectiles.clear();
|
||||
|
@ -275,7 +271,7 @@ namespace MWPhysics
|
|||
return 0.f;
|
||||
}
|
||||
|
||||
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group, int projId) const
|
||||
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const
|
||||
{
|
||||
if (from == to)
|
||||
{
|
||||
|
@ -312,7 +308,7 @@ namespace MWPhysics
|
|||
}
|
||||
}
|
||||
|
||||
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId));
|
||||
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo);
|
||||
resultCallback.m_collisionFilterGroup = group;
|
||||
resultCallback.m_collisionFilterMask = mask;
|
||||
|
||||
|
@ -466,22 +462,14 @@ namespace MWPhysics
|
|||
|
||||
void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
|
||||
{
|
||||
HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject);
|
||||
mHeightFields[std::make_pair(x,y)] = heightfield;
|
||||
|
||||
mTaskScheduler->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap,
|
||||
CollisionType_Actor|CollisionType_Projectile);
|
||||
mHeightFields[std::make_pair(x,y)] = std::make_unique<HeightField>(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject, mTaskScheduler.get());
|
||||
}
|
||||
|
||||
void PhysicsSystem::removeHeightField (int x, int y)
|
||||
{
|
||||
HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y));
|
||||
if(heightfield != mHeightFields.end())
|
||||
{
|
||||
mTaskScheduler->removeCollisionObject(heightfield->second->getCollisionObject());
|
||||
delete heightfield->second;
|
||||
mHeightFields.erase(heightfield);
|
||||
}
|
||||
}
|
||||
|
||||
const HeightField* PhysicsSystem::getHeightField(int x, int y) const
|
||||
|
@ -489,7 +477,7 @@ namespace MWPhysics
|
|||
const auto heightField = mHeightFields.find(std::make_pair(x, y));
|
||||
if (heightField == mHeightFields.end())
|
||||
return nullptr;
|
||||
return heightField->second;
|
||||
return heightField->second.get();
|
||||
}
|
||||
|
||||
void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType)
|
||||
|
@ -498,14 +486,11 @@ namespace MWPhysics
|
|||
if (!shapeInstance || !shapeInstance->getCollisionShape())
|
||||
return;
|
||||
|
||||
auto obj = std::make_shared<Object>(ptr, shapeInstance, mTaskScheduler.get());
|
||||
auto obj = std::make_shared<Object>(ptr, shapeInstance, collisionType, mTaskScheduler.get());
|
||||
mObjects.emplace(ptr, obj);
|
||||
|
||||
if (obj->isAnimated())
|
||||
mAnimatedObjects.insert(obj.get());
|
||||
|
||||
mTaskScheduler->addCollisionObject(obj->getCollisionObject(), collisionType,
|
||||
CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile);
|
||||
}
|
||||
|
||||
void PhysicsSystem::remove(const MWWorld::Ptr &ptr)
|
||||
|
@ -620,15 +605,44 @@ namespace MWPhysics
|
|||
}
|
||||
}
|
||||
|
||||
void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position)
|
||||
void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) const
|
||||
{
|
||||
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
|
||||
if (foundProjectile != mProjectiles.end())
|
||||
{
|
||||
foundProjectile->second->setPosition(position);
|
||||
mTaskScheduler->updateSingleAabb(foundProjectile->second);
|
||||
const auto foundProjectile = mProjectiles.find(projectileId);
|
||||
assert(foundProjectile != mProjectiles.end());
|
||||
auto* projectile = foundProjectile->second.get();
|
||||
|
||||
btVector3 btFrom = Misc::Convert::toBullet(projectile->getPosition());
|
||||
btVector3 btTo = Misc::Convert::toBullet(position);
|
||||
|
||||
if (btFrom == btTo)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto casterPtr = projectile->getCaster();
|
||||
const auto* caster = [this,&casterPtr]() -> const btCollisionObject*
|
||||
{
|
||||
const Actor* actor = getActor(casterPtr);
|
||||
if (actor)
|
||||
return actor->getCollisionObject();
|
||||
const Object* object = getObject(casterPtr);
|
||||
if (object)
|
||||
return object->getCollisionObject();
|
||||
return nullptr;
|
||||
}();
|
||||
assert(caster);
|
||||
|
||||
ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile);
|
||||
resultCallback.m_collisionFilterMask = 0xff;
|
||||
resultCallback.m_collisionFilterGroup = CollisionType_Projectile;
|
||||
|
||||
const btQuaternion btrot = btQuaternion::getIdentity();
|
||||
btTransform from_ (btrot, btFrom);
|
||||
btTransform to_ (btrot, btTo);
|
||||
|
||||
mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback);
|
||||
|
||||
const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld);
|
||||
projectile->setPosition(newpos);
|
||||
mTaskScheduler->updateSingleAabb(foundProjectile->second);
|
||||
}
|
||||
|
||||
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
|
||||
|
@ -691,10 +705,10 @@ namespace MWPhysics
|
|||
mActors.emplace(ptr, std::move(actor));
|
||||
}
|
||||
|
||||
int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position)
|
||||
int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater)
|
||||
{
|
||||
mProjectileId++;
|
||||
auto projectile = std::make_shared<Projectile>(mProjectileId, caster, position, mTaskScheduler.get(), this);
|
||||
auto projectile = std::make_shared<Projectile>(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this);
|
||||
mProjectiles.emplace(mProjectileId, std::move(projectile));
|
||||
|
||||
return mProjectileId;
|
||||
|
@ -903,7 +917,7 @@ namespace MWPhysics
|
|||
mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight));
|
||||
mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get());
|
||||
mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water,
|
||||
CollisionType_Actor);
|
||||
CollisionType_Actor|CollisionType_Projectile);
|
||||
}
|
||||
|
||||
bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const
|
||||
|
|
|
@ -124,8 +124,8 @@ namespace MWPhysics
|
|||
void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World);
|
||||
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh);
|
||||
|
||||
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position);
|
||||
void updateProjectile(const int projectileId, const osg::Vec3f &position);
|
||||
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater);
|
||||
void updateProjectile(const int projectileId, const osg::Vec3f &position) const;
|
||||
void removeProjectile(const int projectileId);
|
||||
|
||||
void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
|
||||
|
@ -174,7 +174,7 @@ namespace MWPhysics
|
|||
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
|
||||
RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(),
|
||||
std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(),
|
||||
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override;
|
||||
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override;
|
||||
|
||||
RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override;
|
||||
|
||||
|
@ -284,7 +284,7 @@ namespace MWPhysics
|
|||
using ProjectileMap = std::map<int, std::shared_ptr<Projectile>>;
|
||||
ProjectileMap mProjectiles;
|
||||
|
||||
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
|
||||
using HeightFieldMap = std::map<std::pair<int, int>, std::unique_ptr<HeightField>>;
|
||||
HeightFieldMap mHeightFields;
|
||||
|
||||
bool mDebugDrawEnabled;
|
||||
|
|
|
@ -13,22 +13,25 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "collisiontype.hpp"
|
||||
#include "memory"
|
||||
#include "mtphysics.hpp"
|
||||
#include "projectile.hpp"
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
|
||||
: mActive(true)
|
||||
Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
|
||||
: mCanCrossWaterSurface(canCrossWaterSurface)
|
||||
, mCrossedWaterSurface(false)
|
||||
, mActive(true)
|
||||
, mCaster(caster)
|
||||
, mWaterHitPosition(std::nullopt)
|
||||
, mPhysics(physicssystem)
|
||||
, mTaskScheduler(scheduler)
|
||||
, mProjectileId(projectileId)
|
||||
{
|
||||
mShape.reset(new btSphereShape(1.f));
|
||||
mShape = std::make_unique<btSphereShape>(radius);
|
||||
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());
|
||||
|
@ -45,12 +48,9 @@ Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::
|
|||
|
||||
Projectile::~Projectile()
|
||||
{
|
||||
if (mCollisionObject)
|
||||
{
|
||||
if (!mActive)
|
||||
mPhysics->reportCollision(mHitPosition, mHitNormal);
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
}
|
||||
if (!mActive)
|
||||
mPhysics->reportCollision(mHitPosition, mHitNormal);
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
}
|
||||
|
||||
void Projectile::commitPositionChange()
|
||||
|
@ -70,6 +70,17 @@ void Projectile::setPosition(const osg::Vec3f &position)
|
|||
mTransformUpdatePending = true;
|
||||
}
|
||||
|
||||
osg::Vec3f Projectile::getPosition() const
|
||||
{
|
||||
std::scoped_lock lock(mMutex);
|
||||
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
|
||||
}
|
||||
|
||||
bool Projectile::canTraverseWater() const
|
||||
{
|
||||
return mCanCrossWaterSurface;
|
||||
}
|
||||
|
||||
void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
|
||||
{
|
||||
if (!mActive.load(std::memory_order_acquire))
|
||||
|
@ -81,12 +92,6 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
|
|||
mActive.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void Projectile::activate()
|
||||
{
|
||||
assert(!mActive);
|
||||
mActive.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
MWWorld::Ptr Projectile::getCaster() const
|
||||
{
|
||||
std::scoped_lock lock(mMutex);
|
||||
|
@ -111,21 +116,32 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const
|
|||
if (mCaster == target)
|
||||
return false;
|
||||
|
||||
if (!mValidTargets.empty())
|
||||
if (target.isEmpty() || mValidTargets.empty())
|
||||
return true;
|
||||
|
||||
bool validTarget = false;
|
||||
for (const auto& targetActor : mValidTargets)
|
||||
{
|
||||
bool validTarget = false;
|
||||
for (const auto& targetActor : mValidTargets)
|
||||
if (targetActor == target)
|
||||
{
|
||||
if (targetActor == target)
|
||||
{
|
||||
validTarget = true;
|
||||
break;
|
||||
}
|
||||
validTarget = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return validTarget;
|
||||
}
|
||||
return true;
|
||||
return validTarget;
|
||||
}
|
||||
|
||||
std::optional<btVector3> Projectile::getWaterHitPosition()
|
||||
{
|
||||
return std::exchange(mWaterHitPosition, std::nullopt);
|
||||
}
|
||||
|
||||
void Projectile::setWaterHitPosition(btVector3 pos)
|
||||
{
|
||||
if (mCrossedWaterSurface)
|
||||
return;
|
||||
mCrossedWaterSurface = true;
|
||||
mWaterHitPosition = pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#include "components/misc/convert.hpp"
|
||||
|
||||
|
@ -32,7 +33,7 @@ namespace MWPhysics
|
|||
class Projectile final : public PtrHolder
|
||||
{
|
||||
public:
|
||||
Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
|
||||
Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
|
||||
~Projectile() override;
|
||||
|
||||
btConvexShape* getConvexShape() const { return mConvexShape; }
|
||||
|
@ -40,17 +41,13 @@ namespace MWPhysics
|
|||
void commitPositionChange();
|
||||
|
||||
void setPosition(const osg::Vec3f& position);
|
||||
osg::Vec3f getPosition() const;
|
||||
|
||||
btCollisionObject* getCollisionObject() const
|
||||
{
|
||||
return mCollisionObject.get();
|
||||
}
|
||||
|
||||
int getProjectileId() const
|
||||
{
|
||||
return mProjectileId;
|
||||
}
|
||||
|
||||
bool isActive() const
|
||||
{
|
||||
return mActive.load(std::memory_order_acquire);
|
||||
|
@ -65,18 +62,16 @@ namespace MWPhysics
|
|||
MWWorld::Ptr getCaster() const;
|
||||
void setCaster(MWWorld::Ptr caster);
|
||||
|
||||
osg::Vec3f getHitPos() const
|
||||
{
|
||||
assert(!mActive);
|
||||
return Misc::Convert::toOsg(mHitPosition);
|
||||
}
|
||||
bool canTraverseWater() const;
|
||||
|
||||
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
|
||||
void activate();
|
||||
|
||||
void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
|
||||
bool isValidTarget(const MWWorld::Ptr& target) const;
|
||||
|
||||
std::optional<btVector3> getWaterHitPosition();
|
||||
void setWaterHitPosition(btVector3 pos);
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<btCollisionShape> mShape;
|
||||
|
@ -85,9 +80,12 @@ namespace MWPhysics
|
|||
std::unique_ptr<btCollisionObject> mCollisionObject;
|
||||
btTransform mLocalTransform;
|
||||
bool mTransformUpdatePending;
|
||||
bool mCanCrossWaterSurface;
|
||||
bool mCrossedWaterSurface;
|
||||
std::atomic<bool> mActive;
|
||||
MWWorld::Ptr mCaster;
|
||||
MWWorld::Ptr mHitTarget;
|
||||
std::optional<btVector3> mWaterHitPosition;
|
||||
btVector3 mHitPosition;
|
||||
btVector3 mHitNormal;
|
||||
|
||||
|
@ -95,15 +93,11 @@ namespace MWPhysics
|
|||
|
||||
mutable std::mutex mMutex;
|
||||
|
||||
osg::Vec3f mPosition;
|
||||
|
||||
PhysicsSystem *mPhysics;
|
||||
PhysicsTaskScheduler *mTaskScheduler;
|
||||
|
||||
Projectile(const Projectile&);
|
||||
Projectile& operator=(const Projectile&);
|
||||
|
||||
int mProjectileId;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
69
apps/openmw/mwphysics/projectileconvexcallback.cpp
Normal file
69
apps/openmw/mwphysics/projectileconvexcallback.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "actor.hpp"
|
||||
#include "collisiontype.hpp"
|
||||
#include "projectile.hpp"
|
||||
#include "projectileconvexcallback.hpp"
|
||||
#include "ptrholder.hpp"
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj)
|
||||
: btCollisionWorld::ClosestConvexResultCallback(from, to)
|
||||
, mMe(me), mProjectile(proj)
|
||||
{
|
||||
assert(mProjectile);
|
||||
}
|
||||
|
||||
btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace)
|
||||
{
|
||||
// don't hit the caster
|
||||
if (result.m_hitCollisionObject == mMe)
|
||||
return 1.f;
|
||||
|
||||
// don't hit the projectile
|
||||
if (result.m_hitCollisionObject == mProjectile->getCollisionObject())
|
||||
return 1.f;
|
||||
|
||||
btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace);
|
||||
switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup)
|
||||
{
|
||||
case CollisionType_Actor:
|
||||
{
|
||||
auto* target = static_cast<Actor*>(result.m_hitCollisionObject->getUserPointer());
|
||||
if (!mProjectile->isValidTarget(target->getPtr()))
|
||||
return 1.f;
|
||||
mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal);
|
||||
break;
|
||||
}
|
||||
case CollisionType_Projectile:
|
||||
{
|
||||
auto* target = static_cast<Projectile*>(result.m_hitCollisionObject->getUserPointer());
|
||||
if (!mProjectile->isValidTarget(target->getCaster()))
|
||||
return 1.f;
|
||||
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||
break;
|
||||
}
|
||||
case CollisionType_Water:
|
||||
{
|
||||
mProjectile->setWaterHitPosition(m_hitPointWorld);
|
||||
if (mProjectile->canTraverseWater())
|
||||
return 1.f;
|
||||
mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
auto* target = static_cast<PtrHolder*>(result.m_hitCollisionObject->getUserPointer());
|
||||
auto ptr = target ? target->getPtr() : MWWorld::Ptr();
|
||||
mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result.m_hitFraction;
|
||||
}
|
||||
|
||||
}
|
||||
|
27
apps/openmw/mwphysics/projectileconvexcallback.hpp
Normal file
27
apps/openmw/mwphysics/projectileconvexcallback.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H
|
||||
#define OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
class btCollisionObject;
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class Projectile;
|
||||
|
||||
class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback
|
||||
{
|
||||
public:
|
||||
ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj);
|
||||
|
||||
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override;
|
||||
|
||||
private:
|
||||
const btCollisionObject* mMe;
|
||||
Projectile* mProjectile;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -29,7 +29,7 @@ namespace MWPhysics
|
|||
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
|
||||
virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(),
|
||||
std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(),
|
||||
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0;
|
||||
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0;
|
||||
|
||||
virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include "collisiontype.hpp"
|
||||
#include "actor.hpp"
|
||||
#include "closestnotmeconvexresultcallback.hpp"
|
||||
#include "actorconvexcallback.hpp"
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
|
@ -24,7 +24,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star
|
|||
to.setOrigin(btend);
|
||||
|
||||
const btVector3 motion = btstart-btend;
|
||||
ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0), world);
|
||||
ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world);
|
||||
// Inherit the actor's collision group and mask
|
||||
newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
@ -62,7 +62,7 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const
|
|||
btTransform to(trans.getBasis(), btend);
|
||||
|
||||
const btVector3 motion = btstart-btend;
|
||||
ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world);
|
||||
ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world);
|
||||
// Inherit the actor's collision group and mask
|
||||
newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <osg/Material>
|
||||
#include <osg/Fog>
|
||||
#include <osg/BlendFunc>
|
||||
#include <osg/TexEnvCombine>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/Camera>
|
||||
#include <osg/PositionAttitudeTransform>
|
||||
|
@ -15,6 +16,8 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/fallback/fallback.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
#include <components/sceneutil/shadow.hpp>
|
||||
|
||||
|
@ -85,7 +88,7 @@ namespace MWRender
|
|||
class SetUpBlendVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||
SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mNoAlphaUniform(new osg::Uniform("noAlpha", false))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -102,10 +105,17 @@ namespace MWRender
|
|||
newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON);
|
||||
node.setStateSet(newStateSet);
|
||||
}
|
||||
|
||||
if (stateset->getMode(GL_BLEND) & osg::StateAttribute::ON)
|
||||
{
|
||||
// Disable noBlendAlphaEnv
|
||||
stateset->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF);
|
||||
stateset->addUniform(mNoAlphaUniform);
|
||||
}
|
||||
}
|
||||
traverse(node);
|
||||
}
|
||||
private:
|
||||
osg::ref_ptr<osg::Uniform> mNoAlphaUniform;
|
||||
};
|
||||
|
||||
CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem,
|
||||
|
@ -164,6 +174,20 @@ namespace MWRender
|
|||
fog->setEnd(10000000);
|
||||
stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE);
|
||||
|
||||
// Opaque stuff must have 1 as its fragment alpha as the FBO is translucent, so having blending off isn't enough
|
||||
osg::ref_ptr<osg::TexEnvCombine> noBlendAlphaEnv = new osg::TexEnvCombine();
|
||||
noBlendAlphaEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE);
|
||||
noBlendAlphaEnv->setSource0_Alpha(osg::TexEnvCombine::CONSTANT);
|
||||
noBlendAlphaEnv->setConstantColor(osg::Vec4(0.0, 0.0, 0.0, 1.0));
|
||||
noBlendAlphaEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE);
|
||||
noBlendAlphaEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
|
||||
osg::ref_ptr<osg::Texture2D> dummyTexture = new osg::Texture2D();
|
||||
dummyTexture->setInternalFormat(GL_RED);
|
||||
dummyTexture->setTextureSize(1, 1);
|
||||
stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON);
|
||||
stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON);
|
||||
stateset->addUniform(new osg::Uniform("noAlpha", true));
|
||||
|
||||
osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel;
|
||||
lightmodel->setAmbientIntensity(osg::Vec4(0.0, 0.0, 0.0, 1.0));
|
||||
stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON);
|
||||
|
@ -227,6 +251,7 @@ namespace MWRender
|
|||
|
||||
void CharacterPreview::setBlendMode()
|
||||
{
|
||||
mResourceSystem->getSceneManager()->recreateShaders(mNode, "objects", true);
|
||||
SetUpBlendVisitor visitor;
|
||||
mNode->accept(visitor);
|
||||
}
|
||||
|
|
|
@ -76,8 +76,8 @@ namespace MWRender
|
|||
mLandFogStart = viewDistance * (1 - fogDepth);
|
||||
mLandFogEnd = viewDistance;
|
||||
}
|
||||
mUnderwaterFogStart = std::min(viewDistance, 6666.f) * (1 - underwaterFog);
|
||||
mUnderwaterFogEnd = std::min(viewDistance, 6666.f);
|
||||
mUnderwaterFogStart = std::min(viewDistance, 7168.f) * (1 - underwaterFog);
|
||||
mUnderwaterFogEnd = std::min(viewDistance, 7168.f);
|
||||
}
|
||||
mFogColor = color;
|
||||
}
|
||||
|
|
|
@ -682,7 +682,7 @@ namespace MWRender
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
it = mPendingImageDest.erase(it);
|
||||
mPendingImageDest.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
282
apps/openmw/mwrender/groundcover.cpp
Normal file
282
apps/openmw/mwrender/groundcover.cpp
Normal file
|
@ -0,0 +1,282 @@
|
|||
#include "groundcover.hpp"
|
||||
|
||||
#include <osg/Geometry>
|
||||
#include <osg/VertexAttribDivisor>
|
||||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
|
||||
#include "apps/openmw/mwworld/esmstore.hpp"
|
||||
#include "apps/openmw/mwbase/environment.hpp"
|
||||
#include "apps/openmw/mwbase/world.hpp"
|
||||
|
||||
#include "vismask.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ESM::REC_STAT:
|
||||
return store.get<ESM::Static>().searchStatic(id)->mModel;
|
||||
default:
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::setWindSpeed(float windSpeed)
|
||||
{
|
||||
mWindSpeed = windSpeed;
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos)
|
||||
{
|
||||
mPlayerPos = playerPos;
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::setDefaults(osg::StateSet *stateset)
|
||||
{
|
||||
osg::ref_ptr<osg::Uniform> windUniform = new osg::Uniform("windSpeed", 0.0f);
|
||||
stateset->addUniform(windUniform.get());
|
||||
|
||||
osg::ref_ptr<osg::Uniform> playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f));
|
||||
stateset->addUniform(playerPosUniform.get());
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
|
||||
{
|
||||
osg::ref_ptr<osg::Uniform> windUniform = stateset->getUniform("windSpeed");
|
||||
if (windUniform != nullptr)
|
||||
windUniform->set(mWindSpeed);
|
||||
|
||||
osg::ref_ptr<osg::Uniform> playerPosUniform = stateset->getUniform("playerPos");
|
||||
if (playerPosUniform != nullptr)
|
||||
playerPosUniform->set(mPlayerPos);
|
||||
}
|
||||
|
||||
class InstancingVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
InstancingVisitor(std::vector<Groundcover::GroundcoverEntry>& instances, osg::Vec3f& chunkPosition)
|
||||
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||
, mInstances(instances)
|
||||
, mChunkPosition(chunkPosition)
|
||||
{
|
||||
}
|
||||
|
||||
void apply(osg::Node& node) override
|
||||
{
|
||||
osg::ref_ptr<osg::StateSet> ss = node.getStateSet();
|
||||
if (ss != nullptr)
|
||||
{
|
||||
ss->removeAttribute(osg::StateAttribute::MATERIAL);
|
||||
removeAlpha(ss);
|
||||
}
|
||||
|
||||
traverse(node);
|
||||
}
|
||||
|
||||
void apply(osg::Geometry& geom) override
|
||||
{
|
||||
for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i)
|
||||
{
|
||||
geom.getPrimitiveSet(i)->setNumInstances(mInstances.size());
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Vec4Array> transforms = new osg::Vec4Array(mInstances.size());
|
||||
osg::BoundingBox box;
|
||||
float radius = geom.getBoundingBox().radius();
|
||||
for (unsigned int i = 0; i < transforms->getNumElements(); i++)
|
||||
{
|
||||
osg::Vec3f pos(mInstances[i].mPos.asVec3());
|
||||
osg::Vec3f relativePos = pos - mChunkPosition;
|
||||
(*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale);
|
||||
|
||||
// Use an additional margin due to groundcover animation
|
||||
float instanceRadius = radius * mInstances[i].mScale * 1.1f;
|
||||
osg::BoundingSphere instanceBounds(relativePos, instanceRadius);
|
||||
box.expandBy(instanceBounds);
|
||||
}
|
||||
|
||||
geom.setInitialBound(box);
|
||||
|
||||
osg::ref_ptr<osg::Vec3Array> rotations = new osg::Vec3Array(mInstances.size());
|
||||
for (unsigned int i = 0; i < rotations->getNumElements(); i++)
|
||||
{
|
||||
(*rotations)[i] = mInstances[i].mPos.asRotationVec3();
|
||||
}
|
||||
|
||||
// Display lists do not support instancing in OSG 3.4
|
||||
geom.setUseDisplayList(false);
|
||||
|
||||
geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX);
|
||||
geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX);
|
||||
|
||||
osg::ref_ptr<osg::StateSet> ss = geom.getOrCreateStateSet();
|
||||
ss->setAttribute(new osg::VertexAttribDivisor(6, 1));
|
||||
ss->setAttribute(new osg::VertexAttribDivisor(7, 1));
|
||||
|
||||
ss->removeAttribute(osg::StateAttribute::MATERIAL);
|
||||
removeAlpha(ss);
|
||||
|
||||
traverse(geom);
|
||||
}
|
||||
private:
|
||||
std::vector<Groundcover::GroundcoverEntry> mInstances;
|
||||
osg::Vec3f mChunkPosition;
|
||||
|
||||
void removeAlpha(osg::StateSet* stateset)
|
||||
{
|
||||
// MGE uses default alpha settings for groundcover, so we can not rely on alpha properties
|
||||
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
|
||||
stateset->removeMode(GL_ALPHA_TEST);
|
||||
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
|
||||
stateset->removeMode(GL_BLEND);
|
||||
stateset->setRenderBinToInherit();
|
||||
}
|
||||
};
|
||||
|
||||
class DensityCalculator
|
||||
{
|
||||
public:
|
||||
DensityCalculator(float density)
|
||||
: mDensity(density)
|
||||
{
|
||||
}
|
||||
|
||||
bool isInstanceEnabled()
|
||||
{
|
||||
if (mDensity >= 1.f) return true;
|
||||
|
||||
mCurrentGroundcover += mDensity;
|
||||
if (mCurrentGroundcover < 1.f) return false;
|
||||
|
||||
mCurrentGroundcover -= 1.f;
|
||||
|
||||
return true;
|
||||
}
|
||||
void reset() { mCurrentGroundcover = 0.f; }
|
||||
|
||||
private:
|
||||
float mCurrentGroundcover = 0.f;
|
||||
float mDensity = 0.f;
|
||||
};
|
||||
|
||||
inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound)
|
||||
{
|
||||
osg::Vec2f size = maxBound - minBound;
|
||||
if (size.x() >=1 && size.y() >=1) return true;
|
||||
|
||||
osg::Vec3f pos = ref.mPos.asVec3();
|
||||
osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE;
|
||||
if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y())
|
||||
|| (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
{
|
||||
ChunkId id = std::make_tuple(center, size, activeGrid);
|
||||
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
|
||||
if (obj)
|
||||
return obj->asNode();
|
||||
else
|
||||
{
|
||||
InstanceMap instances;
|
||||
collectInstances(instances, size, center);
|
||||
osg::ref_ptr<osg::Node> node = createChunk(instances, center);
|
||||
mCache->addEntryToObjectCache(id, node.get());
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density)
|
||||
: GenericResourceManager<ChunkId>(nullptr)
|
||||
, mSceneManager(sceneManager)
|
||||
, mDensity(density)
|
||||
{
|
||||
}
|
||||
|
||||
void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center)
|
||||
{
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f));
|
||||
osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f));
|
||||
DensityCalculator calculator(mDensity);
|
||||
std::vector<ESM::ESMReader> esm;
|
||||
osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
|
||||
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
|
||||
{
|
||||
for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY)
|
||||
{
|
||||
const ESM::Cell* cell = store.get<ESM::Cell>().searchStatic(cellX, cellY);
|
||||
if (!cell) continue;
|
||||
|
||||
calculator.reset();
|
||||
for (size_t i=0; i<cell->mContextList.size(); ++i)
|
||||
{
|
||||
unsigned int index = cell->mContextList.at(i).index;
|
||||
if (esm.size() <= index)
|
||||
esm.resize(index+1);
|
||||
cell->restore(esm[index], i);
|
||||
ESM::CellRef ref;
|
||||
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
|
||||
bool deleted = false;
|
||||
while(cell->getNextRef(esm[index], ref, deleted))
|
||||
{
|
||||
if (deleted) continue;
|
||||
if (!ref.mRefNum.fromGroundcoverFile()) continue;
|
||||
|
||||
if (!calculator.isInstanceEnabled()) continue;
|
||||
if (!isInChunkBorders(ref, minBound, maxBound)) continue;
|
||||
|
||||
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
||||
int type = store.findStatic(ref.mRefID);
|
||||
std::string model = getGroundcoverModel(type, ref.mRefID, store);
|
||||
if (model.empty()) continue;
|
||||
model = "meshes/" + model;
|
||||
|
||||
instances[model].emplace_back(ref, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center)
|
||||
{
|
||||
osg::ref_ptr<osg::Group> group = new osg::Group;
|
||||
osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE;
|
||||
for (auto& pair : instances)
|
||||
{
|
||||
const osg::Node* temp = mSceneManager->getTemplate(pair.first);
|
||||
osg::ref_ptr<osg::Node> node = static_cast<osg::Node*>(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES)));
|
||||
|
||||
// Keep link to original mesh to keep it in cache
|
||||
group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp));
|
||||
|
||||
InstancingVisitor visitor(pair.second, worldCenter);
|
||||
node->accept(visitor);
|
||||
group->addChild(node);
|
||||
}
|
||||
|
||||
group->getBound();
|
||||
group->setNodeMask(Mask_Groundcover);
|
||||
mSceneManager->recreateShaders(group, "groundcover", false, true);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
unsigned int Groundcover::getNodeMask()
|
||||
{
|
||||
return Mask_Groundcover;
|
||||
}
|
||||
|
||||
void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const
|
||||
{
|
||||
stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize());
|
||||
}
|
||||
}
|
69
apps/openmw/mwrender/groundcover.hpp
Normal file
69
apps/openmw/mwrender/groundcover.hpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#ifndef OPENMW_MWRENDER_GROUNDCOVER_H
|
||||
#define OPENMW_MWRENDER_GROUNDCOVER_H
|
||||
|
||||
#include <components/terrain/quadtreeworld.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class GroundcoverUpdater : public SceneUtil::StateSetUpdater
|
||||
{
|
||||
public:
|
||||
GroundcoverUpdater()
|
||||
: mWindSpeed(0.f)
|
||||
, mPlayerPos(osg::Vec3f())
|
||||
{
|
||||
}
|
||||
|
||||
void setWindSpeed(float windSpeed);
|
||||
void setPlayerPos(osg::Vec3f playerPos);
|
||||
|
||||
protected:
|
||||
void setDefaults(osg::StateSet *stateset) override;
|
||||
void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override;
|
||||
|
||||
private:
|
||||
float mWindSpeed;
|
||||
osg::Vec3f mPlayerPos;
|
||||
};
|
||||
|
||||
typedef std::tuple<osg::Vec2f, float, bool> ChunkId; // Center, Size, ActiveGrid
|
||||
class Groundcover : public Resource::GenericResourceManager<ChunkId>, public Terrain::QuadTreeWorld::ChunkManager
|
||||
{
|
||||
public:
|
||||
Groundcover(Resource::SceneManager* sceneManager, float density);
|
||||
~Groundcover() = default;
|
||||
|
||||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
|
||||
|
||||
unsigned int getNodeMask() override;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
|
||||
|
||||
struct GroundcoverEntry
|
||||
{
|
||||
ESM::Position mPos;
|
||||
float mScale;
|
||||
std::string mModel;
|
||||
|
||||
GroundcoverEntry(const ESM::CellRef& ref, const std::string& model)
|
||||
{
|
||||
mPos = ref.mPos;
|
||||
mScale = ref.mScale;
|
||||
mModel = model;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Resource::SceneManager* mSceneManager;
|
||||
float mDensity;
|
||||
|
||||
typedef std::map<std::string, std::vector<GroundcoverEntry>> InstanceMap;
|
||||
osg::ref_ptr<osg::Node> createChunk(InstanceMap& instances, const osg::Vec2f& center);
|
||||
void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -65,6 +65,15 @@ namespace
|
|||
return val*val;
|
||||
}
|
||||
|
||||
std::pair<int, int> divideIntoSegments(const osg::BoundingBox& bounds, float mapSize)
|
||||
{
|
||||
osg::Vec2f min(bounds.xMin(), bounds.yMin());
|
||||
osg::Vec2f max(bounds.xMax(), bounds.yMax());
|
||||
osg::Vec2f length = max - min;
|
||||
const int segsX = static_cast<int>(std::ceil(length.x() / mapSize));
|
||||
const int segsY = static_cast<int>(std::ceil(length.y() / mapSize));
|
||||
return {segsX, segsY};
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
|
@ -127,12 +136,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell)
|
|||
}
|
||||
else
|
||||
{
|
||||
// FIXME: segmenting code duplicated from requestMap
|
||||
osg::Vec2f min(mBounds.xMin(), mBounds.yMin());
|
||||
osg::Vec2f max(mBounds.xMax(), mBounds.yMax());
|
||||
osg::Vec2f length = max-min;
|
||||
const int segsX = static_cast<int>(std::ceil(length.x() / mMapWorldSize));
|
||||
const int segsY = static_cast<int>(std::ceil(length.y() / mMapWorldSize));
|
||||
auto segments = divideIntoSegments(mBounds, mMapWorldSize);
|
||||
|
||||
std::unique_ptr<ESM::FogState> fog (new ESM::FogState());
|
||||
|
||||
|
@ -142,11 +146,11 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell)
|
|||
fog->mBounds.mMaxY = mBounds.yMax();
|
||||
fog->mNorthMarkerAngle = mAngle;
|
||||
|
||||
fog->mFogTextures.reserve(segsX*segsY);
|
||||
fog->mFogTextures.reserve(segments.first * segments.second);
|
||||
|
||||
for (int x=0; x<segsX; ++x)
|
||||
for (int x = 0; x < segments.first; ++x)
|
||||
{
|
||||
for (int y=0; y<segsY; ++y)
|
||||
for (int y = 0; y < segments.second; ++y)
|
||||
{
|
||||
const MapSegment& segment = mSegments[std::make_pair(x,y)];
|
||||
|
||||
|
@ -422,56 +426,74 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell)
|
|||
// If there is fog state in the CellStore (e.g. when it came from a savegame) we need to do some checks
|
||||
// to see if this state is still valid.
|
||||
// Both the cell bounds and the NorthMarker rotation could be changed by the content files or exchanged models.
|
||||
// If they changed by too much (for bounds, < padding is considered acceptable) then parts of the interior might not
|
||||
// be covered by the map anymore.
|
||||
// If they changed by too much then parts of the interior might not be covered by the map anymore.
|
||||
// The following code detects this, and discards the CellStore's fog state if it needs to.
|
||||
bool cellHasValidFog = false;
|
||||
std::vector<std::pair<int, int>> segmentMappings;
|
||||
if (cell->getFog())
|
||||
{
|
||||
ESM::FogState* fog = cell->getFog();
|
||||
|
||||
osg::Vec3f newMin (fog->mBounds.mMinX, fog->mBounds.mMinY, zMin);
|
||||
osg::Vec3f newMax (fog->mBounds.mMaxX, fog->mBounds.mMaxY, zMax);
|
||||
|
||||
osg::Vec3f minDiff = newMin - mBounds._min;
|
||||
osg::Vec3f maxDiff = newMax - mBounds._max;
|
||||
|
||||
if (std::abs(minDiff.x()) > padding || std::abs(minDiff.y()) > padding
|
||||
|| std::abs(maxDiff.x()) > padding || std::abs(maxDiff.y()) > padding
|
||||
|| std::abs(mAngle - fog->mNorthMarkerAngle) > osg::DegreesToRadians(5.f))
|
||||
if (std::abs(mAngle - fog->mNorthMarkerAngle) < osg::DegreesToRadians(5.f))
|
||||
{
|
||||
// Nuke it
|
||||
cellHasValidFog = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Looks sane, use it
|
||||
mBounds = osg::BoundingBox(newMin, newMax);
|
||||
// Expand mBounds so the saved textures fit the same grid
|
||||
int xOffset = 0;
|
||||
int yOffset = 0;
|
||||
if(fog->mBounds.mMinX < mBounds.xMin())
|
||||
{
|
||||
mBounds.xMin() = fog->mBounds.mMinX;
|
||||
}
|
||||
else if(fog->mBounds.mMinX > mBounds.xMin())
|
||||
{
|
||||
float diff = fog->mBounds.mMinX - mBounds.xMin();
|
||||
xOffset += diff / mMapWorldSize;
|
||||
xOffset++;
|
||||
mBounds.xMin() = fog->mBounds.mMinX - xOffset * mMapWorldSize;
|
||||
}
|
||||
if(fog->mBounds.mMinY < mBounds.yMin())
|
||||
{
|
||||
mBounds.yMin() = fog->mBounds.mMinY;
|
||||
}
|
||||
else if(fog->mBounds.mMinY > mBounds.yMin())
|
||||
{
|
||||
float diff = fog->mBounds.mMinY - mBounds.yMin();
|
||||
yOffset += diff / mMapWorldSize;
|
||||
yOffset++;
|
||||
mBounds.yMin() = fog->mBounds.mMinY - yOffset * mMapWorldSize;
|
||||
}
|
||||
mBounds.xMax() = std::max(mBounds.xMax(), fog->mBounds.mMaxX);
|
||||
mBounds.yMax() = std::max(mBounds.yMax(), fog->mBounds.mMaxY);
|
||||
|
||||
if(xOffset != 0 || yOffset != 0)
|
||||
Log(Debug::Warning) << "Warning: expanding fog by " << xOffset << ", " << yOffset;
|
||||
|
||||
const auto& textures = fog->mFogTextures;
|
||||
segmentMappings.reserve(textures.size());
|
||||
osg::BoundingBox savedBounds{
|
||||
fog->mBounds.mMinX, fog->mBounds.mMinY, 0,
|
||||
fog->mBounds.mMaxX, fog->mBounds.mMaxY, 0
|
||||
};
|
||||
auto segments = divideIntoSegments(savedBounds, mMapWorldSize);
|
||||
for (int x = 0; x < segments.first; ++x)
|
||||
for (int y = 0; y < segments.second; ++y)
|
||||
segmentMappings.emplace_back(std::make_pair(x + xOffset, y + yOffset));
|
||||
|
||||
mAngle = fog->mNorthMarkerAngle;
|
||||
cellHasValidFog = true;
|
||||
}
|
||||
}
|
||||
|
||||
osg::Vec2f min(mBounds.xMin(), mBounds.yMin());
|
||||
osg::Vec2f max(mBounds.xMax(), mBounds.yMax());
|
||||
|
||||
osg::Vec2f length = max-min;
|
||||
osg::Vec2f center(mBounds.center().x(), mBounds.center().y());
|
||||
osg::Quat cameraOrient (mAngle, osg::Vec3d(0,0,-1));
|
||||
|
||||
osg::Vec2f center(bounds.center().x(), bounds.center().y());
|
||||
|
||||
// divide into segments
|
||||
const int segsX = static_cast<int>(std::ceil(length.x() / mMapWorldSize));
|
||||
const int segsY = static_cast<int>(std::ceil(length.y() / mMapWorldSize));
|
||||
|
||||
int i = 0;
|
||||
for (int x=0; x<segsX; ++x)
|
||||
auto segments = divideIntoSegments(mBounds, mMapWorldSize);
|
||||
for (int x = 0; x < segments.first; ++x)
|
||||
{
|
||||
for (int y=0; y<segsY; ++y)
|
||||
for (int y = 0; y < segments.second; ++y)
|
||||
{
|
||||
osg::Vec2f start = min + osg::Vec2f(mMapWorldSize*x, mMapWorldSize*y);
|
||||
osg::Vec2f newcenter = start + osg::Vec2f(mMapWorldSize/2.f, mMapWorldSize/2.f);
|
||||
|
||||
osg::Quat cameraOrient (mAngle, osg::Vec3d(0,0,-1));
|
||||
osg::Vec2f a = newcenter - center;
|
||||
osg::Vec3f rotatedCenter = cameraOrient * (osg::Vec3f(a.x(), a.y(), 0));
|
||||
|
||||
|
@ -483,26 +505,24 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell)
|
|||
|
||||
setupRenderToTexture(camera, x, y);
|
||||
|
||||
MapSegment& segment = mSegments[std::make_pair(x,y)];
|
||||
auto coords = std::make_pair(x,y);
|
||||
MapSegment& segment = mSegments[coords];
|
||||
if (!segment.mFogOfWarImage)
|
||||
{
|
||||
if (!cellHasValidFog)
|
||||
segment.initFogOfWar();
|
||||
else
|
||||
bool loaded = false;
|
||||
for(size_t index{}; index < segmentMappings.size(); index++)
|
||||
{
|
||||
ESM::FogState* fog = cell->getFog();
|
||||
|
||||
// We are using the same bounds and angle as we were using when the textures were originally made. Segments should come out the same.
|
||||
if (i >= int(fog->mFogTextures.size()))
|
||||
if(segmentMappings[index] == coords)
|
||||
{
|
||||
Log(Debug::Warning) << "Warning: fog texture count mismatch";
|
||||
ESM::FogState* fog = cell->getFog();
|
||||
segment.loadFogOfWar(fog->mFogTextures[index]);
|
||||
loaded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
segment.loadFogOfWar(fog->mFogTextures[i]);
|
||||
}
|
||||
if(!loaded)
|
||||
segment.initFogOfWar();
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -249,15 +249,6 @@ namespace MWRender
|
|||
}
|
||||
};
|
||||
|
||||
class TemplateRef : public osg::Object
|
||||
{
|
||||
public:
|
||||
TemplateRef() {}
|
||||
TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {}
|
||||
META_Object(MWRender, TemplateRef)
|
||||
std::vector<osg::ref_ptr<const Object>> mObjects;
|
||||
};
|
||||
|
||||
class RefnumSet : public osg::Object
|
||||
{
|
||||
public:
|
||||
|
@ -407,6 +398,7 @@ namespace MWRender
|
|||
int type = store.findStatic(ref.mRefID);
|
||||
if (!typeFilter(type,size>=2)) continue;
|
||||
if (deleted) { refs.erase(ref.mRefNum); continue; }
|
||||
if (ref.mRefNum.fromGroundcoverFile()) continue;
|
||||
refs[ref.mRefNum] = ref;
|
||||
}
|
||||
}
|
||||
|
@ -530,7 +522,7 @@ namespace MWRender
|
|||
|
||||
osg::ref_ptr<osg::Group> group = new osg::Group;
|
||||
osg::ref_ptr<osg::Group> mergeGroup = new osg::Group;
|
||||
osg::ref_ptr<TemplateRef> templateRefs = new TemplateRef;
|
||||
osg::ref_ptr<Resource::TemplateMultiRef> templateRefs = new Resource::TemplateMultiRef;
|
||||
osgUtil::StateToCompile stateToCompile(0, nullptr);
|
||||
CopyOp copyop;
|
||||
for (const auto& pair : nodes)
|
||||
|
@ -596,7 +588,7 @@ namespace MWRender
|
|||
if (numinstances > 0)
|
||||
{
|
||||
// add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache
|
||||
templateRefs->mObjects.emplace_back(cnode);
|
||||
templateRefs->addRef(cnode);
|
||||
|
||||
if (pair.second.mNeedCompile)
|
||||
{
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
|
||||
#include <limits>
|
||||
#include <cstdlib>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include <osg/AlphaFunc>
|
||||
#include <osg/Light>
|
||||
#include <osg/LightModel>
|
||||
#include <osg/Fog>
|
||||
|
@ -13,25 +12,20 @@
|
|||
#include <osg/Group>
|
||||
#include <osg/UserDataContainer>
|
||||
#include <osg/ComputeBoundsVisitor>
|
||||
#include <osg/ShapeDrawable>
|
||||
#include <osg/TextureCubeMap>
|
||||
|
||||
#include <osgUtil/LineSegmentIntersector>
|
||||
|
||||
#include <osg/ImageUtils>
|
||||
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
#include <components/nifosg/nifloader.hpp>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/keyframemanager.hpp>
|
||||
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
@ -74,7 +68,8 @@
|
|||
#include "recastmesh.hpp"
|
||||
#include "fogmanager.hpp"
|
||||
#include "objectpaging.hpp"
|
||||
|
||||
#include "screenshotmanager.hpp"
|
||||
#include "groundcover.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
@ -250,6 +245,10 @@ namespace MWRender
|
|||
globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0";
|
||||
globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0";
|
||||
|
||||
float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93;
|
||||
globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f);
|
||||
globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance);
|
||||
|
||||
// It is unnecessary to stop/start the viewer as no frames are being rendered yet.
|
||||
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines);
|
||||
|
||||
|
@ -276,7 +275,8 @@ namespace MWRender
|
|||
const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders");
|
||||
const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders");
|
||||
|
||||
mTerrainStorage = new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps);
|
||||
mTerrainStorage.reset(new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps));
|
||||
const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain");
|
||||
|
||||
if (Settings::Manager::getBool("distant terrain", "Terrain"))
|
||||
{
|
||||
|
@ -284,12 +284,11 @@ namespace MWRender
|
|||
int compMapPower = Settings::Manager::getInt("composite map level", "Terrain");
|
||||
compMapPower = std::max(-3, compMapPower);
|
||||
float compMapLevel = pow(2, compMapPower);
|
||||
const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain");
|
||||
const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain");
|
||||
float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain");
|
||||
maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f);
|
||||
mTerrain.reset(new Terrain::QuadTreeWorld(
|
||||
sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug,
|
||||
sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug,
|
||||
compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize));
|
||||
if (Settings::Manager::getBool("object paging", "Terrain"))
|
||||
{
|
||||
|
@ -299,11 +298,43 @@ namespace MWRender
|
|||
}
|
||||
}
|
||||
else
|
||||
mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug));
|
||||
mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug));
|
||||
|
||||
mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells"));
|
||||
mTerrain->setWorkQueue(mWorkQueue.get());
|
||||
|
||||
if (Settings::Manager::getBool("enabled", "Groundcover"))
|
||||
{
|
||||
osg::ref_ptr<osg::Group> groundcoverRoot = new osg::Group;
|
||||
groundcoverRoot->setNodeMask(Mask_Groundcover);
|
||||
groundcoverRoot->setName("Groundcover Root");
|
||||
sceneRoot->addChild(groundcoverRoot);
|
||||
|
||||
// Force a unified alpha handling instead of data from meshes
|
||||
osg::ref_ptr<osg::AlphaFunc> alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f/255.f);
|
||||
groundcoverRoot->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON);
|
||||
|
||||
mGroundcoverUpdater = new GroundcoverUpdater;
|
||||
groundcoverRoot->addUpdateCallback(mGroundcoverUpdater);
|
||||
|
||||
float chunkSize = Settings::Manager::getFloat("min chunk size", "Groundcover");
|
||||
if (chunkSize >= 1.0f)
|
||||
chunkSize = 1.0f;
|
||||
else if (chunkSize >= 0.5f)
|
||||
chunkSize = 0.5f;
|
||||
else if (chunkSize >= 0.25f)
|
||||
chunkSize = 0.25f;
|
||||
else if (chunkSize != 0.125f)
|
||||
chunkSize = 0.125f;
|
||||
|
||||
float density = Settings::Manager::getFloat("density", "Groundcover");
|
||||
density = std::clamp(density, 0.f, 1.f);
|
||||
|
||||
mGroundcoverWorld.reset(new Terrain::QuadTreeWorld(groundcoverRoot, mTerrainStorage.get(), Mask_Groundcover, lodFactor, chunkSize));
|
||||
mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density));
|
||||
static_cast<Terrain::QuadTreeWorld*>(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get());
|
||||
mResourceSystem->addResourceManager(mGroundcover.get());
|
||||
}
|
||||
// water goes after terrain for correct waterculling order
|
||||
mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath));
|
||||
|
||||
|
@ -311,6 +342,8 @@ namespace MWRender
|
|||
if (Settings::Manager::getBool("view over shoulder", "Camera"))
|
||||
mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get()));
|
||||
|
||||
mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get()));
|
||||
|
||||
mViewer->setLightingMode(osgViewer::View::NO_LIGHT);
|
||||
|
||||
osg::ref_ptr<osg::LightSource> source = new osg::LightSource;
|
||||
|
@ -513,7 +546,11 @@ namespace MWRender
|
|||
mWater->changeCell(store);
|
||||
|
||||
if (store->getCell()->isExterior())
|
||||
{
|
||||
mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
|
||||
if (mGroundcoverWorld)
|
||||
mGroundcoverWorld->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
|
||||
}
|
||||
}
|
||||
void RenderingManager::removeCell(const MWWorld::CellStore *store)
|
||||
{
|
||||
|
@ -522,7 +559,11 @@ namespace MWRender
|
|||
mObjects->removeCell(store);
|
||||
|
||||
if (store->getCell()->isExterior())
|
||||
{
|
||||
mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
|
||||
if (mGroundcoverWorld)
|
||||
mGroundcoverWorld->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
|
||||
}
|
||||
|
||||
mWater->removeCell(store);
|
||||
}
|
||||
|
@ -532,6 +573,8 @@ namespace MWRender
|
|||
if (!enable)
|
||||
mWater->setCullCallback(nullptr);
|
||||
mTerrain->enable(enable);
|
||||
if (mGroundcoverWorld)
|
||||
mGroundcoverWorld->enable(enable);
|
||||
}
|
||||
|
||||
void RenderingManager::setSkyEnabled(bool enabled)
|
||||
|
@ -617,6 +660,16 @@ namespace MWRender
|
|||
mEffectManager->update(dt);
|
||||
mSky->update(dt);
|
||||
mWater->update(dt);
|
||||
|
||||
if (mGroundcoverUpdater)
|
||||
{
|
||||
const MWWorld::Ptr& player = mPlayerAnimation->getPtr();
|
||||
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
|
||||
|
||||
float windSpeed = mSky->getBaseWindSpeed();
|
||||
mGroundcoverUpdater->setWindSpeed(windSpeed);
|
||||
mGroundcoverUpdater->setPlayerPos(playerPos);
|
||||
}
|
||||
}
|
||||
|
||||
updateNavMesh();
|
||||
|
@ -695,298 +748,31 @@ namespace MWRender
|
|||
mSky->setWaterHeight(height);
|
||||
}
|
||||
|
||||
class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback
|
||||
void RenderingManager::screenshot(osg::Image* image, int w, int h)
|
||||
{
|
||||
public:
|
||||
NotifyDrawCompletedCallback(unsigned int frame)
|
||||
: mDone(false), mFrame(frame)
|
||||
{
|
||||
}
|
||||
mScreenshotManager->screenshot(image, w, h);
|
||||
}
|
||||
|
||||
void operator () (osg::RenderInfo& renderInfo) const override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame)
|
||||
{
|
||||
mDone = true;
|
||||
mCondition.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void waitTillDone()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mDone)
|
||||
return;
|
||||
mCondition.wait(lock);
|
||||
}
|
||||
|
||||
mutable std::condition_variable mCondition;
|
||||
mutable std::mutex mMutex;
|
||||
mutable bool mDone;
|
||||
unsigned int mFrame;
|
||||
};
|
||||
|
||||
bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr)
|
||||
bool RenderingManager::screenshot360(osg::Image* image)
|
||||
{
|
||||
int screenshotW = mViewer->getCamera()->getViewport()->width();
|
||||
int screenshotH = mViewer->getCamera()->getViewport()->height();
|
||||
int screenshotMapping = 0;
|
||||
|
||||
std::vector<std::string> settingArgs;
|
||||
Misc::StringUtils::split(settingStr, settingArgs);
|
||||
|
||||
if (settingArgs.size() > 0)
|
||||
{
|
||||
std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"};
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
if (settingArgs[0].compare(typeStrings[i]) == 0)
|
||||
{
|
||||
screenshotMapping = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << ".";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// planet mapping needs higher resolution
|
||||
int cubeSize = screenshotMapping == 2 ? screenshotW : screenshotW / 2;
|
||||
|
||||
if (settingArgs.size() > 1)
|
||||
screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str()));
|
||||
|
||||
if (settingArgs.size() > 2)
|
||||
screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str()));
|
||||
|
||||
if (settingArgs.size() > 3)
|
||||
cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str()));
|
||||
|
||||
if (mCamera->isVanityOrPreviewModeEnabled())
|
||||
{
|
||||
Log(Debug::Warning) << "Spherical screenshots are not allowed in preview mode.";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rawCubemap = screenshotMapping == 3;
|
||||
|
||||
if (rawCubemap)
|
||||
screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row
|
||||
else if (screenshotMapping == 2)
|
||||
screenshotH = screenshotW; // use square resolution for planet mapping
|
||||
|
||||
std::vector<osg::ref_ptr<osg::Image>> images;
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
images.push_back(new osg::Image);
|
||||
|
||||
osg::Vec3 directions[6] = {
|
||||
rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1),
|
||||
osg::Vec3(0,0,-1),
|
||||
osg::Vec3(-1,0,0),
|
||||
rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0),
|
||||
osg::Vec3(0,1,0),
|
||||
osg::Vec3(0,-1,0)};
|
||||
|
||||
double rotations[] = {
|
||||
-osg::PI / 2.0,
|
||||
osg::PI / 2.0,
|
||||
osg::PI,
|
||||
0,
|
||||
osg::PI / 2.0,
|
||||
osg::PI / 2.0};
|
||||
|
||||
double fovBackup = mFieldOfView;
|
||||
mFieldOfView = 90.0; // each cubemap side sees 90 degrees
|
||||
|
||||
int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask();
|
||||
|
||||
if (mCamera->isFirstPerson())
|
||||
mPlayerAnimation->getObjectRoot()->setNodeMask(0);
|
||||
|
||||
for (int i = 0; i < 6; ++i) // for each cubemap side
|
||||
{
|
||||
osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]);
|
||||
|
||||
if (!rawCubemap)
|
||||
transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1));
|
||||
|
||||
osg::Image *sideImage = images[i].get();
|
||||
screenshot(sideImage,cubeSize,cubeSize,transform);
|
||||
|
||||
if (!rawCubemap)
|
||||
sideImage->flipHorizontal();
|
||||
}
|
||||
mScreenshotManager->screenshot360(image);
|
||||
|
||||
mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup);
|
||||
mFieldOfView = fovBackup;
|
||||
|
||||
if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images
|
||||
{
|
||||
image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType());
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// run on GPU now:
|
||||
|
||||
osg::ref_ptr<osg::TextureCubeMap> cubeTexture (new osg::TextureCubeMap);
|
||||
cubeTexture->setResizeNonPowerOfTwoHint(false);
|
||||
|
||||
cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST);
|
||||
cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST);
|
||||
|
||||
cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
cubeTexture->setImage(i,images[i].get());
|
||||
|
||||
osg::ref_ptr<osg::Camera> screenshotCamera (new osg::Camera);
|
||||
osg::ref_ptr<osg::ShapeDrawable> quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0)));
|
||||
|
||||
std::map<std::string, std::string> defineMap;
|
||||
|
||||
Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager();
|
||||
osg::ref_ptr<osg::Shader> fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT));
|
||||
osg::ref_ptr<osg::Shader> vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX));
|
||||
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
|
||||
|
||||
osg::ref_ptr<osg::Program> program (new osg::Program);
|
||||
program->addShader(fragmentShader);
|
||||
program->addShader(vertexShader);
|
||||
stateset->setAttributeAndModes(program, osg::StateAttribute::ON);
|
||||
|
||||
stateset->addUniform(new osg::Uniform("cubeMap",0));
|
||||
stateset->addUniform(new osg::Uniform("mapping",screenshotMapping));
|
||||
stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON);
|
||||
|
||||
quad->setStateSet(stateset);
|
||||
quad->setUpdateCallback(nullptr);
|
||||
|
||||
screenshotCamera->addChild(quad);
|
||||
|
||||
renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderingManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h)
|
||||
{
|
||||
camera->setNodeMask(Mask_RenderToTexture);
|
||||
camera->attach(osg::Camera::COLOR_BUFFER, image);
|
||||
camera->setRenderOrder(osg::Camera::PRE_RENDER);
|
||||
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
|
||||
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT);
|
||||
|
||||
camera->setViewport(0, 0, w, h);
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
|
||||
texture->setInternalFormat(GL_RGB);
|
||||
texture->setTextureSize(w,h);
|
||||
texture->setResizeNonPowerOfTwoHint(false);
|
||||
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
camera->attach(osg::Camera::COLOR_BUFFER,texture);
|
||||
|
||||
image->setDataType(GL_UNSIGNED_BYTE);
|
||||
image->setPixelFormat(texture->getInternalFormat());
|
||||
|
||||
mRootNode->addChild(camera);
|
||||
|
||||
// The draw needs to complete before we can copy back our image.
|
||||
osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback(0));
|
||||
camera->setFinalDrawCallback(callback);
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false);
|
||||
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
mViewer->renderingTraversals();
|
||||
callback->waitTillDone();
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff();
|
||||
|
||||
// now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed
|
||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||
|
||||
camera->removeChildren(0, camera->getNumChildren());
|
||||
mRootNode->removeChild(camera);
|
||||
}
|
||||
|
||||
class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback
|
||||
{
|
||||
public:
|
||||
ReadImageFromFramebufferCallback(osg::Image* image, int width, int height)
|
||||
: mWidth(width), mHeight(height), mImage(image)
|
||||
{
|
||||
}
|
||||
void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override
|
||||
{
|
||||
int screenW = renderInfo.getCurrentCamera()->getViewport()->width();
|
||||
int screenH = renderInfo.getCurrentCamera()->getViewport()->height();
|
||||
double imageaspect = (double)mWidth/(double)mHeight;
|
||||
int leftPadding = std::max(0, static_cast<int>(screenW - screenH * imageaspect) / 2);
|
||||
int topPadding = std::max(0, static_cast<int>(screenH - screenW / imageaspect) / 2);
|
||||
int width = screenW - leftPadding*2;
|
||||
int height = screenH - topPadding*2;
|
||||
mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE);
|
||||
mImage->scaleImage(mWidth, mHeight, 1);
|
||||
}
|
||||
private:
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
osg::ref_ptr<osg::Image> mImage;
|
||||
};
|
||||
|
||||
void RenderingManager::screenshotFramebuffer(osg::Image* image, int w, int h)
|
||||
{
|
||||
osg::Camera* camera = mViewer->getCamera();
|
||||
osg::ref_ptr<osg::Drawable> tempDrw = new osg::Drawable;
|
||||
tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h));
|
||||
tempDrw->setCullingActive(false);
|
||||
tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera
|
||||
camera->addChild(tempDrw);
|
||||
osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber()));
|
||||
camera->setFinalDrawCallback(callback);
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
mViewer->renderingTraversals();
|
||||
callback->waitTillDone();
|
||||
// now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed
|
||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||
camera->removeChild(tempDrw);
|
||||
camera->setFinalDrawCallback(nullptr);
|
||||
}
|
||||
|
||||
void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform)
|
||||
{
|
||||
osg::ref_ptr<osg::Camera> rttCamera (new osg::Camera);
|
||||
rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance);
|
||||
rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform);
|
||||
|
||||
rttCamera->setUpdateCallback(new NoTraverseCallback);
|
||||
rttCamera->addChild(mSceneRoot);
|
||||
|
||||
rttCamera->addChild(mWater->getReflectionCamera());
|
||||
rttCamera->addChild(mWater->getRefractionCamera());
|
||||
|
||||
rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI));
|
||||
|
||||
rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
renderCameraToImage(rttCamera.get(),image,w,h);
|
||||
}
|
||||
|
||||
osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox &worldbb)
|
||||
{
|
||||
if (!worldbb.valid()) return osg::Vec4f();
|
||||
|
@ -1077,7 +863,7 @@ namespace MWRender
|
|||
mIntersectionVisitor->setIntersector(intersector);
|
||||
|
||||
int mask = ~0;
|
||||
mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater);
|
||||
mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater|Mask_Groundcover);
|
||||
if (ignorePlayer)
|
||||
mask &= ~(Mask_Player);
|
||||
if (ignoreActors)
|
||||
|
@ -1236,6 +1022,12 @@ namespace MWRender
|
|||
fov = std::min(mFieldOfView, 140.f);
|
||||
float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f);
|
||||
mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f));
|
||||
|
||||
if (mGroundcoverWorld)
|
||||
{
|
||||
int groundcoverDistance = Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover"));
|
||||
mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderingManager::updateTextureFiltering()
|
||||
|
@ -1430,6 +1222,8 @@ namespace MWRender
|
|||
void RenderingManager::setActiveGrid(const osg::Vec4i &grid)
|
||||
{
|
||||
mTerrain->setActiveGrid(grid);
|
||||
if (mGroundcoverWorld)
|
||||
mGroundcoverWorld->setActiveGrid(grid);
|
||||
}
|
||||
bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled)
|
||||
{
|
||||
|
|
|
@ -70,10 +70,11 @@ namespace DetourNavigator
|
|||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
class GroundcoverUpdater;
|
||||
class StateUpdater;
|
||||
|
||||
class EffectManager;
|
||||
class ScreenshotManager;
|
||||
class FogManager;
|
||||
class SkyManager;
|
||||
class NpcAnimation;
|
||||
|
@ -87,6 +88,7 @@ namespace MWRender
|
|||
class ActorsPaths;
|
||||
class RecastMesh;
|
||||
class ObjectPaging;
|
||||
class Groundcover;
|
||||
|
||||
class RenderingManager : public MWRender::RenderingInterface
|
||||
{
|
||||
|
@ -148,9 +150,8 @@ namespace MWRender
|
|||
void setWaterHeight(float level);
|
||||
|
||||
/// Take a screenshot of w*h onto the given image, not including the GUI.
|
||||
void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); // make a new render at given size
|
||||
void screenshotFramebuffer(osg::Image* image, int w, int h); // copy directly from framebuffer and scale to given size
|
||||
bool screenshot360(osg::Image* image, std::string settingStr);
|
||||
void screenshot(osg::Image* image, int w, int h);
|
||||
bool screenshot360(osg::Image* image);
|
||||
|
||||
struct RayResult
|
||||
{
|
||||
|
@ -248,8 +249,6 @@ namespace MWRender
|
|||
|
||||
void reportStats() const;
|
||||
|
||||
void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h);
|
||||
|
||||
void updateNavMesh();
|
||||
|
||||
void updateRecastMesh();
|
||||
|
@ -263,6 +262,8 @@ namespace MWRender
|
|||
osg::ref_ptr<osg::Group> mSceneRoot;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
|
||||
osg::ref_ptr<GroundcoverUpdater> mGroundcoverUpdater;
|
||||
|
||||
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
|
||||
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
|
||||
|
||||
|
@ -277,10 +278,13 @@ namespace MWRender
|
|||
std::unique_ptr<Objects> mObjects;
|
||||
std::unique_ptr<Water> mWater;
|
||||
std::unique_ptr<Terrain::World> mTerrain;
|
||||
TerrainStorage* mTerrainStorage;
|
||||
std::unique_ptr<Terrain::World> mGroundcoverWorld;
|
||||
std::unique_ptr<TerrainStorage> mTerrainStorage;
|
||||
std::unique_ptr<ObjectPaging> mObjectPaging;
|
||||
std::unique_ptr<Groundcover> mGroundcover;
|
||||
std::unique_ptr<SkyManager> mSky;
|
||||
std::unique_ptr<FogManager> mFog;
|
||||
std::unique_ptr<ScreenshotManager> mScreenshotManager;
|
||||
std::unique_ptr<EffectManager> mEffectManager;
|
||||
std::unique_ptr<SceneUtil::ShadowManager> mShadowManager;
|
||||
osg::ref_ptr<NpcAnimation> mPlayerAnimation;
|
||||
|
|
324
apps/openmw/mwrender/screenshotmanager.cpp
Normal file
324
apps/openmw/mwrender/screenshotmanager.cpp
Normal file
|
@ -0,0 +1,324 @@
|
|||
#include "screenshotmanager.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include <osg/ImageUtils>
|
||||
#include <osg/ShapeDrawable>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/TextureCubeMap>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwgui/loadingscreen.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "util.hpp"
|
||||
#include "vismask.hpp"
|
||||
#include "water.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
enum Screenshot360Type
|
||||
{
|
||||
Spherical,
|
||||
Cylindrical,
|
||||
Planet,
|
||||
RawCubemap
|
||||
};
|
||||
|
||||
class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback
|
||||
{
|
||||
public:
|
||||
NotifyDrawCompletedCallback(unsigned int frame)
|
||||
: mDone(false), mFrame(frame)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () (osg::RenderInfo& renderInfo) const override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame)
|
||||
{
|
||||
mDone = true;
|
||||
mCondition.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void waitTillDone()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mDone)
|
||||
return;
|
||||
mCondition.wait(lock);
|
||||
}
|
||||
|
||||
mutable std::condition_variable mCondition;
|
||||
mutable std::mutex mMutex;
|
||||
mutable bool mDone;
|
||||
unsigned int mFrame;
|
||||
};
|
||||
|
||||
class ReadImageFromFramebufferCallback : public osg::Drawable::DrawCallback
|
||||
{
|
||||
public:
|
||||
ReadImageFromFramebufferCallback(osg::Image* image, int width, int height)
|
||||
: mWidth(width), mHeight(height), mImage(image)
|
||||
{
|
||||
}
|
||||
void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* /*drawable*/) const override
|
||||
{
|
||||
int screenW = renderInfo.getCurrentCamera()->getViewport()->width();
|
||||
int screenH = renderInfo.getCurrentCamera()->getViewport()->height();
|
||||
double imageaspect = (double)mWidth/(double)mHeight;
|
||||
int leftPadding = std::max(0, static_cast<int>(screenW - screenH * imageaspect) / 2);
|
||||
int topPadding = std::max(0, static_cast<int>(screenH - screenW / imageaspect) / 2);
|
||||
int width = screenW - leftPadding*2;
|
||||
int height = screenH - topPadding*2;
|
||||
mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE);
|
||||
mImage->scaleImage(mWidth, mHeight, 1);
|
||||
}
|
||||
private:
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
osg::ref_ptr<osg::Image> mImage;
|
||||
};
|
||||
|
||||
ScreenshotManager::ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, osg::ref_ptr<osg::Group> sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water)
|
||||
: mViewer(viewer)
|
||||
, mRootNode(rootNode)
|
||||
, mSceneRoot(sceneRoot)
|
||||
, mResourceSystem(resourceSystem)
|
||||
, mWater(water)
|
||||
{
|
||||
}
|
||||
|
||||
void ScreenshotManager::screenshot(osg::Image* image, int w, int h)
|
||||
{
|
||||
osg::Camera* camera = mViewer->getCamera();
|
||||
osg::ref_ptr<osg::Drawable> tempDrw = new osg::Drawable;
|
||||
tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h));
|
||||
tempDrw->setCullingActive(false);
|
||||
tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera
|
||||
camera->addChild(tempDrw);
|
||||
osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber()));
|
||||
camera->setFinalDrawCallback(callback);
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
mViewer->renderingTraversals();
|
||||
callback->waitTillDone();
|
||||
// now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed
|
||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||
camera->removeChild(tempDrw);
|
||||
camera->setFinalDrawCallback(nullptr);
|
||||
}
|
||||
|
||||
bool ScreenshotManager::screenshot360(osg::Image* image)
|
||||
{
|
||||
int screenshotW = mViewer->getCamera()->getViewport()->width();
|
||||
int screenshotH = mViewer->getCamera()->getViewport()->height();
|
||||
Screenshot360Type screenshotMapping = Spherical;
|
||||
|
||||
const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video");
|
||||
std::vector<std::string> settingArgs;
|
||||
Misc::StringUtils::split(settingStr, settingArgs);
|
||||
|
||||
if (settingArgs.size() > 0)
|
||||
{
|
||||
std::string typeStrings[4] = {"spherical", "cylindrical", "planet", "cubemap"};
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (settingArgs[0].compare(typeStrings[i]) == 0)
|
||||
{
|
||||
screenshotMapping = static_cast<Screenshot360Type>(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << ".";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// planet mapping needs higher resolution
|
||||
int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2;
|
||||
|
||||
if (settingArgs.size() > 1)
|
||||
screenshotW = std::min(10000, std::atoi(settingArgs[1].c_str()));
|
||||
|
||||
if (settingArgs.size() > 2)
|
||||
screenshotH = std::min(10000, std::atoi(settingArgs[2].c_str()));
|
||||
|
||||
if (settingArgs.size() > 3)
|
||||
cubeSize = std::min(5000, std::atoi(settingArgs[3].c_str()));
|
||||
|
||||
bool rawCubemap = screenshotMapping == RawCubemap;
|
||||
|
||||
if (rawCubemap)
|
||||
screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row
|
||||
else if (screenshotMapping == Planet)
|
||||
screenshotH = screenshotW; // use square resolution for planet mapping
|
||||
|
||||
std::vector<osg::ref_ptr<osg::Image>> images;
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
images.push_back(new osg::Image);
|
||||
|
||||
osg::Vec3 directions[6] = {
|
||||
rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1),
|
||||
osg::Vec3(0,0,-1),
|
||||
osg::Vec3(-1,0,0),
|
||||
rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0),
|
||||
osg::Vec3(0,1,0),
|
||||
osg::Vec3(0,-1,0)};
|
||||
|
||||
double rotations[] = {
|
||||
-osg::PI / 2.0,
|
||||
osg::PI / 2.0,
|
||||
osg::PI,
|
||||
0,
|
||||
osg::PI / 2.0,
|
||||
osg::PI / 2.0 };
|
||||
|
||||
for (int i = 0; i < 6; ++i) // for each cubemap side
|
||||
{
|
||||
osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1), directions[i]);
|
||||
|
||||
if (!rawCubemap)
|
||||
transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1));
|
||||
|
||||
osg::Image *sideImage = images[i].get();
|
||||
makeCubemapScreenshot(sideImage, cubeSize, cubeSize, transform);
|
||||
|
||||
if (!rawCubemap)
|
||||
sideImage->flipHorizontal();
|
||||
}
|
||||
|
||||
if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images
|
||||
{
|
||||
image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType());
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// run on GPU now:
|
||||
osg::ref_ptr<osg::TextureCubeMap> cubeTexture (new osg::TextureCubeMap);
|
||||
cubeTexture->setResizeNonPowerOfTwoHint(false);
|
||||
|
||||
cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST);
|
||||
cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST);
|
||||
|
||||
cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
cubeTexture->setImage(i, images[i].get());
|
||||
|
||||
osg::ref_ptr<osg::Camera> screenshotCamera(new osg::Camera);
|
||||
osg::ref_ptr<osg::ShapeDrawable> quad(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 2.0)));
|
||||
|
||||
std::map<std::string, std::string> defineMap;
|
||||
|
||||
Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager();
|
||||
osg::ref_ptr<osg::Shader> fragmentShader(shaderMgr.getShader("s360_fragment.glsl", defineMap,osg::Shader::FRAGMENT));
|
||||
osg::ref_ptr<osg::Shader> vertexShader(shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX));
|
||||
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
|
||||
|
||||
osg::ref_ptr<osg::Program> program(new osg::Program);
|
||||
program->addShader(fragmentShader);
|
||||
program->addShader(vertexShader);
|
||||
stateset->setAttributeAndModes(program, osg::StateAttribute::ON);
|
||||
|
||||
stateset->addUniform(new osg::Uniform("cubeMap", 0));
|
||||
stateset->addUniform(new osg::Uniform("mapping", screenshotMapping));
|
||||
stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON);
|
||||
|
||||
quad->setStateSet(stateset);
|
||||
quad->setUpdateCallback(nullptr);
|
||||
|
||||
screenshotCamera->addChild(quad);
|
||||
|
||||
renderCameraToImage(screenshotCamera, image, screenshotW, screenshotH);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenshotManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h)
|
||||
{
|
||||
camera->setNodeMask(Mask_RenderToTexture);
|
||||
camera->attach(osg::Camera::COLOR_BUFFER, image);
|
||||
camera->setRenderOrder(osg::Camera::PRE_RENDER);
|
||||
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
|
||||
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT);
|
||||
|
||||
camera->setViewport(0, 0, w, h);
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
|
||||
texture->setInternalFormat(GL_RGB);
|
||||
texture->setTextureSize(w,h);
|
||||
texture->setResizeNonPowerOfTwoHint(false);
|
||||
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
camera->attach(osg::Camera::COLOR_BUFFER,texture);
|
||||
|
||||
image->setDataType(GL_UNSIGNED_BYTE);
|
||||
image->setPixelFormat(texture->getInternalFormat());
|
||||
|
||||
mRootNode->addChild(camera);
|
||||
|
||||
// The draw needs to complete before we can copy back our image.
|
||||
osg::ref_ptr<NotifyDrawCompletedCallback> callback (new NotifyDrawCompletedCallback(0));
|
||||
camera->setFinalDrawCallback(callback);
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false);
|
||||
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
mViewer->renderingTraversals();
|
||||
callback->waitTillDone();
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff();
|
||||
|
||||
// now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed
|
||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||
|
||||
camera->removeChildren(0, camera->getNumChildren());
|
||||
mRootNode->removeChild(camera);
|
||||
}
|
||||
|
||||
void ScreenshotManager::makeCubemapScreenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform)
|
||||
{
|
||||
osg::ref_ptr<osg::Camera> rttCamera (new osg::Camera);
|
||||
float nearClip = Settings::Manager::getFloat("near clip", "Camera");
|
||||
float viewDistance = Settings::Manager::getFloat("viewing distance", "Camera");
|
||||
// each cubemap side sees 90 degrees
|
||||
rttCamera->setProjectionMatrixAsPerspective(90.0, w/float(h), nearClip, viewDistance);
|
||||
rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform);
|
||||
|
||||
rttCamera->setUpdateCallback(new NoTraverseCallback);
|
||||
rttCamera->addChild(mSceneRoot);
|
||||
|
||||
rttCamera->addChild(mWater->getReflectionCamera());
|
||||
rttCamera->addChild(mWater->getRefractionCamera());
|
||||
|
||||
rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI));
|
||||
|
||||
rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
renderCameraToImage(rttCamera.get(),image,w,h);
|
||||
}
|
||||
}
|
40
apps/openmw/mwrender/screenshotmanager.hpp
Normal file
40
apps/openmw/mwrender/screenshotmanager.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#ifndef MWRENDER_SCREENSHOTMANAGER_H
|
||||
#define MWRENDER_SCREENSHOTMANAGER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <osg/Group>
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class ResourceSystem;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class Water;
|
||||
|
||||
class ScreenshotManager
|
||||
{
|
||||
public:
|
||||
ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, osg::ref_ptr<osg::Group> sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water);
|
||||
|
||||
void screenshot(osg::Image* image, int w, int h);
|
||||
bool screenshot360(osg::Image* image);
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osgViewer::Viewer> mViewer;
|
||||
osg::ref_ptr<osg::Group> mRootNode;
|
||||
osg::ref_ptr<osg::Group> mSceneRoot;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
Water* mWater;
|
||||
|
||||
void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h);
|
||||
void makeCubemapScreenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd());
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1134,6 +1134,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana
|
|||
, mRainEntranceSpeed(1)
|
||||
, mRainMaxRaindrops(0)
|
||||
, mWindSpeed(0.f)
|
||||
, mBaseWindSpeed(0.f)
|
||||
, mEnabled(true)
|
||||
, mSunEnabled(true)
|
||||
, mWeatherAlpha(0.f)
|
||||
|
@ -1685,6 +1686,7 @@ void SkyManager::setWeather(const WeatherResult& weather)
|
|||
mRainMaxHeight = weather.mRainMaxHeight;
|
||||
mRainSpeed = weather.mRainSpeed;
|
||||
mWindSpeed = weather.mWindSpeed;
|
||||
mBaseWindSpeed = weather.mBaseWindSpeed;
|
||||
|
||||
if (mRainEffect != weather.mRainEffect)
|
||||
{
|
||||
|
@ -1853,6 +1855,13 @@ void SkyManager::setWeather(const WeatherResult& weather)
|
|||
fader->setAlpha(weather.mEffectFade);
|
||||
}
|
||||
|
||||
float SkyManager::getBaseWindSpeed() const
|
||||
{
|
||||
if (!mCreated) return 0.f;
|
||||
|
||||
return mBaseWindSpeed;
|
||||
}
|
||||
|
||||
void SkyManager::sunEnable()
|
||||
{
|
||||
if (!mCreated) return;
|
||||
|
|
|
@ -70,6 +70,7 @@ namespace MWRender
|
|||
float mDLFogOffset;
|
||||
|
||||
float mWindSpeed;
|
||||
float mBaseWindSpeed;
|
||||
float mCurrentWindSpeed;
|
||||
float mNextWindSpeed;
|
||||
|
||||
|
@ -181,6 +182,8 @@ namespace MWRender
|
|||
|
||||
void setRainIntensityUniform(osg::Uniform *uniform);
|
||||
|
||||
float getBaseWindSpeed() const;
|
||||
|
||||
private:
|
||||
void create();
|
||||
///< no need to call this, automatically done on first enable()
|
||||
|
@ -265,6 +268,7 @@ namespace MWRender
|
|||
float mRainEntranceSpeed;
|
||||
int mRainMaxRaindrops;
|
||||
float mWindSpeed;
|
||||
float mBaseWindSpeed;
|
||||
|
||||
bool mEnabled;
|
||||
bool mSunEnabled;
|
||||
|
|
|
@ -53,7 +53,9 @@ namespace MWRender
|
|||
Mask_PreCompile = (1<<18),
|
||||
|
||||
// Set on a camera's cull mask to enable the LightManager
|
||||
Mask_Lighting = (1<<19)
|
||||
Mask_Lighting = (1<<19),
|
||||
|
||||
Mask_Groundcover = (1<<20),
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ public:
|
|||
|
||||
mClipNodeTransform = new osg::Group;
|
||||
mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane));
|
||||
addChild(mClipNodeTransform);
|
||||
osg::Group::addChild(mClipNodeTransform);
|
||||
|
||||
mClipNode = new osg::ClipNode;
|
||||
|
||||
|
@ -240,11 +240,11 @@ public:
|
|||
setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
|
||||
setReferenceFrame(osg::Camera::RELATIVE_RF);
|
||||
setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water"));
|
||||
setName("RefractionCamera");
|
||||
osg::Camera::setName("RefractionCamera");
|
||||
setCullCallback(new InheritViewPointCallback);
|
||||
setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
|
||||
|
||||
setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting);
|
||||
setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting|Mask_Groundcover);
|
||||
setNodeMask(Mask_RenderToTexture);
|
||||
setViewport(0, 0, rttSize, rttSize);
|
||||
|
||||
|
@ -261,7 +261,7 @@ public:
|
|||
getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE);
|
||||
|
||||
mClipCullNode = new ClipCullNode;
|
||||
addChild(mClipCullNode);
|
||||
osg::Camera::addChild(mClipCullNode);
|
||||
|
||||
mRefractionTexture = new osg::Texture2D;
|
||||
mRefractionTexture->setTextureSize(rttSize, rttSize);
|
||||
|
@ -335,7 +335,7 @@ public:
|
|||
setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
|
||||
setReferenceFrame(osg::Camera::RELATIVE_RF);
|
||||
setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water"));
|
||||
setName("ReflectionCamera");
|
||||
osg::Camera::setName("ReflectionCamera");
|
||||
setCullCallback(new InheritViewPointCallback);
|
||||
|
||||
setInterior(isInterior);
|
||||
|
@ -364,7 +364,7 @@ public:
|
|||
getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON);
|
||||
|
||||
mClipCullNode = new ClipCullNode;
|
||||
addChild(mClipCullNode);
|
||||
osg::Camera::addChild(mClipCullNode);
|
||||
|
||||
SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet());
|
||||
}
|
||||
|
@ -372,12 +372,13 @@ public:
|
|||
void setInterior(bool isInterior)
|
||||
{
|
||||
int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water");
|
||||
reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail));
|
||||
reflectionDetail = std::min(5, std::max(isInterior ? 2 : 0, reflectionDetail));
|
||||
unsigned int extraMask = 0;
|
||||
if(reflectionDetail >= 1) extraMask |= Mask_Terrain;
|
||||
if(reflectionDetail >= 2) extraMask |= Mask_Static;
|
||||
if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object;
|
||||
if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor;
|
||||
if(reflectionDetail >= 5) extraMask |= Mask_Groundcover;
|
||||
setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,10 +27,15 @@
|
|||
#include <components/interpreter/opcodes.hpp>
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
|
||||
#include <components/esm/loadmgef.hpp>
|
||||
#include <components/esm/loadcrea.hpp>
|
||||
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/scriptmanager.hpp"
|
||||
|
@ -1583,7 +1588,15 @@ namespace MWScript
|
|||
msg << "Grid: " << cell->getCell()->getGridX() << " " << cell->getCell()->getGridY() << std::endl;
|
||||
osg::Vec3f pos (ptr.getRefData().getPosition().asVec3());
|
||||
msg << "Coordinates: " << pos.x() << " " << pos.y() << " " << pos.z() << std::endl;
|
||||
msg << "Model: " << ptr.getClass().getModel(ptr) << std::endl;
|
||||
auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
||||
std::string model = ::Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), vfs);
|
||||
msg << "Model: " << model << std::endl;
|
||||
if(!model.empty())
|
||||
{
|
||||
const std::string archive = vfs->getArchive(model);
|
||||
if(!archive.empty())
|
||||
msg << "(" << archive << ")" << std::endl;
|
||||
}
|
||||
if (!ptr.getClass().getScript(ptr).empty())
|
||||
msg << "Script: " << ptr.getClass().getScript(ptr) << std::endl;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
|
||||
namespace MWSound
|
||||
{
|
||||
// Extra play flags, not intended for caller use
|
||||
enum PlayModeEx
|
||||
{
|
||||
Play_2D = 0,
|
||||
Play_3D = 1 << 31,
|
||||
};
|
||||
|
||||
// For testing individual PlayMode flags
|
||||
inline int operator&(int a, PlayMode b) { return a & static_cast<int>(b); }
|
||||
inline int operator&(PlayMode a, PlayMode b) { return static_cast<int>(a) & static_cast<int>(b); }
|
||||
|
|
152
apps/openmw/mwsound/sound_buffer.cpp
Normal file
152
apps/openmw/mwsound/sound_buffer.cpp
Normal file
|
@ -0,0 +1,152 @@
|
|||
#include "sound_buffer.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct AudioParams
|
||||
{
|
||||
float mAudioDefaultMinDistance;
|
||||
float mAudioDefaultMaxDistance;
|
||||
float mAudioMinDistanceMult;
|
||||
float mAudioMaxDistanceMult;
|
||||
};
|
||||
|
||||
AudioParams makeAudioParams(const MWBase::World& world)
|
||||
{
|
||||
const auto& settings = world.getStore().get<ESM::GameSetting>();
|
||||
AudioParams params;
|
||||
params.mAudioDefaultMinDistance = settings.find("fAudioDefaultMinDistance")->mValue.getFloat();
|
||||
params.mAudioDefaultMaxDistance = settings.find("fAudioDefaultMaxDistance")->mValue.getFloat();
|
||||
params.mAudioMinDistanceMult = settings.find("fAudioMinDistanceMult")->mValue.getFloat();
|
||||
params.mAudioMaxDistanceMult = settings.find("fAudioMaxDistanceMult")->mValue.getFloat();
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
SoundBufferPool::SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output) :
|
||||
mVfs(&vfs),
|
||||
mOutput(&output),
|
||||
mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024),
|
||||
mBufferCacheMin(std::min(static_cast<std::size_t>(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) * 1024 * 1024, mBufferCacheMax))
|
||||
{
|
||||
}
|
||||
|
||||
SoundBufferPool::~SoundBufferPool()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
Sound_Buffer* SoundBufferPool::lookup(const std::string& soundId) const
|
||||
{
|
||||
const auto it = mBufferNameMap.find(soundId);
|
||||
if (it != mBufferNameMap.end())
|
||||
{
|
||||
Sound_Buffer* sfx = it->second;
|
||||
if (sfx->getHandle() != nullptr)
|
||||
return sfx;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Sound_Buffer* SoundBufferPool::load(const std::string& soundId)
|
||||
{
|
||||
if (mBufferNameMap.empty())
|
||||
{
|
||||
for (const ESM::Sound& sound : MWBase::Environment::get().getWorld()->getStore().get<ESM::Sound>())
|
||||
insertSound(Misc::StringUtils::lowerCase(sound.mId), sound);
|
||||
}
|
||||
|
||||
Sound_Buffer* sfx;
|
||||
const auto it = mBufferNameMap.find(soundId);
|
||||
if (it != mBufferNameMap.end())
|
||||
sfx = it->second;
|
||||
else
|
||||
{
|
||||
const ESM::Sound *sound = MWBase::Environment::get().getWorld()->getStore().get<ESM::Sound>().search(soundId);
|
||||
if (sound == nullptr)
|
||||
return {};
|
||||
sfx = insertSound(soundId, *sound);
|
||||
}
|
||||
|
||||
if (sfx->getHandle() == nullptr)
|
||||
{
|
||||
auto [handle, size] = mOutput->loadSound(sfx->getResourceName());
|
||||
if (handle == nullptr)
|
||||
return {};
|
||||
|
||||
sfx->mHandle = handle;
|
||||
|
||||
mBufferCacheSize += size;
|
||||
if (mBufferCacheSize > mBufferCacheMax)
|
||||
{
|
||||
unloadUnused();
|
||||
if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax)
|
||||
Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!";
|
||||
}
|
||||
mUnusedBuffers.push_front(sfx);
|
||||
}
|
||||
|
||||
return sfx;
|
||||
}
|
||||
|
||||
void SoundBufferPool::clear()
|
||||
{
|
||||
for (auto &sfx : mSoundBuffers)
|
||||
{
|
||||
if(sfx.mHandle)
|
||||
mOutput->unloadSound(sfx.mHandle);
|
||||
sfx.mHandle = nullptr;
|
||||
}
|
||||
mUnusedBuffers.clear();
|
||||
}
|
||||
|
||||
Sound_Buffer* SoundBufferPool::insertSound(const std::string& soundId, const ESM::Sound& sound)
|
||||
{
|
||||
static const AudioParams audioParams = makeAudioParams(*MWBase::Environment::get().getWorld());
|
||||
|
||||
float volume = static_cast<float>(std::pow(10.0, (sound.mData.mVolume / 255.0 * 3348.0 - 3348.0) / 2000.0));
|
||||
float min = sound.mData.mMinRange;
|
||||
float max = sound.mData.mMaxRange;
|
||||
if (min == 0 && max == 0)
|
||||
{
|
||||
min = audioParams.mAudioDefaultMinDistance;
|
||||
max = audioParams.mAudioDefaultMaxDistance;
|
||||
}
|
||||
|
||||
min *= audioParams.mAudioMinDistanceMult;
|
||||
max *= audioParams.mAudioMaxDistanceMult;
|
||||
min = std::max(min, 1.0f);
|
||||
max = std::max(min, max);
|
||||
|
||||
Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max);
|
||||
mVfs->normalizeFilename(sfx.mResourceName);
|
||||
|
||||
mBufferNameMap.emplace(soundId, &sfx);
|
||||
return &sfx;
|
||||
}
|
||||
|
||||
void SoundBufferPool::unloadUnused()
|
||||
{
|
||||
while (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMin)
|
||||
{
|
||||
Sound_Buffer* const unused = mUnusedBuffers.back();
|
||||
|
||||
mBufferCacheSize -= mOutput->unloadSound(unused->getHandle());
|
||||
unused->mHandle = nullptr;
|
||||
|
||||
mUnusedBuffers.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue