1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-10-09 08:26:37 +00:00

Merge remote-tracking branch 'remotes/origin/master' into stereo_friendly_water

This commit is contained in:
madsbuvi 2021-01-29 18:16:47 +01:00
commit 51a37ec6d2
95 changed files with 1506 additions and 496 deletions

View file

@ -44,7 +44,6 @@
Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound
Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures
Bug #5370: Opening an unlocked but trapped door uses the key
Bug #5379: Wandering NPCs falling through cantons
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
@ -127,6 +126,7 @@
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

View file

@ -802,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;
object->setEdited(Object::Override_Position);
ESM::Position position = object->getPosition();
object->setEdited (Object::Override_Position);
position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight;
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();
@ -848,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);
@ -878,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)
@ -897,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:
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()))
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]);
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()))
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);
dropInstance(objectTag->mObject, smallestDropHeight);
objectTag->mObject->apply(macro);
}
}
break;
case TerrainSep:
case CollisionSep:
{
int counter = 0;
for(osg::ref_ptr<TagBase> tag: selection)
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
{
dropInstance(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]);
objectTag->mObject->apply (macro);
counter++;
}
}
break;
}
}
CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget)
CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget)
: mWorldspaceWidget(worldspacewidget)
{
std::vector<osg::ref_ptr<TagBase> > selection = mWorldspaceWidget->getSelection (Mask_Reference);
@ -969,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;

View file

@ -28,10 +28,13 @@ namespace CSVRender
enum DropMode
{
Collision,
Terrain,
CollisionSep,
TerrainSep
Separate = 0b1,
Collision = 0b10,
Terrain = 0b100,
CollisionSep = Collision | Separate,
TerrainSep = Terrain | Separate,
};
CSVWidget::SceneToolMode *mSubMode;
@ -53,8 +56,8 @@ namespace CSVRender
osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos);
osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart);
void handleSelectDrag(const QPoint& pos);
void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight);
float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight);
void dropInstance(CSVRender::Object* object, float dropHeight);
float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight);
public:
@ -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:

View file

@ -21,7 +21,7 @@ add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
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

View file

@ -177,6 +177,8 @@ namespace
~ScopedProfile()
{
if (!mStats.collectStats("engine"))
return;
const osg::Timer_t end = mTimer.tick();
const UserStats& stats = UserStatsValue<sType>::sValue;
@ -460,6 +462,11 @@ void OMW::Engine::addContentFile(const std::string& file)
mContentFiles.push_back(file);
}
void OMW::Engine::addGroundcoverFile(const std::string& file)
{
mGroundcoverFiles.emplace_back(file);
}
void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
{
mSkipMenu = skipMenu;
@ -721,7 +728,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
// Create the world
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName,
mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName,
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
mEnvironment.getWorld()->setupPlayer();
@ -863,16 +870,29 @@ void OMW::Engine::go()
prepareEngine (settings);
std::ofstream stats;
if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE"))
{
stats.open(path, std::ios_base::out);
if (stats.is_open())
Log(Debug::Info) << "Stats will be written to: " << path;
else
Log(Debug::Warning) << "Failed to open file for stats: " << path;
}
// Setup profiler
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler;
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler(stats.is_open());
initStatsHandler(*statshandler);
mViewer->addEventHandler(statshandler);
osg::ref_ptr<Resource::StatsHandler> resourceshandler = new Resource::StatsHandler;
osg::ref_ptr<Resource::StatsHandler> resourceshandler = new Resource::StatsHandler(stats.is_open());
mViewer->addEventHandler(resourceshandler);
if (stats.is_open())
Resource::CollectStatistics(mViewer);
// Start the game
if (!mSaveGameFile.empty())
{
@ -897,14 +917,6 @@ void OMW::Engine::go()
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
}
std::ofstream stats;
if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE"))
{
stats.open(path, std::ios_base::out);
if (!stats)
Log(Debug::Warning) << "Failed to open file for stats: " << path;
}
// Start the main rendering loop
osg::Timer frameTimer;
double simulationTime = 0.0;

View file

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

View file

@ -62,6 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
("content", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
("groundcover", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon")
("no-sound", bpo::value<bool>()->implicit_value(true)
->default_value(false), "disable all sounds")
@ -190,11 +193,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
return false;
}
StringsVector::const_iterator it(content.begin());
StringsVector::const_iterator end(content.end());
for (; it != end; ++it)
for (auto& file : content)
{
engine.addContentFile(*it);
engine.addContentFile(file);
}
StringsVector groundcover = variables["groundcover"].as<Files::EscapeStringVector>().toStdStringVector();
for (auto& file : groundcover)
{
engine.addGroundcoverFile(file);
}
// startup-settings

View file

@ -38,10 +38,10 @@ namespace MWClass
}
}
void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const
void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
if(!model.empty())
physics.addObject(ptr, model, rotation);
physics.addObject(ptr, model);
}
std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const

View file

@ -17,7 +17,7 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override;
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -17,12 +17,16 @@
namespace MWClass
{
Actor::Actor() {}
Actor::~Actor() {}
void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const
{
MWBase::Environment::get().getWorld()->adjustPosition(ptr, force);
}
void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const
void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
if (!model.empty())
{

View file

@ -15,16 +15,16 @@ namespace MWClass
{
protected:
Actor() = default;
Actor();
public:
~Actor() override = default;
virtual ~Actor();
void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override;
///< Adjust position to stand on ground. Must be called post model load
/// @param force do this even if the ptr is flying
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override;
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
bool useAnim() const override;
@ -46,8 +46,8 @@ namespace MWClass
float getCurrentSpeed(const MWWorld::Ptr& ptr) const override;
// not implemented
Actor(const Actor&) = delete;
Actor& operator= (const Actor&) = delete;
Actor(const Actor&);
Actor& operator= (const Actor&);
};
}

View file

@ -26,6 +26,11 @@ namespace MWClass
}
}
void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Apparatus> *ref = ptr.get<ESM::Apparatus>();

View file

@ -17,6 +17,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -34,6 +34,11 @@ namespace MWClass
}
}
void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Armor> *ref = ptr.get<ESM::Armor>();

View file

@ -16,6 +16,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -22,6 +22,10 @@ namespace MWClass
}
}
void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const
{
}
std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const
{
return std::string();

View file

@ -15,6 +15,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -31,6 +31,11 @@ namespace MWClass
}
}
void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Book::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Book> *ref = ptr.get<ESM::Book>();

View file

@ -14,6 +14,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -29,6 +29,11 @@ namespace MWClass
}
}
void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Clothing> *ref = ptr.get<ESM::Clothing>();

View file

@ -14,6 +14,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -111,10 +111,10 @@ namespace MWClass
}
}
void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const
void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
if(!model.empty())
physics.addObject(ptr, model, rotation);
physics.addObject(ptr, model);
}
std::string Container::getModel(const MWWorld::ConstPtr &ptr) const

View file

@ -44,7 +44,7 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override;
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -62,10 +62,10 @@ namespace MWClass
}
}
void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const
void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
if(!model.empty())
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door);
physics.addObject(ptr, model, MWPhysics::CollisionType_Door);
// Resume the door's opening/closing animation if it wasn't finished
if (ptr.getRefData().getCustomData())

View file

@ -18,7 +18,7 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override;
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
bool isDoor() const override;

View file

@ -28,6 +28,11 @@ namespace MWClass
}
}
void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Ingredient> *ref = ptr.get<ESM::Ingredient>();

View file

@ -14,6 +14,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -33,7 +33,7 @@ namespace MWClass
renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault));
}
void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const
void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
MWWorld::LiveCellRef<ESM::Light> *ref =
ptr.get<ESM::Light>();
@ -41,7 +41,7 @@ namespace MWClass
// TODO: add option somewhere to enable collision for placeable objects
if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0)
physics.addObject(ptr, model, rotation);
physics.addObject(ptr, model);
if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault))
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0,

View file

@ -14,7 +14,7 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override;
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
bool useAnim() const override;

View file

@ -28,6 +28,11 @@ namespace MWClass
}
}
void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Lockpick> *ref = ptr.get<ESM::Lockpick>();

View file

@ -14,6 +14,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -37,6 +37,11 @@ namespace MWClass
}
}
void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Miscellaneous> *ref = ptr.get<ESM::Miscellaneous>();

View file

@ -14,6 +14,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -30,6 +30,11 @@ namespace MWClass
}
}
void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Potion> *ref = ptr.get<ESM::Potion>();

View file

@ -14,6 +14,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -28,6 +28,11 @@ namespace MWClass
}
}
void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Probe> *ref = ptr.get<ESM::Probe>();

View file

@ -14,6 +14,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -25,6 +25,11 @@ namespace MWClass
}
}
void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Repair> *ref = ptr.get<ESM::Repair>();

View file

@ -14,6 +14,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -23,10 +23,10 @@ namespace MWClass
}
}
void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const
void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
if(!model.empty())
physics.addObject(ptr, model, rotation);
physics.addObject(ptr, model);
}
std::string Static::getModel(const MWWorld::ConstPtr &ptr) const
@ -63,9 +63,4 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell);
}
bool Static::isStatic() const
{
return true;
}
}

View file

@ -14,7 +14,7 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override;
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.
@ -25,8 +25,6 @@ namespace MWClass
static void registerSelf();
std::string getModel(const MWWorld::ConstPtr &ptr) const override;
bool isStatic() const override;
};
}

View file

@ -34,6 +34,11 @@ namespace MWClass
}
}
void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
{
// TODO: add option somewhere to enable collision for placeable objects
}
std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const
{
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();

View file

@ -15,6 +15,8 @@ namespace MWClass
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering
void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override;
std::string getName (const MWWorld::ConstPtr& ptr) const override;
///< \return name or ID; can return an empty string.

View file

@ -269,7 +269,7 @@ namespace MWGui
mWaterTextureSize->setIndexSelected(2);
int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water");
waterReflectionDetail = std::min(4, std::max(0, waterReflectionDetail));
waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail));
mWaterReflectionDetail->setIndexSelected(waterReflectionDetail);
mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video"));
@ -353,7 +353,7 @@ namespace MWGui
void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos)
{
unsigned int level = std::min((unsigned int)4, (unsigned int)pos);
unsigned int level = std::min((unsigned int)5, (unsigned int)pos);
Settings::Manager::setInt("reflection detail", "Water", level);
apply();
}

View file

@ -309,7 +309,7 @@ namespace MWMechanics
if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance))
mPath.pop_front();
if (mPath.size() == 1 && (mPath.front() - position).length2() < destinationTolerance * destinationTolerance)
if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance)
mPath.pop_front();
}

View file

@ -67,7 +67,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
updateScale();
if(!mRotationallyInvariant)
setRotation(mPtr.getRefData().getBaseNode()->getAttitude());
updateRotation();
updatePosition();
addCollisionMask(getCollisionMask());
@ -197,10 +197,10 @@ osg::Vec3f Actor::getPreviousPosition() const
return mPreviousPosition;
}
void Actor::setRotation(osg::Quat quat)
void Actor::updateRotation ()
{
std::scoped_lock lock(mPositionMutex);
mRotation = quat;
mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
}
bool Actor::isRotationallyInvariant() const

View file

@ -49,7 +49,7 @@ namespace MWPhysics
void enableCollisionBody(bool collision);
void updateScale();
void setRotation(osg::Quat quat);
void updateRotation();
/**
* Return true if the collision shape looks the same no matter how its Z rotated.

View file

@ -540,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));

View file

@ -14,7 +14,7 @@
namespace MWPhysics
{
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler)
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler)
: mShapeInstance(shapeInstance)
, mSolid(true)
, mTaskScheduler(scheduler)
@ -27,7 +27,7 @@ namespace MWPhysics
mCollisionObject->setUserPointer(this);
setScale(ptr.getCellRef().getScale());
setRotation(rotation);
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3()));
commitPositionChange();
@ -51,10 +51,10 @@ namespace MWPhysics
mScaleUpdatePending = true;
}
void Object::setRotation(osg::Quat quat)
void Object::setRotation(const btQuaternion& quat)
{
std::unique_lock<std::mutex> lock(mPositionMutex);
mLocalTransform.setRotation(Misc::Convert::toBullet(quat));
mLocalTransform.setRotation(quat);
mTransformUpdatePending = true;
}
@ -116,9 +116,6 @@ namespace MWPhysics
if (mShapeInstance->mAnimatedShapes.empty())
return false;
if (mPtr.getRefData().getBaseNode() == nullptr)
return true;
assert (mShapeInstance->getCollisionShape()->isCompound());
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());

View file

@ -26,12 +26,12 @@ namespace MWPhysics
class Object final : public PtrHolder
{
public:
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler);
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler);
~Object() override;
const Resource::BulletShapeInstance* getShapeInstance() const;
void setScale(float scale);
void setRotation(osg::Quat quat);
void setRotation(const btQuaternion& quat);
void setOrigin(const btVector3& vec);
void commitPositionChange();
btCollisionObject* getCollisionObject();

View file

@ -456,20 +456,20 @@ namespace MWPhysics
return heightField->second.get();
}
void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType)
void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType)
{
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
if (!shapeInstance || !shapeInstance->getCollisionShape())
return;
auto obj = std::make_shared<Object>(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get());
auto obj = std::make_shared<Object>(ptr, shapeInstance, collisionType, mTaskScheduler.get());
mObjects.emplace(ptr, obj);
if (obj->isAnimated())
mAnimatedObjects.insert(obj.get());
}
void PhysicsSystem::remove(const MWWorld::Ptr &ptr, bool keepObject)
void PhysicsSystem::remove(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end())
@ -479,7 +479,6 @@ namespace MWPhysics
mAnimatedObjects.erase(found->second.get());
if (!keepObject)
mObjects.erase(found);
}
@ -622,12 +621,12 @@ namespace MWPhysics
mTaskScheduler->updateSingleAabb(foundProjectile->second);
}
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate)
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end())
{
found->second->setRotation(rotate);
found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
mTaskScheduler->updateSingleAabb(found->second);
return;
}
@ -636,7 +635,7 @@ namespace MWPhysics
{
if (!foundActor->second->isRotationallyInvariant())
{
foundActor->second->setRotation(rotate);
foundActor->second->updateRotation();
mTaskScheduler->updateSingleAabb(foundActor->second);
}
return;

View file

@ -121,7 +121,7 @@ namespace MWPhysics
void setWaterHeight(float height);
void disableWater();
void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World);
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, float radius, bool canTraverseWater);
@ -138,10 +138,10 @@ namespace MWPhysics
Projectile* getProjectile(int projectileId) const;
// Object or Actor
void remove (const MWWorld::Ptr& ptr, bool keepObject = false);
void remove (const MWWorld::Ptr& ptr);
void updateScale (const MWWorld::Ptr& ptr);
void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate);
void updateRotation (const MWWorld::Ptr& ptr);
void updatePosition (const MWWorld::Ptr& ptr);
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);

View file

@ -55,7 +55,9 @@ namespace MWPhysics
}
default:
{
mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld);
auto* target = static_cast<PtrHolder*>(result.m_hitCollisionObject->getUserPointer());
auto ptr = target ? target->getPtr() : MWWorld::Ptr();
mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld);
break;
}
}

View file

@ -0,0 +1,282 @@
#include "groundcover.hpp"
#include <osg/Geometry>
#include <osg/VertexAttribDivisor>
#include <components/esm/esmreader.hpp>
#include "apps/openmw/mwworld/esmstore.hpp"
#include "apps/openmw/mwbase/environment.hpp"
#include "apps/openmw/mwbase/world.hpp"
#include "vismask.hpp"
namespace MWRender
{
std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store)
{
switch (type)
{
case ESM::REC_STAT:
return store.get<ESM::Static>().searchStatic(id)->mModel;
default:
return std::string();
}
}
void GroundcoverUpdater::setWindSpeed(float windSpeed)
{
mWindSpeed = windSpeed;
}
void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos)
{
mPlayerPos = playerPos;
}
void GroundcoverUpdater::setDefaults(osg::StateSet *stateset)
{
osg::ref_ptr<osg::Uniform> windUniform = new osg::Uniform("windSpeed", 0.0f);
stateset->addUniform(windUniform.get());
osg::ref_ptr<osg::Uniform> playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f));
stateset->addUniform(playerPosUniform.get());
}
void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
{
osg::ref_ptr<osg::Uniform> windUniform = stateset->getUniform("windSpeed");
if (windUniform != nullptr)
windUniform->set(mWindSpeed);
osg::ref_ptr<osg::Uniform> playerPosUniform = stateset->getUniform("playerPos");
if (playerPosUniform != nullptr)
playerPosUniform->set(mPlayerPos);
}
class InstancingVisitor : public osg::NodeVisitor
{
public:
InstancingVisitor(std::vector<Groundcover::GroundcoverEntry>& instances, osg::Vec3f& chunkPosition)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mInstances(instances)
, mChunkPosition(chunkPosition)
{
}
void apply(osg::Node& node) override
{
osg::ref_ptr<osg::StateSet> ss = node.getStateSet();
if (ss != nullptr)
{
ss->removeAttribute(osg::StateAttribute::MATERIAL);
removeAlpha(ss);
}
traverse(node);
}
void apply(osg::Geometry& geom) override
{
for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i)
{
geom.getPrimitiveSet(i)->setNumInstances(mInstances.size());
}
osg::ref_ptr<osg::Vec4Array> transforms = new osg::Vec4Array(mInstances.size());
osg::BoundingBox box;
float radius = geom.getBoundingBox().radius();
for (unsigned int i = 0; i < transforms->getNumElements(); i++)
{
osg::Vec3f pos(mInstances[i].mPos.asVec3());
osg::Vec3f relativePos = pos - mChunkPosition;
(*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale);
// Use an additional margin due to groundcover animation
float instanceRadius = radius * mInstances[i].mScale * 1.1f;
osg::BoundingSphere instanceBounds(relativePos, instanceRadius);
box.expandBy(instanceBounds);
}
geom.setInitialBound(box);
osg::ref_ptr<osg::Vec3Array> rotations = new osg::Vec3Array(mInstances.size());
for (unsigned int i = 0; i < rotations->getNumElements(); i++)
{
(*rotations)[i] = mInstances[i].mPos.asRotationVec3();
}
// Display lists do not support instancing in OSG 3.4
geom.setUseDisplayList(false);
geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX);
geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX);
osg::ref_ptr<osg::StateSet> ss = geom.getOrCreateStateSet();
ss->setAttribute(new osg::VertexAttribDivisor(6, 1));
ss->setAttribute(new osg::VertexAttribDivisor(7, 1));
ss->removeAttribute(osg::StateAttribute::MATERIAL);
removeAlpha(ss);
traverse(geom);
}
private:
std::vector<Groundcover::GroundcoverEntry> mInstances;
osg::Vec3f mChunkPosition;
void removeAlpha(osg::StateSet* stateset)
{
// MGE uses default alpha settings for groundcover, so we can not rely on alpha properties
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
stateset->removeMode(GL_ALPHA_TEST);
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
stateset->removeMode(GL_BLEND);
stateset->setRenderBinToInherit();
}
};
class DensityCalculator
{
public:
DensityCalculator(float density)
: mDensity(density)
{
}
bool isInstanceEnabled()
{
if (mDensity >= 1.f) return true;
mCurrentGroundcover += mDensity;
if (mCurrentGroundcover < 1.f) return false;
mCurrentGroundcover -= 1.f;
return true;
}
void reset() { mCurrentGroundcover = 0.f; }
private:
float mCurrentGroundcover = 0.f;
float mDensity = 0.f;
};
inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound)
{
osg::Vec2f size = maxBound - minBound;
if (size.x() >=1 && size.y() >=1) return true;
osg::Vec3f pos = ref.mPos.asVec3();
osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE;
if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y())
|| (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y()))
return false;
return true;
}
osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
{
ChunkId id = std::make_tuple(center, size, activeGrid);
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
if (obj)
return obj->asNode();
else
{
InstanceMap instances;
collectInstances(instances, size, center);
osg::ref_ptr<osg::Node> node = createChunk(instances, center);
mCache->addEntryToObjectCache(id, node.get());
return node;
}
}
Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density)
: GenericResourceManager<ChunkId>(nullptr)
, mSceneManager(sceneManager)
, mDensity(density)
{
}
void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center)
{
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f));
osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f));
DensityCalculator calculator(mDensity);
std::vector<ESM::ESMReader> esm;
osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
{
for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY)
{
const ESM::Cell* cell = store.get<ESM::Cell>().searchStatic(cellX, cellY);
if (!cell) continue;
calculator.reset();
for (size_t i=0; i<cell->mContextList.size(); ++i)
{
unsigned int index = cell->mContextList.at(i).index;
if (esm.size() <= index)
esm.resize(index+1);
cell->restore(esm[index], i);
ESM::CellRef ref;
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
bool deleted = false;
while(cell->getNextRef(esm[index], ref, deleted))
{
if (deleted) continue;
if (!ref.mRefNum.fromGroundcoverFile()) continue;
if (!calculator.isInstanceEnabled()) continue;
if (!isInChunkBorders(ref, minBound, maxBound)) continue;
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
int type = store.findStatic(ref.mRefID);
std::string model = getGroundcoverModel(type, ref.mRefID, store);
if (model.empty()) continue;
model = "meshes/" + model;
instances[model].emplace_back(ref, model);
}
}
}
}
}
osg::ref_ptr<osg::Node> Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center)
{
osg::ref_ptr<osg::Group> group = new osg::Group;
osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE;
for (auto& pair : instances)
{
const osg::Node* temp = mSceneManager->getTemplate(pair.first);
osg::ref_ptr<osg::Node> node = static_cast<osg::Node*>(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES)));
// Keep link to original mesh to keep it in cache
group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp));
InstancingVisitor visitor(pair.second, worldCenter);
node->accept(visitor);
group->addChild(node);
}
group->getBound();
group->setNodeMask(Mask_Groundcover);
mSceneManager->recreateShaders(group, "groundcover", false, true);
return group;
}
unsigned int Groundcover::getNodeMask()
{
return Mask_Groundcover;
}
void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize());
}
}

View file

@ -0,0 +1,69 @@
#ifndef OPENMW_MWRENDER_GROUNDCOVER_H
#define OPENMW_MWRENDER_GROUNDCOVER_H
#include <components/terrain/quadtreeworld.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/statesetupdater.hpp>
#include <components/esm/loadcell.hpp>
namespace MWRender
{
class GroundcoverUpdater : public SceneUtil::StateSetUpdater
{
public:
GroundcoverUpdater()
: mWindSpeed(0.f)
, mPlayerPos(osg::Vec3f())
{
}
void setWindSpeed(float windSpeed);
void setPlayerPos(osg::Vec3f playerPos);
protected:
void setDefaults(osg::StateSet *stateset) override;
void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override;
private:
float mWindSpeed;
osg::Vec3f mPlayerPos;
};
typedef std::tuple<osg::Vec2f, float, bool> ChunkId; // Center, Size, ActiveGrid
class Groundcover : public Resource::GenericResourceManager<ChunkId>, public Terrain::QuadTreeWorld::ChunkManager
{
public:
Groundcover(Resource::SceneManager* sceneManager, float density);
~Groundcover() = default;
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
unsigned int getNodeMask() override;
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
struct GroundcoverEntry
{
ESM::Position mPos;
float mScale;
std::string mModel;
GroundcoverEntry(const ESM::CellRef& ref, const std::string& model)
{
mPos = ref.mPos;
mScale = ref.mScale;
mModel = model;
}
};
private:
Resource::SceneManager* mSceneManager;
float mDensity;
typedef std::map<std::string, std::vector<GroundcoverEntry>> InstanceMap;
osg::ref_ptr<osg::Node> createChunk(InstanceMap& instances, const osg::Vec2f& center);
void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center);
};
}
#endif

View file

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

View file

@ -3,6 +3,7 @@
#include <limits>
#include <cstdlib>
#include <osg/AlphaFunc>
#include <osg/Light>
#include <osg/LightModel>
#include <osg/Fog>
@ -68,6 +69,7 @@
#include "fogmanager.hpp"
#include "objectpaging.hpp"
#include "screenshotmanager.hpp"
#include "groundcover.hpp"
namespace MWRender
{
@ -243,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);
@ -269,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"))
{
@ -277,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"))
{
@ -292,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));
@ -508,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)
{
@ -517,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);
}
@ -527,6 +573,8 @@ namespace MWRender
if (!enable)
mWater->setCullCallback(nullptr);
mTerrain->enable(enable);
if (mGroundcoverWorld)
mGroundcoverWorld->enable(enable);
}
void RenderingManager::setSkyEnabled(bool enabled)
@ -612,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();
@ -805,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)
@ -964,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()
@ -1158,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)
{

View file

@ -70,7 +70,7 @@ namespace DetourNavigator
namespace MWRender
{
class GroundcoverUpdater;
class StateUpdater;
class EffectManager;
@ -88,6 +88,7 @@ namespace MWRender
class ActorsPaths;
class RecastMesh;
class ObjectPaging;
class Groundcover;
class RenderingManager : public MWRender::RenderingInterface
{
@ -261,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;
@ -275,8 +278,10 @@ 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;

View file

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

View file

@ -330,12 +330,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;
mNodeMask = Mask_Scene | Mask_Sky | Mask_Lighting | extraMask;
}

View file

@ -687,7 +687,11 @@ namespace MWWorld
case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break;
case ESM::REC_PROB: mProbes.load(ref, deleted, store); break;
case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break;
case ESM::REC_STAT: mStatics.load(ref, deleted, store); break;
case ESM::REC_STAT:
{
if (ref.mRefNum.fromGroundcoverFile()) return;
mStatics.load(ref, deleted, store); break;
}
case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break;
case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break;

View file

@ -18,20 +18,9 @@ namespace MWWorld
if (ptr.getRefData().getBaseNode())
{
ptr.getRefData().setBaseNode(nullptr);
}
mObjects.push_back (ptr);
return true;
}
};
struct ListObjectsVisitor
{
std::vector<MWWorld::Ptr> mObjects;
bool operator() (MWWorld::Ptr ptr)
{
mObjects.push_back (ptr);
return true;
}
};

View file

@ -25,12 +25,16 @@ namespace MWWorld
{
std::map<std::string, std::shared_ptr<Class> > Class::sClasses;
Class::Class() {}
Class::~Class() {}
void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const
{
}
void Class::insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const
void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const
{
}

View file

@ -6,7 +6,6 @@
#include <string>
#include <vector>
#include <osg/Quat>
#include <osg/Vec4f>
#include "ptr.hpp"
@ -58,9 +57,13 @@ namespace MWWorld
std::string mTypeName;
// not implemented
Class (const Class&);
Class& operator= (const Class&);
protected:
Class() = default;
Class();
std::shared_ptr<Action> defaultItemActivate(const Ptr &ptr, const Ptr &actor) const;
///< Generate default action for activating inventory items
@ -69,16 +72,14 @@ namespace MWWorld
public:
virtual ~Class() = default;
Class (const Class&) = delete;
Class& operator= (const Class&) = delete;
virtual ~Class();
const std::string& getTypeName() const {
return mTypeName;
}
virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const;
virtual void insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const;
virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const;
///< Add reference into a cell for rendering (default implementation: don't render anything).
virtual std::string getName (const ConstPtr& ptr) const = 0;
@ -318,10 +319,6 @@ namespace MWWorld
return false;
}
virtual bool isStatic() const {
return false;
}
virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const;
virtual bool canFly(const MWWorld::ConstPtr& ptr) const;
virtual bool canSwim(const MWWorld::ConstPtr& ptr) const;

View file

@ -75,20 +75,18 @@ namespace
* osg::Quat(xr, osg::Vec3(-1, 0, 0));
}
osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order)
void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order)
{
const auto pos = ptr.getRefData().getPosition();
if (!ptr.getRefData().getBaseNode())
return;
const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos)
: (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : makeObjectOsgQuat(pos));
return rot;
}
void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, osg::Quat rotation)
{
if (ptr.getRefData().getBaseNode())
rendering.rotateObject(ptr, rotation);
rendering.rotateObject(ptr,
ptr.getClass().isActor()
? makeActorOsgQuat(ptr.getRefData().getPosition())
: (order == RotationOrder::inverse
? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition())
: makeObjectOsgQuat(ptr.getRefData().getPosition()))
);
}
std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs)
@ -105,7 +103,7 @@ namespace
}
void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
MWRender::RenderingManager& rendering, std::set<ESM::RefNum>& pagedRefs, bool onlyPhysics)
MWRender::RenderingManager& rendering, std::set<ESM::RefNum>& pagedRefs)
{
if (ptr.getRefData().getBaseNode() || physics.getActor(ptr))
{
@ -113,17 +111,17 @@ namespace
return;
}
std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS());
const auto rotation = makeNodeRotation(ptr, RotationOrder::direct);
if (!onlyPhysics)
{
bool useAnim = ptr.getClass().useAnim();
std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS());
const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end())
ptr.getClass().insertObjectRendering(ptr, model, rendering);
else
ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode
setNodeRotation(ptr, rendering, RotationOrder::direct);
setNodeRotation(ptr, rendering, rotation);
ptr.getClass().insertObject (ptr, model, physics);
if (useAnim)
MWBase::Environment::get().getMechanicsManager()->add(ptr);
@ -134,9 +132,6 @@ namespace
// Restore effect particles
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
}
if (!physics.getObject(ptr))
ptr.getClass().insertObject (ptr, model, rotation, physics);
}
void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator)
{
@ -206,12 +201,11 @@ namespace
{
MWWorld::CellStore& mCell;
Loading::Listener& mLoadingListener;
bool mOnlyStatics;
bool mTest;
std::vector<MWWorld::Ptr> mToInsert;
InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test);
InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test);
bool operator() (const MWWorld::Ptr& ptr);
@ -219,8 +213,8 @@ namespace
void insert(AddObject&& addObject);
};
InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test)
: mCell (cell), mLoadingListener (loadingListener), mOnlyStatics(onlyStatics), mTest(test)
InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test)
: mCell (cell), mLoadingListener (loadingListener), mTest(test)
{}
bool InsertVisitor::operator() (const MWWorld::Ptr& ptr)
@ -236,7 +230,7 @@ namespace
{
for (MWWorld::Ptr& ptr : mToInsert)
{
if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && ((mOnlyStatics && ptr.getClass().isStatic()) || !mOnlyStatics))
if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled())
{
try
{
@ -269,16 +263,6 @@ namespace
return std::abs(cellPosition.first) + std::abs(cellPosition.second);
}
bool isCellInCollection(int x, int y, MWWorld::Scene::CellStoreCollection& collection)
{
for (auto *cell : collection)
{
assert(cell->getCell()->isExterior());
if (x == cell->getCell()->getGridX() && y == cell->getCell()->getGridY())
return true;
}
return false;
}
}
@ -292,7 +276,7 @@ namespace MWWorld
{
if (!ptr.getRefData().getBaseNode()) return;
ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering);
setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct));
setNodeRotation(ptr, mRendering, RotationOrder::direct);
reloadTerrain();
}
}
@ -308,9 +292,8 @@ namespace MWWorld
void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order)
{
const auto rot = makeNodeRotation(ptr, order);
setNodeRotation(ptr, mRendering, rot);
mPhysics->updateRotation(ptr, rot);
setNodeRotation(ptr, mRendering, order);
mPhysics->updateRotation(ptr);
}
void Scene::updateObjectScale(const Ptr &ptr)
@ -330,41 +313,15 @@ namespace MWWorld
mRendering.update (duration, paused);
}
void Scene::unloadInactiveCell (CellStore* cell, bool test)
void Scene::unloadCell (CellStoreCollection::iterator iter, bool test)
{
assert(mActiveCells.find(cell) == mActiveCells.end());
assert(mInactiveCells.find(cell) != mInactiveCells.end());
if (!test)
Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription();
ListObjectsVisitor visitor;
cell->forEach(visitor);
for (const auto& ptr : visitor.mObjects)
mPhysics->remove(ptr);
if (cell->getCell()->isExterior())
{
const auto cellX = cell->getCell()->getGridX();
const auto cellY = cell->getCell()->getGridY();
mPhysics->removeHeightField(cellX, cellY);
}
mInactiveCells.erase(cell);
}
void Scene::deactivateCell(CellStore* cell, bool test)
{
assert(mInactiveCells.find(cell) != mInactiveCells.end());
if (mActiveCells.find(cell) == mActiveCells.end())
return;
if (!test)
Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription();
Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription();
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
ListAndResetObjectsVisitor visitor;
cell->forEach(visitor);
(*iter)->forEach(visitor);
const auto world = MWBase::Environment::get().getWorld();
for (const auto& ptr : visitor.mObjects)
{
@ -375,57 +332,75 @@ namespace MWWorld
navigator->removeAgent(world->getPathfindingHalfExtents(ptr));
mRendering.removeActorPath(ptr);
}
mPhysics->remove(ptr, ptr.getClass().isStatic());
mPhysics->remove(ptr);
}
const auto cellX = cell->getCell()->getGridX();
const auto cellY = cell->getCell()->getGridY();
const auto cellX = (*iter)->getCell()->getGridX();
const auto cellY = (*iter)->getCell()->getGridY();
if (cell->getCell()->isExterior())
if ((*iter)->getCell()->isExterior())
{
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
navigator->removeObject(DetourNavigator::ObjectId(heightField));
mPhysics->removeHeightField(cellX, cellY);
}
if (cell->getCell()->hasWater())
if ((*iter)->getCell()->hasWater())
navigator->removeWater(osg::Vec2i(cellX, cellY));
if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell->getCell()))
if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*(*iter)->getCell()))
navigator->removePathgrid(*pathgrid);
const auto player = world->getPlayerPtr();
navigator->update(player.getRefData().getPosition().asVec3());
MWBase::Environment::get().getMechanicsManager()->drop (cell);
MWBase::Environment::get().getMechanicsManager()->drop (*iter);
mRendering.removeCell(cell);
MWBase::Environment::get().getWindowManager()->removeCell(cell);
mRendering.removeCell(*iter);
MWBase::Environment::get().getWindowManager()->removeCell(*iter);
MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (cell);
MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter);
MWBase::Environment::get().getSoundManager()->stopSound (cell);
mActiveCells.erase(cell);
MWBase::Environment::get().getSoundManager()->stopSound (*iter);
mActiveCells.erase(*iter);
}
void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test)
void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test)
{
assert(mActiveCells.find(cell) == mActiveCells.end());
assert(mInactiveCells.find(cell) != mInactiveCells.end());
mActiveCells.insert(cell);
std::pair<CellStoreCollection::iterator, bool> result = mActiveCells.insert(cell);
if(result.second)
{
if (test)
Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription();
else
Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription();
float verts = ESM::Land::LAND_SIZE;
float worldsize = ESM::Land::REAL_SIZE;
const auto world = MWBase::Environment::get().getWorld();
const auto navigator = world->getNavigator();
const int cellX = cell->getCell()->getGridX();
const int cellY = cell->getCell()->getGridY();
// Load terrain physics first...
if (!test && cell->getCell()->isExterior())
{
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
if (data)
{
mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get());
}
else
{
static std::vector<float> defaultHeight;
defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT);
mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get());
}
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(),
heightField->getCollisionObject()->getWorldTransform());
@ -441,7 +416,8 @@ namespace MWWorld
if (respawn)
cell->respawn();
insertCell (*cell, loadingListener, false, test);
// ... then references. This is important for adjustPosition to work correctly.
insertCell (*cell, loadingListener, test);
mRendering.addCell(cell);
if (!test)
@ -474,58 +450,22 @@ namespace MWWorld
navigator->update(player.getRefData().getPosition().asVec3());
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
{
mRendering.configureAmbient(cell->getCell());
}
}
}
mPreloader->notifyLoaded(cell);
}
void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test)
{
assert(mActiveCells.find(cell) == mActiveCells.end());
assert(mInactiveCells.find(cell) == mInactiveCells.end());
mInactiveCells.insert(cell);
if (test)
Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription();
else
Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription();
if (!test && cell->getCell()->isExterior())
{
float verts = ESM::Land::LAND_SIZE;
float worldsize = ESM::Land::REAL_SIZE;
const int cellX = cell->getCell()->getGridX();
const int cellY = cell->getCell()->getGridY();
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
if (data)
{
mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get());
}
else
{
static std::vector<float> defaultHeight;
defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT);
mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get());
}
}
insertCell (*cell, loadingListener, true, test);
}
void Scene::clear()
{
for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); )
{
auto* cell = *iter++;
deactivateCell(cell);
unloadInactiveCell (cell);
}
CellStoreCollection::iterator active = mActiveCells.begin();
while (active!=mActiveCells.end())
unloadCell (active++);
assert(mActiveCells.empty());
assert(mInactiveCells.empty());
mCurrentCell = nullptr;
mPreloader->clear();
@ -568,24 +508,20 @@ namespace MWWorld
void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent)
{
for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); )
CellStoreCollection::iterator active = mActiveCells.begin();
while (active!=mActiveCells.end())
{
auto* cell = *iter++;
if (cell->getCell()->isExterior())
if ((*active)->getCell()->isExterior())
{
const auto dx = std::abs(playerCellX - cell->getCell()->getGridX());
const auto dy = std::abs(playerCellY - cell->getCell()->getGridY());
if (dx > mHalfGridSize || dy > mHalfGridSize)
deactivateCell(cell);
if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1)
unloadInactiveCell(cell);
if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize &&
std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize)
{
// keep cells within the new grid
++active;
continue;
}
else
{
deactivateCell(cell);
unloadInactiveCell(cell);
}
unloadCell (active++);
}
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
@ -597,24 +533,32 @@ namespace MWWorld
mRendering.getPagedRefnums(newGrid, mPagedRefs);
std::size_t refsToLoad = 0;
const auto cellsToLoad = [&playerCellX,&playerCellY,&refsToLoad](CellStoreCollection& collection, int range) -> std::vector<std::pair<int,int>>
{
std::vector<std::pair<int, int>> cellsPositionsToLoad;
for (int x = playerCellX - range; x <= playerCellX + range; ++x)
// get the number of refs to load
for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x)
{
for (int y = playerCellY - range; y <= playerCellY + range; ++y)
for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y)
{
if (!isCellInCollection(x, y, collection))
CellStoreCollection::iterator iter = mActiveCells.begin();
while (iter!=mActiveCells.end())
{
assert ((*iter)->getCell()->isExterior());
if (x==(*iter)->getCell()->getGridX() &&
y==(*iter)->getCell()->getGridY())
break;
++iter;
}
if (iter==mActiveCells.end())
{
refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count();
cellsPositionsToLoad.emplace_back(x, y);
}
}
}
return cellsPositionsToLoad;
};
auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize);
auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1);
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
@ -638,26 +582,30 @@ namespace MWWorld
return getCellPositionPriority(lhs) < getCellPositionPriority(rhs);
});
std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(),
[&] (const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) {
return getCellPositionPriority(lhs) < getCellPositionPriority(rhs);
});
// Load cells
for (const auto& [x,y] : cellsPositionsToLoadInactive)
for (const auto& cellPosition : cellsPositionsToLoad)
{
if (!isCellInCollection(x, y, mInactiveCells))
const auto x = cellPosition.first;
const auto y = cellPosition.second;
CellStoreCollection::iterator iter = mActiveCells.begin();
while (iter != mActiveCells.end())
{
assert ((*iter)->getCell()->isExterior());
if (x == (*iter)->getCell()->getGridX() &&
y == (*iter)->getCell()->getGridY())
break;
++iter;
}
if (iter == mActiveCells.end())
{
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y);
loadInactiveCell (cell, loadingListener);
}
}
for (const auto& [x,y] : cellsPositionsToLoad)
{
if (!isCellInCollection(x, y, mActiveCells))
{
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y);
activateCell (cell, loadingListener, changeEvent);
loadCell (cell, loadingListener, changeEvent);
}
}
@ -690,8 +638,7 @@ namespace MWWorld
CellStoreCollection::iterator iter = mActiveCells.begin();
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY);
loadInactiveCell (cell, loadingListener, true);
activateCell (cell, loadingListener, false, true);
loadCell (cell, loadingListener, false, true);
iter = mActiveCells.begin();
while (iter != mActiveCells.end())
@ -699,8 +646,7 @@ namespace MWWorld
if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() &&
it->mData.mY == (*iter)->getCell()->getGridY())
{
deactivateCell(*iter, true);
unloadInactiveCell (*iter, true);
unloadCell(iter, true);
break;
}
@ -738,8 +684,7 @@ namespace MWWorld
loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")...");
CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName);
loadInactiveCell (cell, loadingListener, true);
activateCell (cell, loadingListener, false, true);
loadCell (cell, loadingListener, false, true);
CellStoreCollection::iterator iter = mActiveCells.begin();
while (iter != mActiveCells.end())
@ -748,8 +693,7 @@ namespace MWWorld
if (it->mName == (*iter)->getCell()->mName)
{
deactivateCell(*iter, true);
unloadInactiveCell (*iter, true);
unloadCell(iter, true);
break;
}
@ -872,21 +816,15 @@ namespace MWWorld
Log(Debug::Info) << "Changing to interior";
// unload
for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); )
{
auto* cell = *iter++;
deactivateCell(cell);
unloadInactiveCell(cell);
}
assert(mActiveCells.empty());
assert(mInactiveCells.empty());
CellStoreCollection::iterator active = mActiveCells.begin();
while (active!=mActiveCells.end())
unloadCell (active++);
loadingListener->setProgressRange(cell->count());
// Load cell.
mPagedRefs.clear();
loadInactiveCell (cell, loadingListener);
activateCell (cell, loadingListener, changeEvent);
loadCell (cell, loadingListener, changeEvent);
changePlayerCell(cell, position, adjustPlayerPos);
@ -934,26 +872,23 @@ namespace MWWorld
mCellChanged = false;
}
void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test)
void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test)
{
InsertVisitor insertVisitor (cell, *loadingListener, onlyStatics, test);
InsertVisitor insertVisitor (cell, *loadingListener, test);
cell.forEach (insertVisitor);
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyStatics); });
if (!onlyStatics)
{
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); });
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); });
// do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order
PositionVisitor posVisitor;
cell.forEach (posVisitor);
}
}
void Scene::addObjectToScene (const Ptr& ptr)
{
try
{
addObject(ptr, *mPhysics, mRendering, mPagedRefs, false);
addObject(ptr, *mPhysics, mRendering, mPagedRefs);
addObject(ptr, *mPhysics, mNavigator);
MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale());
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();

View file

@ -65,13 +65,13 @@ namespace MWWorld
class Scene
{
public:
using CellStoreCollection = std::set<CellStore *>;
typedef std::set<CellStore *> CellStoreCollection;
private:
CellStore* mCurrentCell; // the cell the player is in
CellStoreCollection mActiveCells;
CellStoreCollection mInactiveCells;
bool mCellChanged;
MWPhysics::PhysicsSystem *mPhysics;
MWRender::RenderingManager& mRendering;
@ -92,7 +92,7 @@ namespace MWWorld
std::set<ESM::RefNum> mPagedRefs;
void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test = false);
void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false);
osg::Vec2i mCurrentGridCenter;
// Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center
@ -108,11 +108,6 @@ namespace MWWorld
osg::Vec4i gridCenterToBounds(const osg::Vec2i &centerCell) const;
osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const;
void unloadInactiveCell (CellStore* cell, bool test = false);
void deactivateCell (CellStore* cell, bool test = false);
void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false);
void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false);
public:
Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics,
@ -124,6 +119,10 @@ namespace MWWorld
void preloadTerrain(const osg::Vec3f& pos, bool sync=false);
void reloadTerrain();
void unloadCell (CellStoreCollection::iterator iter, bool test = false);
void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false);
void playerMoved (const osg::Vec3f& pos);
void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos);

View file

@ -140,6 +140,7 @@ namespace MWWorld
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
const Files::Collections& fileCollections,
const std::vector<std::string>& contentFiles,
const std::vector<std::string>& groundcoverFiles,
ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride,
const std::string& startCell, const std::string& startupScript,
const std::string& resourcePath, const std::string& userDataPath)
@ -152,7 +153,7 @@ namespace MWWorld
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
{
mEsm.resize(contentFiles.size());
mEsm.resize(contentFiles.size() + groundcoverFiles.size());
Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
listener->loadingOn();
@ -165,7 +166,7 @@ namespace MWWorld
gameContentLoader.addLoader(".omwaddon", &esmLoader);
gameContentLoader.addLoader(".project", &esmLoader);
loadContentFiles(fileCollections, contentFiles, gameContentLoader);
loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader);
listener->loadingOff();
@ -1336,6 +1337,12 @@ namespace MWWorld
void World::adjustPosition(const Ptr &ptr, bool force)
{
if (ptr.isEmpty())
{
Log(Debug::Warning) << "Unable to adjust position for empty object";
return;
}
osg::Vec3f pos (ptr.getRefData().getPosition().asVec3());
if(!ptr.getRefData().getBaseNode())
@ -1344,6 +1351,12 @@ namespace MWWorld
return;
}
if (!ptr.isInCell())
{
Log(Debug::Warning) << "Unable to adjust position for object '" << ptr.getCellRef().getRefId() << "' - it has no cell";
return;
}
const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits<float>::max();
pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below
@ -1407,7 +1420,7 @@ namespace MWWorld
mWorldScene->removeFromPagedRefs(ptr);
mRendering->rotateObject(ptr, rotate);
mPhysics->updateRotation(ptr, rotate);
mPhysics->updateRotation(ptr);
if (const auto object = mPhysics->getObject(ptr))
updateNavigatorObject(object);
@ -2941,7 +2954,7 @@ namespace MWWorld
}
void World::loadContentFiles(const Files::Collections& fileCollections,
const std::vector<std::string>& content, ContentLoader& contentLoader)
const std::vector<std::string>& content, const std::vector<std::string>& groundcover, ContentLoader& contentLoader)
{
int idx = 0;
for (const std::string &file : content)
@ -2959,6 +2972,24 @@ namespace MWWorld
}
idx++;
}
ESM::GroundcoverIndex = idx;
for (const std::string &file : groundcover)
{
boost::filesystem::path filename(file);
const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string());
if (col.doesExist(file))
{
contentLoader.load(col.getPath(file), idx);
}
else
{
std::string message = "Failed loading " + file + ": the groundcover file does not exist";
throw std::runtime_error(message);
}
idx++;
}
}
bool World::startSpellCast(const Ptr &actor)

View file

@ -177,7 +177,7 @@ namespace MWWorld
* @param contentLoader -
*/
void loadContentFiles(const Files::Collections& fileCollections,
const std::vector<std::string>& content, ContentLoader& contentLoader);
const std::vector<std::string>& content, const std::vector<std::string>& groundcover, ContentLoader& contentLoader);
float feetToGameUnits(float feet);
float getActivationDistancePlusTelekinesis();
@ -196,6 +196,7 @@ namespace MWWorld
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
const Files::Collections& fileCollections,
const std::vector<std::string>& contentFiles,
const std::vector<std::string>& groundcoverFiles,
ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride,
const std::string& startCell, const std::string& startupScript,
const std::string& resourcePath, const std::string& userDataPath);

View file

@ -100,6 +100,7 @@ bool Config::GameSettings::readFile(QTextStream &stream, QMultiMap<QString, QStr
if (key != QLatin1String("data")
&& key != QLatin1String("fallback-archive")
&& key != QLatin1String("content")
&& key != QLatin1String("groundcover")
&& key != QLatin1String("script-blacklist"))
settings.remove(key);
@ -200,6 +201,7 @@ bool Config::GameSettings::isOrderedLine(const QString& line)
|| line.contains(QRegExp("^\\s*data\\s*="))
|| line.contains(QRegExp("^\\s*data-local\\s*="))
|| line.contains(QRegExp("^\\s*resources\\s*="))
|| line.contains(QRegExp("^\\s*groundcover\\s*="))
|| line.contains(QRegExp("^\\s*content\\s*="));
}

View file

@ -5,6 +5,11 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
namespace ESM
{
int GroundcoverIndex = std::numeric_limits<int>::max();
}
void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag)
{
if (wide)

View file

@ -12,6 +12,7 @@ namespace ESM
class ESMReader;
const int UnbreakableLock = std::numeric_limits<int>::max();
extern int GroundcoverIndex;
struct RefNum
{
@ -25,6 +26,10 @@ namespace ESM
enum { RefNum_NoContentFile = -1 };
inline bool hasContentFile() const { return mContentFile != RefNum_NoContentFile; }
inline void unset() { mIndex = 0; mContentFile = RefNum_NoContentFile; }
// Note: this method should not be used for objects with invalid RefNum
// (for example, for objects from disabled plugins in savegames).
inline bool fromGroundcoverFile() const { return mContentFile >= GroundcoverIndex; }
};
/* Cell reference. This represents ONE object (of many) inside the

View file

@ -247,10 +247,12 @@ namespace Resource
return mForceShaders;
}
void SceneManager::recreateShaders(osg::ref_ptr<osg::Node> node, const std::string& shaderPrefix, bool translucentFramebuffer)
void SceneManager::recreateShaders(osg::ref_ptr<osg::Node> node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode)
{
osg::ref_ptr<Shader::ShaderVisitor> shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer));
shaderVisitor->setAllowedToModifyStateSets(false);
if (forceShadersForNode)
shaderVisitor->setForceShaders(true);
node->accept(*shaderVisitor);
}

View file

@ -76,7 +76,7 @@ namespace Resource
Shader::ShaderManager& getShaderManager();
/// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed.
void recreateShaders(osg::ref_ptr<osg::Node> node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false);
void recreateShaders(osg::ref_ptr<osg::Node> node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false);
/// @see ShaderVisitor::setForceShaders
void setForceShaders(bool force);

View file

@ -18,10 +18,72 @@
namespace Resource
{
StatsHandler::StatsHandler():
static bool collectStatRendering = false;
static bool collectStatCameraObjects = false;
static bool collectStatViewerObjects = false;
static bool collectStatResource = false;
static bool collectStatGPU = false;
static bool collectStatEvent = false;
static bool collectStatFrameRate = false;
static bool collectStatUpdate = false;
static bool collectStatEngine = false;
static void setupStatCollection()
{
const char* envList = getenv("OPENMW_OSG_STATS_LIST");
if (envList == nullptr)
return;
std::string_view kwList(envList);
auto kwBegin = kwList.begin();
while (kwBegin != kwList.end())
{
auto kwEnd = std::find(kwBegin, kwList.end(), ';');
const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd));
if (kw.compare("gpu") == 0)
collectStatGPU = true;
else if (kw.compare("event") == 0)
collectStatEvent = true;
else if (kw.compare("frame_rate") == 0)
collectStatFrameRate = true;
else if (kw.compare("update") == 0)
collectStatUpdate = true;
else if (kw.compare("engine") == 0)
collectStatEngine = true;
else if (kw.compare("rendering") == 0)
collectStatRendering = true;
else if (kw.compare("cameraobjects") == 0)
collectStatCameraObjects = true;
else if (kw.compare("viewerobjects") == 0)
collectStatViewerObjects = true;
else if (kw.compare("resource") == 0)
collectStatResource = true;
else if (kw.compare("times") == 0)
{
collectStatGPU = true;
collectStatEvent = true;
collectStatFrameRate = true;
collectStatUpdate = true;
collectStatEngine = true;
collectStatRendering = true;
}
if (kwEnd == kwList.end())
break;
kwBegin = std::next(kwEnd);
}
}
StatsHandler::StatsHandler(bool offlineCollect):
_key(osgGA::GUIEventAdapter::KEY_F4),
_initialized(false),
_statsType(false),
_offlineCollect(offlineCollect),
_statsWidth(1280.0f),
_statsHeight(1024.0f),
_font(""),
@ -38,7 +100,8 @@ StatsHandler::StatsHandler():
_font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf");
}
Profiler::Profiler()
Profiler::Profiler(bool offlineCollect):
_offlineCollect(offlineCollect)
{
if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf"))
_font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf");
@ -48,6 +111,28 @@ Profiler::Profiler()
_characterSize = 18;
setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3);
setupStatCollection();
}
bool Profiler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
{
osgViewer::ViewerBase* viewer = nullptr;
bool handled = StatsHandler::handle(ea, aa);
auto* view = dynamic_cast<osgViewer::View*>(&aa);
if (view)
viewer = view->getViewerBase();
if (viewer)
{
// Add/remove openmw stats to the osd as necessary
viewer->getViewerStats()->collectStats("engine", _statsType == StatsHandler::StatsType::VIEWER_STATS);
if (_offlineCollect)
CollectStatistics(viewer);
}
return handled;
}
bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
@ -67,6 +152,9 @@ bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdap
toggle(viewer);
if (_offlineCollect)
CollectStatistics(viewer);
aa.requestRedraw();
return true;
}
@ -281,6 +369,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer)
"FrameNumber",
"",
"Compiling",
"UnrefQueue",
"WorkQueue",
"WorkThread",
"",
@ -294,14 +383,13 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer)
"Nif",
"Keyframe",
"",
"Groundcover Chunk",
"Object Chunk",
"Terrain Chunk",
"Terrain Texture",
"Land",
"Composite",
"",
"UnrefQueue",
"",
"NavMesh UpdateJobs",
"NavMesh CacheSize",
"NavMesh UsedTiles",
@ -370,6 +458,22 @@ void StatsHandler::getUsage(osg::ApplicationUsage &usage) const
usage.addKeyboardMouseBinding(_key, "On screen resource usage stats.");
}
void CollectStatistics(osgViewer::ViewerBase* viewer)
{
osgViewer::Viewer::Cameras cameras;
viewer->getCameras(cameras);
for (auto* camera : cameras)
{
if (collectStatGPU) camera->getStats()->collectStats("gpu", true);
if (collectStatRendering) camera->getStats()->collectStats("rendering", true);
if (collectStatCameraObjects) camera->getStats()->collectStats("scene", true);
}
if (collectStatEvent) viewer->getViewerStats()->collectStats("event", true);
if (collectStatFrameRate) viewer->getViewerStats()->collectStats("frame_rate", true);
if (collectStatUpdate) viewer->getViewerStats()->collectStats("update", true);
if (collectStatResource) viewer->getViewerStats()->collectStats("resource", true);
if (collectStatViewerObjects) viewer->getViewerStats()->collectStats("scene", true);
if (collectStatEngine) viewer->getViewerStats()->collectStats("engine", true);
}
}

View file

@ -18,13 +18,17 @@ namespace Resource
class Profiler : public osgViewer::StatsHandler
{
public:
Profiler();
Profiler(bool offlineCollect);
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override;
private:
bool _offlineCollect;
};
class StatsHandler : public osgGA::GUIEventHandler
{
public:
StatsHandler();
StatsHandler(bool offlineCollect);
void setKey(int key) { _key = key; }
int getKey() const { return _key; }
@ -47,6 +51,7 @@ namespace Resource
osg::ref_ptr<osg::Camera> _camera;
bool _initialized;
bool _statsType;
bool _offlineCollect;
float _statsWidth;
float _statsHeight;
@ -58,6 +63,8 @@ namespace Resource
};
void CollectStatistics(osgViewer::ViewerBase* viewer);
}
#endif

View file

@ -342,6 +342,8 @@ namespace Shader
osg::ref_ptr<osg::Program> program (new osg::Program);
program->addShader(vertexShader);
program->addShader(fragmentShader);
program->addBindAttribLocation("aOffset", 6);
program->addBindAttribLocation("aRotation", 7);
found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first;
}
return found->second;

View file

@ -40,7 +40,7 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T
mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON);
}
osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f &center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile)
osg::ref_ptr<osg::Node> ChunkManager::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, lod, lodFlags);
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);

View file

@ -35,7 +35,7 @@ namespace Terrain
public:
ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer);
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) override;
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;
void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; }
void setCompositeMapLevel(float level) { mCompositeMapLevel = level; }

View file

@ -90,8 +90,6 @@ private:
osg::Vec4i mActiveGrid;
};
const float MIN_SIZE = 1/8.f;
class RootNode : public QuadTreeNode
{
public:
@ -250,6 +248,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour
, mLodFactor(lodFactor)
, mVertexLodMod(vertexLodMod)
, mViewDistance(std::numeric_limits<float>::max())
, mMinSize(1/8.f)
{
mChunkManager->setCompositeMapSize(compMapResolution);
mChunkManager->setCompositeMapLevel(compMapLevel);
@ -257,6 +256,17 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour
mChunkManagers.push_back(mChunkManager.get());
}
QuadTreeWorld::QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize)
: TerrainGrid(parent, storage, nodeMask)
, mViewDataMap(new ViewDataMap)
, mQuadTreeBuilt(false)
, mLodFactor(lodFactor)
, mVertexLodMod(0)
, mViewDistance(std::numeric_limits<float>::max())
, mMinSize(chunkSize)
{
}
QuadTreeWorld::~QuadTreeWorld()
{
}
@ -425,7 +435,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
if (needsUpdate)
{
vd->reset();
DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, mActiveGrid);
DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid);
mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback);
}
@ -438,7 +448,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
entry.mRenderingNode->accept(nv);
}
if (isCullVisitor)
if (mHeightCullCallback && isCullVisitor)
updateWaterCullingView(mHeightCullCallback, vd, static_cast<osgUtil::CullVisitor*>(&nv), mStorage->getCellWorldSize(), !isGridEmpty());
vd->markUnchanged();
@ -457,7 +467,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt()
if (mQuadTreeBuilt)
return;
QuadTreeBuilder builder(mStorage, MIN_SIZE);
QuadTreeBuilder builder(mStorage, mMinSize);
builder.build();
mRootNode = builder.getRootNode();
@ -491,7 +501,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::
ViewData* vd = static_cast<ViewData*>(view);
vd->setViewPoint(viewPoint);
vd->setActiveGrid(grid);
DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, grid);
DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid);
mRootNode->traverseNodes(vd, viewPoint, &lodCallback);
if (!progressTotal)
@ -515,6 +525,7 @@ bool QuadTreeWorld::storeView(const View* view, double referenceTime)
void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats)
{
if (mCompositeMapRenderer)
stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize());
}
@ -522,7 +533,7 @@ void QuadTreeWorld::loadCell(int x, int y)
{
// fallback behavior only for undefined cells (every other is already handled in quadtree)
float dummy;
if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy))
if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy))
TerrainGrid::loadCell(x,y);
else
World::loadCell(x,y);
@ -532,7 +543,7 @@ void QuadTreeWorld::unloadCell(int x, int y)
{
// fallback behavior only for undefined cells (every other is already handled in quadtree)
float dummy;
if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy))
if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy))
TerrainGrid::unloadCell(x,y);
else
World::unloadCell(x,y);

View file

@ -22,6 +22,8 @@ namespace Terrain
public:
QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize);
QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize);
~QuadTreeWorld();
void accept(osg::NodeVisitor& nv);
@ -47,7 +49,7 @@ namespace Terrain
{
public:
virtual ~ChunkManager(){}
virtual osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) = 0;
virtual 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) = 0;
virtual unsigned int getNodeMask() { return 0; }
};
void addChunkManager(ChunkManager*);
@ -66,6 +68,7 @@ namespace Terrain
float mLodFactor;
int mVertexLodMod;
float mViewDistance;
float mMinSize;
};
}

View file

@ -26,6 +26,12 @@ TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::
{
}
TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask)
: Terrain::World(parent, storage, nodeMask)
, mNumSplits(4)
{
}
TerrainGrid::~TerrainGrid()
{
while (!mGrid.empty())
@ -107,6 +113,8 @@ void TerrainGrid::unloadCell(int x, int y)
void TerrainGrid::updateWaterCulling()
{
if (!mHeightCullCallback) return;
osg::ComputeBoundsVisitor computeBoundsVisitor;
mTerrainRoot->accept(computeBoundsVisitor);
float lowZ = computeBoundsVisitor.getBoundingBox()._min.z();

View file

@ -15,6 +15,7 @@ namespace Terrain
{
public:
TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0, int borderMask=0);
TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask=~0);
~TerrainGrid();
void cacheCell(View* view, int x, int y) override;

View file

@ -49,17 +49,38 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst
mResourceSystem->addResourceManager(mTextureManager.get());
}
World::World(osg::Group* parent, Storage* storage, int nodeMask)
: mStorage(storage)
, mParent(parent)
, mCompositeMapCamera(nullptr)
, mCompositeMapRenderer(nullptr)
, mResourceSystem(nullptr)
, mTextureManager(nullptr)
, mChunkManager(nullptr)
, mCellBorder(nullptr)
, mBorderVisible(false)
, mHeightCullCallback(nullptr)
{
mTerrainRoot = new osg::Group;
mTerrainRoot->setNodeMask(nodeMask);
mParent->addChild(mTerrainRoot);
}
World::~World()
{
if (mResourceSystem && mChunkManager)
mResourceSystem->removeResourceManager(mChunkManager.get());
if (mResourceSystem && mTextureManager)
mResourceSystem->removeResourceManager(mTextureManager.get());
mParent->removeChild(mTerrainRoot);
if (mCompositeMapCamera && mCompositeMapRenderer)
{
mCompositeMapCamera->removeChild(mCompositeMapRenderer);
mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera);
delete mStorage;
}
}
void World::setWorkQueue(SceneUtil::WorkQueue* workQueue)
@ -108,16 +129,20 @@ float World::getHeightAt(const osg::Vec3f &worldPos)
void World::updateTextureFiltering()
{
if (mTextureManager)
mTextureManager->updateTextureFiltering();
}
void World::clearAssociatedCaches()
{
if (mChunkManager)
mChunkManager->clearCache();
}
osg::Callback* World::getHeightCullCallback(float highz, unsigned int mask)
{
if (!mHeightCullCallback) return nullptr;
mHeightCullCallback->setHighZ(highz);
mHeightCullCallback->setCullMask(mask);
return mHeightCullCallback;

View file

@ -106,6 +106,7 @@ namespace Terrain
/// @param nodeMask mask for the terrain root
/// @param preCompileMask mask for pre compiling textures
World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask);
World(osg::Group* parent, Storage* storage, int nodeMask);
virtual ~World();
/// Set a WorkQueue to delete objects in the background thread.

View file

@ -223,7 +223,7 @@ For example, to attach a custom weapon bone, you'll need to follow this NIF reco
::
NiNode "root"
NiNode "root"
NiNode "Bip01 L Hand"
NiNode "Weapon Bone Left"
NiStringExtraData "BONE"
@ -276,6 +276,54 @@ Also it is possible to add a "Bip01 Arrow" bone to actor skeletons. In this case
Such approach allows to implement better shooting animations (for example, beast races have tail, so quivers should be attached under different angle and
default arrow fetching animation does not look good).
Groundcover support
-------------------
Groundcover objects is a special kind of objects (e.g. grass), which can be used to improve visual fidelity.
They use these assumptions:
1. Each object is independent, so part of objects can be removed from scene without causing graphical artifacts.
2. Groundover should not have collisions.
3. They are not important for some parts of game scene (e.g. local map).
4. They can not be moved or disabled on the fly.
5. They can not be interacted with.
As result, such objects can be treated in the separate way:
1. It is possible to tweak groundcover objects density.
2. It is possible to safely merge such objects even near player.
3. Such objects can be animated (to simulate wind, for example).
4. Some parts of processing can be skipped.
For example, we do not need to have collision or animation objects for groundcover,
do not need to render groundcover on the map, do not need to render it for the whole visible area (which can be very large with Distant Terrain). It allows to increase performance a lot.
General advices to create assets for this feature:
1. Alpha properties from Nif files are not used, a unified alpha settings are used (alpha testing, "greater of equal" function, 128/255 threshold).
2. Use a single NiTriShape in groundocver mesh, or at least use same properties (texture, alpha, material, etc), so OpenMW can merge them on the fly. Otherwise animations may not work properly.
3. Smooth fading does not work for meshes, which have textures without alpha (e.g. rock).
Groundcover mods can be registered in the openmw.cfg via "groundcover" entries instead of "content" ones:
::
groundcover=my_grass_mod.esp
Every static from such mod is treated as a groundcover object.
Also groundcover detection should be enabled via settings.cfg:
::
[Groundcover]
enabled = true
.. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599
.. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232
.. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886

View file

@ -0,0 +1,56 @@
Groundcover Settings
####################
enabled
-------
:Type: boolean
:Range: True/False
:Default: False
Allows the engine to use groundcover.
Groundcover objects are static objects which come from ESP files, registered via
"groundcover" entries from openmw.cfg rather than "content" ones.
We assume that groundcover objects have no collisions, can not be moved or interacted with,
so we can merge them to pages and animate them indifferently from distance from player.
This setting can only be configured by editing the settings configuration file.
density
-------
:Type: floating point
:Range: 0.0 (0%) to 1.0 (100%)
:Default: 1.0
Determines how many groundcover instances from content files
are used in the game. Can affect performance a lot.
This setting can only be configured by editing the settings configuration file.
distance
--------
:Type: integer
:Range: > 0
:Default: 1
Determines on which distance in cells grass pages are rendered.
Default 1 value means 3x3 cells area (active grid).
May affect performance a lot.
This setting can only be configured by editing the settings configuration file.
min chunk size
--------------
:Type: floating point
:Range: 0.125, 0.25, 0.5, 1.0
:Default: 0.5
Determines a minimum size of groundcover chunks in cells. For example, with 0.5 value
chunks near player will have size 4096x4096 game units. Larger chunks reduce CPU usage
(Draw and Cull bars), but can increase GPU usage (GPU bar) since culling becomes less efficient.
Smaller values do an opposite.
This setting can only be configured by editing the settings configuration file.

View file

@ -42,6 +42,7 @@ The ranges included with each setting are the physically possible ranges, not re
camera
cells
fog
groundcover
map
GUI
HUD

View file

@ -26,6 +26,7 @@ Has no effect if the 'force shaders' option is false.
Enabling per-pixel lighting results in visual differences to the original MW engine.
It is not recommended to enable this option when using vanilla Morrowind files,
because certain lights in Morrowind rely on vertex lighting to look as intended.
Note that groundcover shaders ignore this setting.
clamp lighting
--------------

View file

@ -62,7 +62,7 @@ reflection detail
-----------------
:Type: integer
:Range: 0, 1, 2, 3, 4
:Range: 0, 1, 2, 3, 4, 5
:Default: 2
Controls what kinds of things are rendered in water reflections.
@ -72,6 +72,7 @@ Controls what kinds of things are rendered in water reflections.
2: statics, activators, and doors are also reflected
3: items, containers, and particles are also reflected
4: actors are also reflected
5: groundcover objects are also reflected
In interiors the lowest level is 2.
This setting can be changed ingame with the "Reflection shader detail" dropdown under the Water tab of the Video panel in the Options menu.

View file

@ -449,6 +449,7 @@
<Property key="AddItem" value="World"/>
<Property key="AddItem" value="Objects"/>
<Property key="AddItem" value="Actors"/>
<Property key="AddItem" value="Groundcover"/>
</Widget>
<Widget type="AutoSizedTextBox" skin="SandText" position="64 4 90 16" align="Left Top">
<Property key="Caption" value="Reflection shader detail"/>

View file

@ -950,6 +950,22 @@ lineofsight keep inactive cache = 0
defer aabb update = true
[Models]
# Attempt to load any valid NIF file regardless of its version and track the progress.
# Loading arbitrary meshes is not advised and may cause instability.
load unsupported nif files = false
[Groundcover]
# enable separate groundcover handling
enabled = false
# A groundcover density (0.0 <= value <= 1.0)
# 1.0 means 100% density
density = 1.0
# A maximum distance in cells on which groundcover is rendered.
distance = 1
# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0)
min chunk size = 0.5

View file

@ -7,6 +7,8 @@ set(SDIR ${CMAKE_CURRENT_SOURCE_DIR})
set(DDIRRELATIVE resources/shaders)
set(SHADER_FILES
groundcover_vertex.glsl
groundcover_fragment.glsl
water_vertex.glsl
water_fragment.glsl
water_nm.png

View file

@ -0,0 +1,89 @@
#version 120
#define GROUNDCOVER
#if @diffuseMap
uniform sampler2D diffuseMap;
varying vec2 diffuseMapUV;
#endif
#if @normalMap
uniform sampler2D normalMap;
varying vec2 normalMapUV;
varying vec4 passTangent;
#endif
// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting.
// They may do not look as intended with per-pixel lighting, so ignore this setting for now.
#define PER_PIXEL_LIGHTING @normalMap
varying float euclideanDepth;
varying float linearDepth;
#if PER_PIXEL_LIGHTING
varying vec3 passViewPos;
varying vec3 passNormal;
#else
centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting;
#endif
#include "shadows_fragment.glsl"
#include "lighting.glsl"
float calc_coverage(float a, float alpha_ref, float falloff_rate)
{
return clamp(falloff_rate * (a - alpha_ref) + alpha_ref, 0.0, 1.0);
}
void main()
{
#if @normalMap
vec4 normalTex = texture2D(normalMap, normalMapUV);
vec3 normalizedNormal = normalize(passNormal);
vec3 normalizedTangent = normalize(passTangent.xyz);
vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w;
mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal);
vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0));
#endif
#if @diffuseMap
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV);
#else
gl_FragData[0] = vec4(1.0);
#endif
gl_FragData[0].a = calc_coverage(gl_FragData[0].a, 128.0/255.0, 4.0);
float shadowing = unshadowedLightRatio(linearDepth);
if (euclideanDepth > @groundcoverFadeStart)
gl_FragData[0].a *= 1.0-smoothstep(@groundcoverFadeStart, @groundcoverFadeEnd, euclideanDepth);
vec3 lighting;
#if !PER_PIXEL_LIGHTING
lighting = passLighting + shadowDiffuseLighting * shadowing;
#else
vec3 diffuseLight, ambientLight;
doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight);
lighting = diffuseLight + ambientLight;
#endif
#if @clamp
lighting = clamp(lighting, vec3(0.0), vec3(1.0));
#else
lighting = max(lighting, 0.0);
#endif
gl_FragData[0].xyz *= lighting;
#if @radialFog
float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
#else
float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
#endif
gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue);
applyShadowDebugOverlay();
}

View file

@ -0,0 +1,139 @@
#version 120
#define GROUNDCOVER
attribute vec4 aOffset;
attribute vec3 aRotation;
#if @diffuseMap
varying vec2 diffuseMapUV;
#endif
#if @normalMap
varying vec2 normalMapUV;
varying vec4 passTangent;
#endif
// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting.
// They may do not look as intended with per-pixel lighting, so ignore this setting for now.
#define PER_PIXEL_LIGHTING @normalMap
varying float euclideanDepth;
varying float linearDepth;
#if PER_PIXEL_LIGHTING
varying vec3 passViewPos;
varying vec3 passNormal;
#else
centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting;
#endif
#include "shadows_vertex.glsl"
#include "lighting.glsl"
uniform float osg_SimulationTime;
uniform mat4 osg_ViewMatrixInverse;
uniform mat4 osg_ViewMatrix;
uniform float windSpeed;
uniform vec3 playerPos;
vec2 groundcoverDisplacement(in vec3 worldpos, float h)
{
vec2 windDirection = vec2(1.0);
vec3 footPos = playerPos;
vec3 windVec = vec3(windSpeed * windDirection, 1.0);
float v = length(windVec);
vec2 displace = vec2(2.0 * windVec + 0.1);
vec2 harmonics = vec2(0.0);
harmonics += vec2((1.0 - 0.10*v) * sin(1.0*osg_SimulationTime + worldpos.xy / 1100.0));
harmonics += vec2((1.0 - 0.04*v) * cos(2.0*osg_SimulationTime + worldpos.xy / 750.0));
harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0));
harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0));
float d = length(worldpos - footPos.xyz);
vec3 stomp = vec3(0.0);
if (d < 150.0 && d > 0.0)
{
stomp = (60.0 / d - 0.4) * (worldpos - footPos.xyz);
}
return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp.xy);
}
mat4 rotation(in vec3 angle)
{
float sin_x = sin(angle.x);
float cos_x = cos(angle.x);
float sin_y = sin(angle.y);
float cos_y = cos(angle.y);
float sin_z = sin(angle.z);
float cos_z = cos(angle.z);
return mat4(
cos_z*cos_y+sin_x*sin_y*sin_z, -sin_z*cos_x, cos_z*sin_y+sin_z*sin_x*cos_y, 0.0,
sin_z*cos_y+cos_z*sin_x*sin_y, cos_z*cos_x, sin_z*sin_y-cos_z*sin_x*cos_y, 0.0,
-sin_y*cos_x, sin_x, cos_x*cos_y, 0.0,
0.0, 0.0, 0.0, 1.0);
}
mat3 rotation3(in mat4 rot4)
{
return mat3(
rot4[0].xyz,
rot4[1].xyz,
rot4[2].xyz);
}
void main(void)
{
vec3 position = aOffset.xyz;
float scale = aOffset.w;
mat4 rotation = rotation(aRotation);
vec4 displacedVertex = rotation * scale * gl_Vertex;
displacedVertex = vec4(displacedVertex.xyz + position, 1.0);
vec4 worldPos = osg_ViewMatrixInverse * gl_ModelViewMatrix * displacedVertex;
worldPos.xy += groundcoverDisplacement(worldPos.xyz, gl_Vertex.z);
vec4 viewPos = osg_ViewMatrix * worldPos;
gl_ClipVertex = viewPos;
euclideanDepth = length(viewPos.xyz);
if (length(gl_ModelViewMatrix * vec4(position, 1.0)) > @groundcoverFadeEnd)
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
else
gl_Position = gl_ProjectionMatrix * viewPos;
linearDepth = gl_Position.z;
#if (!PER_PIXEL_LIGHTING || @shadows_enabled)
vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz);
#endif
#if @diffuseMap
diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy;
#endif
#if @normalMap
normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy;
passTangent = gl_MultiTexCoord7.xyzw * rotation;
#endif
#if PER_PIXEL_LIGHTING
passViewPos = viewPos.xyz;
passNormal = rotation3(rotation) * gl_Normal.xyz;
#else
vec3 diffuseLight, ambientLight;
doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting);
passLighting = diffuseLight + ambientLight;
#endif
#if (@shadows_enabled)
setupShadowCoords(viewPos, viewNormal);
#endif
}

View file

@ -8,7 +8,29 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie
float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0);
ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination;
diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal, lightDir), 0.0) * illumination;
float lambert = dot(viewNormal.xyz, lightDir) * illumination;
#ifndef GROUNDCOVER
lambert = max(lambert, 0.0);
#else
{
float cosine = dot(normalize(viewPos), normalize(viewNormal.xyz));
if (lambert >= 0.0)
cosine = -cosine;
float mult = 1.0;
float divisor = 8.0;
if (cosine < 0.0 && cosine >= -1.0/divisor)
mult = mix(1.0, 0.3, -cosine*divisor);
else if (cosine < -1.0/divisor)
mult = 0.3;
lambert *= mult;
lambert = abs(lambert);
}
#endif
diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert;
}
#if PER_PIXEL_LIGHTING