mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-10-09 15:26:35 +00:00
Merge remote-tracking branch 'remotes/origin/master' into stereo_friendly_water
This commit is contained in:
commit
51a37ec6d2
95 changed files with 1506 additions and 496 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
ESM::Position position = object->getPosition();
|
||||
object->setEdited (Object::Override_Position);
|
||||
position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight;
|
||||
object->setPosition(position.pos);
|
||||
}
|
||||
object->setEdited(Object::Override_Position);
|
||||
ESM::Position position = object->getPosition();
|
||||
position.pos[2] -= dropHeight;
|
||||
object->setPosition(position.pos);
|
||||
}
|
||||
|
||||
float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight)
|
||||
float CSVRender::InstanceMode::calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight)
|
||||
{
|
||||
osg::Vec3d point = object->getPosition().asVec3();
|
||||
|
||||
|
@ -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:
|
||||
{
|
||||
float smallestDropHeight = std::numeric_limits<float>::max();
|
||||
int counter = 0;
|
||||
for(osg::ref_ptr<TagBase> tag: selection)
|
||||
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||
{
|
||||
float thisDrop = getDropHeight(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]);
|
||||
if (thisDrop < smallestDropHeight)
|
||||
smallestDropHeight = thisDrop;
|
||||
counter++;
|
||||
}
|
||||
for(osg::ref_ptr<TagBase> tag: selection)
|
||||
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||
{
|
||||
objectTag->mObject->setEdited (Object::Override_Position);
|
||||
ESM::Position position = objectTag->mObject->getPosition();
|
||||
position.pos[2] -= smallestDropHeight;
|
||||
objectTag->mObject->setPosition(position.pos);
|
||||
objectTag->mObject->apply (macro);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TerrainSep:
|
||||
case CollisionSep:
|
||||
{
|
||||
int counter = 0;
|
||||
for(osg::ref_ptr<TagBase> tag: selection)
|
||||
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||
{
|
||||
dropInstance(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]);
|
||||
objectTag->mObject->apply (macro);
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
int counter = 0;
|
||||
for (osg::ref_ptr<TagBase> tag : selection)
|
||||
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
||||
{
|
||||
float objectHeight = dropObjectDataHandler.mObjectHeights[counter];
|
||||
float dropHeight = calculateDropHeight(dropMode, objectTag->mObject, objectHeight);
|
||||
dropInstance(objectTag->mObject, dropHeight);
|
||||
objectTag->mObject->apply(macro);
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float smallestDropHeight = std::numeric_limits<float>::max();
|
||||
int counter = 0;
|
||||
for (osg::ref_ptr<TagBase> tag : selection)
|
||||
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
||||
{
|
||||
float objectHeight = dropObjectDataHandler.mObjectHeights[counter];
|
||||
float thisDrop = calculateDropHeight(dropMode, objectTag->mObject, objectHeight);
|
||||
if (thisDrop < smallestDropHeight)
|
||||
smallestDropHeight = thisDrop;
|
||||
counter++;
|
||||
}
|
||||
for (osg::ref_ptr<TagBase> tag : selection)
|
||||
if (CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(tag.get()))
|
||||
{
|
||||
dropInstance(objectTag->mObject, smallestDropHeight);
|
||||
objectTag->mObject->apply(macro);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget)
|
||||
CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget)
|
||||
: mWorldspaceWidget(worldspacewidget)
|
||||
{
|
||||
std::vector<osg::ref_ptr<TagBase> > selection = mWorldspaceWidget->getSelection (Mask_Reference);
|
||||
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -85,6 +85,7 @@ namespace OMW
|
|||
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
|
||||
std::string mCellName;
|
||||
std::vector<std::string> mContentFiles;
|
||||
std::vector<std::string> mGroundcoverFiles;
|
||||
bool mSkipMenu;
|
||||
bool mUseSound;
|
||||
bool mCompileAll;
|
||||
|
@ -155,6 +156,7 @@ namespace OMW
|
|||
* @param file - filename (extension is required)
|
||||
*/
|
||||
void addContentFile(const std::string& file);
|
||||
void addGroundcoverFile(const std::string& file);
|
||||
|
||||
/// Disable or enable all sounds
|
||||
void setSoundUsage(bool soundUsage);
|
||||
|
|
|
@ -62,6 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
("content", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
|
||||
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
|
||||
|
||||
("groundcover", bpo::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
|
||||
->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon")
|
||||
|
||||
("no-sound", bpo::value<bool>()->implicit_value(true)
|
||||
->default_value(false), "disable all sounds")
|
||||
|
||||
|
@ -190,11 +193,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
return false;
|
||||
}
|
||||
|
||||
StringsVector::const_iterator it(content.begin());
|
||||
StringsVector::const_iterator end(content.end());
|
||||
for (; it != end; ++it)
|
||||
for (auto& file : content)
|
||||
{
|
||||
engine.addContentFile(*it);
|
||||
engine.addContentFile(file);
|
||||
}
|
||||
|
||||
StringsVector groundcover = variables["groundcover"].as<Files::EscapeStringVector>().toStdStringVector();
|
||||
for (auto& file : groundcover)
|
||||
{
|
||||
engine.addGroundcoverFile(file);
|
||||
}
|
||||
|
||||
// startup-settings
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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&);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,8 +479,7 @@ namespace MWPhysics
|
|||
|
||||
mAnimatedObjects.erase(found->second.get());
|
||||
|
||||
if (!keepObject)
|
||||
mObjects.erase(found);
|
||||
mObjects.erase(found);
|
||||
}
|
||||
|
||||
ActorMap::iterator foundActor = mActors.find(ptr);
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
282
apps/openmw/mwrender/groundcover.cpp
Normal file
282
apps/openmw/mwrender/groundcover.cpp
Normal file
|
@ -0,0 +1,282 @@
|
|||
#include "groundcover.hpp"
|
||||
|
||||
#include <osg/Geometry>
|
||||
#include <osg/VertexAttribDivisor>
|
||||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
|
||||
#include "apps/openmw/mwworld/esmstore.hpp"
|
||||
#include "apps/openmw/mwbase/environment.hpp"
|
||||
#include "apps/openmw/mwbase/world.hpp"
|
||||
|
||||
#include "vismask.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ESM::REC_STAT:
|
||||
return store.get<ESM::Static>().searchStatic(id)->mModel;
|
||||
default:
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::setWindSpeed(float windSpeed)
|
||||
{
|
||||
mWindSpeed = windSpeed;
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos)
|
||||
{
|
||||
mPlayerPos = playerPos;
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::setDefaults(osg::StateSet *stateset)
|
||||
{
|
||||
osg::ref_ptr<osg::Uniform> windUniform = new osg::Uniform("windSpeed", 0.0f);
|
||||
stateset->addUniform(windUniform.get());
|
||||
|
||||
osg::ref_ptr<osg::Uniform> playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f));
|
||||
stateset->addUniform(playerPosUniform.get());
|
||||
}
|
||||
|
||||
void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
|
||||
{
|
||||
osg::ref_ptr<osg::Uniform> windUniform = stateset->getUniform("windSpeed");
|
||||
if (windUniform != nullptr)
|
||||
windUniform->set(mWindSpeed);
|
||||
|
||||
osg::ref_ptr<osg::Uniform> playerPosUniform = stateset->getUniform("playerPos");
|
||||
if (playerPosUniform != nullptr)
|
||||
playerPosUniform->set(mPlayerPos);
|
||||
}
|
||||
|
||||
class InstancingVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
InstancingVisitor(std::vector<Groundcover::GroundcoverEntry>& instances, osg::Vec3f& chunkPosition)
|
||||
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||
, mInstances(instances)
|
||||
, mChunkPosition(chunkPosition)
|
||||
{
|
||||
}
|
||||
|
||||
void apply(osg::Node& node) override
|
||||
{
|
||||
osg::ref_ptr<osg::StateSet> ss = node.getStateSet();
|
||||
if (ss != nullptr)
|
||||
{
|
||||
ss->removeAttribute(osg::StateAttribute::MATERIAL);
|
||||
removeAlpha(ss);
|
||||
}
|
||||
|
||||
traverse(node);
|
||||
}
|
||||
|
||||
void apply(osg::Geometry& geom) override
|
||||
{
|
||||
for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i)
|
||||
{
|
||||
geom.getPrimitiveSet(i)->setNumInstances(mInstances.size());
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Vec4Array> transforms = new osg::Vec4Array(mInstances.size());
|
||||
osg::BoundingBox box;
|
||||
float radius = geom.getBoundingBox().radius();
|
||||
for (unsigned int i = 0; i < transforms->getNumElements(); i++)
|
||||
{
|
||||
osg::Vec3f pos(mInstances[i].mPos.asVec3());
|
||||
osg::Vec3f relativePos = pos - mChunkPosition;
|
||||
(*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale);
|
||||
|
||||
// Use an additional margin due to groundcover animation
|
||||
float instanceRadius = radius * mInstances[i].mScale * 1.1f;
|
||||
osg::BoundingSphere instanceBounds(relativePos, instanceRadius);
|
||||
box.expandBy(instanceBounds);
|
||||
}
|
||||
|
||||
geom.setInitialBound(box);
|
||||
|
||||
osg::ref_ptr<osg::Vec3Array> rotations = new osg::Vec3Array(mInstances.size());
|
||||
for (unsigned int i = 0; i < rotations->getNumElements(); i++)
|
||||
{
|
||||
(*rotations)[i] = mInstances[i].mPos.asRotationVec3();
|
||||
}
|
||||
|
||||
// Display lists do not support instancing in OSG 3.4
|
||||
geom.setUseDisplayList(false);
|
||||
|
||||
geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX);
|
||||
geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX);
|
||||
|
||||
osg::ref_ptr<osg::StateSet> ss = geom.getOrCreateStateSet();
|
||||
ss->setAttribute(new osg::VertexAttribDivisor(6, 1));
|
||||
ss->setAttribute(new osg::VertexAttribDivisor(7, 1));
|
||||
|
||||
ss->removeAttribute(osg::StateAttribute::MATERIAL);
|
||||
removeAlpha(ss);
|
||||
|
||||
traverse(geom);
|
||||
}
|
||||
private:
|
||||
std::vector<Groundcover::GroundcoverEntry> mInstances;
|
||||
osg::Vec3f mChunkPosition;
|
||||
|
||||
void removeAlpha(osg::StateSet* stateset)
|
||||
{
|
||||
// MGE uses default alpha settings for groundcover, so we can not rely on alpha properties
|
||||
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
|
||||
stateset->removeMode(GL_ALPHA_TEST);
|
||||
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
|
||||
stateset->removeMode(GL_BLEND);
|
||||
stateset->setRenderBinToInherit();
|
||||
}
|
||||
};
|
||||
|
||||
class DensityCalculator
|
||||
{
|
||||
public:
|
||||
DensityCalculator(float density)
|
||||
: mDensity(density)
|
||||
{
|
||||
}
|
||||
|
||||
bool isInstanceEnabled()
|
||||
{
|
||||
if (mDensity >= 1.f) return true;
|
||||
|
||||
mCurrentGroundcover += mDensity;
|
||||
if (mCurrentGroundcover < 1.f) return false;
|
||||
|
||||
mCurrentGroundcover -= 1.f;
|
||||
|
||||
return true;
|
||||
}
|
||||
void reset() { mCurrentGroundcover = 0.f; }
|
||||
|
||||
private:
|
||||
float mCurrentGroundcover = 0.f;
|
||||
float mDensity = 0.f;
|
||||
};
|
||||
|
||||
inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound)
|
||||
{
|
||||
osg::Vec2f size = maxBound - minBound;
|
||||
if (size.x() >=1 && size.y() >=1) return true;
|
||||
|
||||
osg::Vec3f pos = ref.mPos.asVec3();
|
||||
osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE;
|
||||
if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y())
|
||||
|| (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
{
|
||||
ChunkId id = std::make_tuple(center, size, activeGrid);
|
||||
|
||||
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
|
||||
if (obj)
|
||||
return obj->asNode();
|
||||
else
|
||||
{
|
||||
InstanceMap instances;
|
||||
collectInstances(instances, size, center);
|
||||
osg::ref_ptr<osg::Node> node = createChunk(instances, center);
|
||||
mCache->addEntryToObjectCache(id, node.get());
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density)
|
||||
: GenericResourceManager<ChunkId>(nullptr)
|
||||
, mSceneManager(sceneManager)
|
||||
, mDensity(density)
|
||||
{
|
||||
}
|
||||
|
||||
void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center)
|
||||
{
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f));
|
||||
osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f));
|
||||
DensityCalculator calculator(mDensity);
|
||||
std::vector<ESM::ESMReader> esm;
|
||||
osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
|
||||
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
|
||||
{
|
||||
for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY)
|
||||
{
|
||||
const ESM::Cell* cell = store.get<ESM::Cell>().searchStatic(cellX, cellY);
|
||||
if (!cell) continue;
|
||||
|
||||
calculator.reset();
|
||||
for (size_t i=0; i<cell->mContextList.size(); ++i)
|
||||
{
|
||||
unsigned int index = cell->mContextList.at(i).index;
|
||||
if (esm.size() <= index)
|
||||
esm.resize(index+1);
|
||||
cell->restore(esm[index], i);
|
||||
ESM::CellRef ref;
|
||||
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
|
||||
bool deleted = false;
|
||||
while(cell->getNextRef(esm[index], ref, deleted))
|
||||
{
|
||||
if (deleted) continue;
|
||||
if (!ref.mRefNum.fromGroundcoverFile()) continue;
|
||||
|
||||
if (!calculator.isInstanceEnabled()) continue;
|
||||
if (!isInChunkBorders(ref, minBound, maxBound)) continue;
|
||||
|
||||
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
||||
int type = store.findStatic(ref.mRefID);
|
||||
std::string model = getGroundcoverModel(type, ref.mRefID, store);
|
||||
if (model.empty()) continue;
|
||||
model = "meshes/" + model;
|
||||
|
||||
instances[model].emplace_back(ref, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center)
|
||||
{
|
||||
osg::ref_ptr<osg::Group> group = new osg::Group;
|
||||
osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE;
|
||||
for (auto& pair : instances)
|
||||
{
|
||||
const osg::Node* temp = mSceneManager->getTemplate(pair.first);
|
||||
osg::ref_ptr<osg::Node> node = static_cast<osg::Node*>(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES)));
|
||||
|
||||
// Keep link to original mesh to keep it in cache
|
||||
group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp));
|
||||
|
||||
InstancingVisitor visitor(pair.second, worldCenter);
|
||||
node->accept(visitor);
|
||||
group->addChild(node);
|
||||
}
|
||||
|
||||
group->getBound();
|
||||
group->setNodeMask(Mask_Groundcover);
|
||||
mSceneManager->recreateShaders(group, "groundcover", false, true);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
unsigned int Groundcover::getNodeMask()
|
||||
{
|
||||
return Mask_Groundcover;
|
||||
}
|
||||
|
||||
void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const
|
||||
{
|
||||
stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize());
|
||||
}
|
||||
}
|
69
apps/openmw/mwrender/groundcover.hpp
Normal file
69
apps/openmw/mwrender/groundcover.hpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#ifndef OPENMW_MWRENDER_GROUNDCOVER_H
|
||||
#define OPENMW_MWRENDER_GROUNDCOVER_H
|
||||
|
||||
#include <components/terrain/quadtreeworld.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class GroundcoverUpdater : public SceneUtil::StateSetUpdater
|
||||
{
|
||||
public:
|
||||
GroundcoverUpdater()
|
||||
: mWindSpeed(0.f)
|
||||
, mPlayerPos(osg::Vec3f())
|
||||
{
|
||||
}
|
||||
|
||||
void setWindSpeed(float windSpeed);
|
||||
void setPlayerPos(osg::Vec3f playerPos);
|
||||
|
||||
protected:
|
||||
void setDefaults(osg::StateSet *stateset) override;
|
||||
void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override;
|
||||
|
||||
private:
|
||||
float mWindSpeed;
|
||||
osg::Vec3f mPlayerPos;
|
||||
};
|
||||
|
||||
typedef std::tuple<osg::Vec2f, float, bool> ChunkId; // Center, Size, ActiveGrid
|
||||
class Groundcover : public Resource::GenericResourceManager<ChunkId>, public Terrain::QuadTreeWorld::ChunkManager
|
||||
{
|
||||
public:
|
||||
Groundcover(Resource::SceneManager* sceneManager, float density);
|
||||
~Groundcover() = default;
|
||||
|
||||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
|
||||
|
||||
unsigned int getNodeMask() override;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
|
||||
|
||||
struct GroundcoverEntry
|
||||
{
|
||||
ESM::Position mPos;
|
||||
float mScale;
|
||||
std::string mModel;
|
||||
|
||||
GroundcoverEntry(const ESM::CellRef& ref, const std::string& model)
|
||||
{
|
||||
mPos = ref.mPos;
|
||||
mScale = ref.mScale;
|
||||
mModel = model;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Resource::SceneManager* mSceneManager;
|
||||
float mDensity;
|
||||
|
||||
typedef std::map<std::string, std::vector<GroundcoverEntry>> InstanceMap;
|
||||
osg::ref_ptr<osg::Node> createChunk(InstanceMap& instances, const osg::Vec2f& center);
|
||||
void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -18,23 +18,12 @@ namespace MWWorld
|
|||
if (ptr.getRefData().getBaseNode())
|
||||
{
|
||||
ptr.getRefData().setBaseNode(nullptr);
|
||||
mObjects.push_back (ptr);
|
||||
}
|
||||
mObjects.push_back (ptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct ListObjectsVisitor
|
||||
{
|
||||
std::vector<MWWorld::Ptr> mObjects;
|
||||
|
||||
bool operator() (MWWorld::Ptr ptr)
|
||||
{
|
||||
mObjects.push_back (ptr);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,29 +111,26 @@ namespace
|
|||
return;
|
||||
}
|
||||
|
||||
bool useAnim = ptr.getClass().useAnim();
|
||||
std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS());
|
||||
const auto rotation = makeNodeRotation(ptr, RotationOrder::direct);
|
||||
if (!onlyPhysics)
|
||||
{
|
||||
bool useAnim = ptr.getClass().useAnim();
|
||||
|
||||
const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
|
||||
if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end())
|
||||
ptr.getClass().insertObjectRendering(ptr, model, rendering);
|
||||
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);
|
||||
if (useAnim)
|
||||
MWBase::Environment::get().getMechanicsManager()->add(ptr);
|
||||
|
||||
if (ptr.getClass().isActor())
|
||||
rendering.addWaterRippleEmitter(ptr);
|
||||
if (ptr.getClass().isActor())
|
||||
rendering.addWaterRippleEmitter(ptr);
|
||||
|
||||
// Restore effect particles
|
||||
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
|
||||
}
|
||||
if (!physics.getObject(ptr))
|
||||
ptr.getClass().insertObject (ptr, model, rotation, physics);
|
||||
// Restore effect particles
|
||||
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
|
||||
}
|
||||
|
||||
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,157 +332,140 @@ 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 (test)
|
||||
Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription();
|
||||
else
|
||||
Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription();
|
||||
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto navigator = world->getNavigator();
|
||||
|
||||
const int cellX = cell->getCell()->getGridX();
|
||||
const int cellY = cell->getCell()->getGridY();
|
||||
|
||||
if (!test && cell->getCell()->isExterior())
|
||||
if(result.second)
|
||||
{
|
||||
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
|
||||
navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(),
|
||||
heightField->getCollisionObject()->getWorldTransform());
|
||||
}
|
||||
if (test)
|
||||
Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription();
|
||||
else
|
||||
Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription();
|
||||
|
||||
if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell->getCell()))
|
||||
navigator->addPathgrid(*cell->getCell(), *pathgrid);
|
||||
float verts = ESM::Land::LAND_SIZE;
|
||||
float worldsize = ESM::Land::REAL_SIZE;
|
||||
|
||||
// register local scripts
|
||||
// do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice
|
||||
MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell);
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto navigator = world->getNavigator();
|
||||
|
||||
if (respawn)
|
||||
cell->respawn();
|
||||
const int cellX = cell->getCell()->getGridX();
|
||||
const int cellY = cell->getCell()->getGridY();
|
||||
|
||||
insertCell (*cell, loadingListener, false, test);
|
||||
|
||||
mRendering.addCell(cell);
|
||||
if (!test)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->addCell(cell);
|
||||
bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior();
|
||||
float waterLevel = cell->getWaterLevel();
|
||||
mRendering.setWaterEnabled(waterEnabled);
|
||||
if (waterEnabled)
|
||||
// Load terrain physics first...
|
||||
if (!test && cell->getCell()->isExterior())
|
||||
{
|
||||
mPhysics->enableWater(waterLevel);
|
||||
mRendering.setWaterHeight(waterLevel);
|
||||
|
||||
if (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)
|
||||
{
|
||||
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
|
||||
navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE,
|
||||
cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform());
|
||||
mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(),
|
||||
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());
|
||||
}
|
||||
|
||||
if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell->getCell()))
|
||||
navigator->addPathgrid(*cell->getCell(), *pathgrid);
|
||||
|
||||
// register local scripts
|
||||
// do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice
|
||||
MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell);
|
||||
|
||||
if (respawn)
|
||||
cell->respawn();
|
||||
|
||||
// ... then references. This is important for adjustPosition to work correctly.
|
||||
insertCell (*cell, loadingListener, test);
|
||||
|
||||
mRendering.addCell(cell);
|
||||
if (!test)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->addCell(cell);
|
||||
bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior();
|
||||
float waterLevel = cell->getWaterLevel();
|
||||
mRendering.setWaterEnabled(waterEnabled);
|
||||
if (waterEnabled)
|
||||
{
|
||||
mPhysics->enableWater(waterLevel);
|
||||
mRendering.setWaterHeight(waterLevel);
|
||||
|
||||
if (cell->getCell()->isExterior())
|
||||
{
|
||||
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
|
||||
navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE,
|
||||
cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform());
|
||||
}
|
||||
else
|
||||
{
|
||||
navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(),
|
||||
cell->getWaterLevel(), btTransform::getIdentity());
|
||||
}
|
||||
}
|
||||
else
|
||||
mPhysics->disableWater();
|
||||
|
||||
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
navigator->update(player.getRefData().getPosition().asVec3());
|
||||
|
||||
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
|
||||
{
|
||||
|
||||
mRendering.configureAmbient(cell->getCell());
|
||||
}
|
||||
}
|
||||
else
|
||||
mPhysics->disableWater();
|
||||
|
||||
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
deactivateCell(cell);
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
// get the number of refs to load
|
||||
for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x)
|
||||
{
|
||||
std::vector<std::pair<int, int>> cellsPositionsToLoad;
|
||||
for (int x = playerCellX - range; x <= playerCellX + range; ++x)
|
||||
for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y)
|
||||
{
|
||||
for (int y = playerCellY - range; y <= playerCellY + range; ++y)
|
||||
CellStoreCollection::iterator iter = mActiveCells.begin();
|
||||
|
||||
while (iter!=mActiveCells.end())
|
||||
{
|
||||
if (!isCellInCollection(x, y, collection))
|
||||
{
|
||||
refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count();
|
||||
cellsPositionsToLoad.emplace_back(x, y);
|
||||
}
|
||||
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())
|
||||
{
|
||||
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y);
|
||||
loadInactiveCell (cell, loadingListener);
|
||||
assert ((*iter)->getCell()->isExterior());
|
||||
|
||||
if (x == (*iter)->getCell()->getGridX() &&
|
||||
y == (*iter)->getCell()->getGridY())
|
||||
break;
|
||||
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
for (const auto& [x,y] : cellsPositionsToLoad)
|
||||
{
|
||||
if (!isCellInCollection(x, y, mActiveCells))
|
||||
|
||||
if (iter == mActiveCells.end())
|
||||
{
|
||||
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, mNavigator); });
|
||||
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);
|
||||
}
|
||||
// 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();
|
||||
|
|
|
@ -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 ¢erCell) 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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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*="));
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ¢er, 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);
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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,14 +525,15 @@ bool QuadTreeWorld::storeView(const View* view, double referenceTime)
|
|||
|
||||
void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats)
|
||||
{
|
||||
stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize());
|
||||
if (mCompositeMapRenderer)
|
||||
stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize());
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
mResourceSystem->removeResourceManager(mChunkManager.get());
|
||||
mResourceSystem->removeResourceManager(mTextureManager.get());
|
||||
if (mResourceSystem && mChunkManager)
|
||||
mResourceSystem->removeResourceManager(mChunkManager.get());
|
||||
if (mResourceSystem && mTextureManager)
|
||||
mResourceSystem->removeResourceManager(mTextureManager.get());
|
||||
|
||||
mParent->removeChild(mTerrainRoot);
|
||||
|
||||
mCompositeMapCamera->removeChild(mCompositeMapRenderer);
|
||||
mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera);
|
||||
|
||||
delete mStorage;
|
||||
if (mCompositeMapCamera && mCompositeMapRenderer)
|
||||
{
|
||||
mCompositeMapCamera->removeChild(mCompositeMapRenderer);
|
||||
mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera);
|
||||
}
|
||||
}
|
||||
|
||||
void World::setWorkQueue(SceneUtil::WorkQueue* workQueue)
|
||||
|
@ -108,16 +129,20 @@ float World::getHeightAt(const osg::Vec3f &worldPos)
|
|||
|
||||
void World::updateTextureFiltering()
|
||||
{
|
||||
mTextureManager->updateTextureFiltering();
|
||||
if (mTextureManager)
|
||||
mTextureManager->updateTextureFiltering();
|
||||
}
|
||||
|
||||
void World::clearAssociatedCaches()
|
||||
{
|
||||
mChunkManager->clearCache();
|
||||
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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -223,10 +223,10 @@ For example, to attach a custom weapon bone, you'll need to follow this NIF reco
|
|||
|
||||
::
|
||||
|
||||
NiNode "root"
|
||||
NiNode "Bip01 L Hand"
|
||||
NiNode "Weapon Bone Left"
|
||||
NiStringExtraData "BONE"
|
||||
NiNode "root"
|
||||
NiNode "Bip01 L Hand"
|
||||
NiNode "Weapon Bone Left"
|
||||
NiStringExtraData "BONE"
|
||||
|
||||
OpenMW will detect ``Weapon Bone Left`` node and attach it to ``Bip01 L Hand`` bone of the target skeleton.
|
||||
|
||||
|
@ -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
|
||||
|
|
56
docs/source/reference/modding/settings/groundcover.rst
Normal file
56
docs/source/reference/modding/settings/groundcover.rst
Normal 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.
|
|
@ -42,6 +42,7 @@ The ranges included with each setting are the physically possible ranges, not re
|
|||
camera
|
||||
cells
|
||||
fog
|
||||
groundcover
|
||||
map
|
||||
GUI
|
||||
HUD
|
||||
|
|
|
@ -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
|
||||
--------------
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
89
files/shaders/groundcover_fragment.glsl
Normal file
89
files/shaders/groundcover_fragment.glsl
Normal 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();
|
||||
}
|
139
files/shaders/groundcover_vertex.glsl
Normal file
139
files/shaders/groundcover_vertex.glsl
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue