Add OpenMW commits up to 4 Feb 2021

# Conflicts:
#   apps/openmw/engine.cpp
#   apps/openmw/mwmechanics/npcstats.hpp
#   apps/openmw/mwrender/globalmap.cpp
pull/593/head
David Cernat 4 years ago
commit e1259fdc41

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

@ -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);
}
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(file);
}
StringsVector groundcover = variables["groundcover"].as<Files::EscapeStringVector>().toStdStringVector();
for (auto& file : groundcover)
{
engine.addContentFile(*it);
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,7 +2640,7 @@ 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()))
@ -2647,7 +2650,7 @@ void CharacterController::update(float duration, bool animationOnly)
}
// 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;
};
}

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

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

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

@ -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(bounds.center().x(), bounds.center().y());
osg::Vec2f center(mBounds.center().x(), mBounds.center().y());
osg::Quat cameraOrient (mAngle, osg::Vec3d(0,0,-1));
// 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)
{
}
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;
};
mScreenshotManager->screenshot(image, w, h);
}
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;

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

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

@ -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…
Cancel
Save