diff --git a/CHANGELOG.md b/CHANGELOG.md index ce01b7293..88d6049f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -130,6 +130,7 @@ Bug #6036: OpenMW-CS: Terrain selection at the border of cells omits certain corner vertices Bug #6043: Actor can have torch missing when torch animation is played Bug #6047: Mouse bindings can be triggered during save loading + Bug #6136: Game freezes when NPCs try to open doors that are about to be closed Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/CMakeLists.txt b/CMakeLists.txt index 085451e40..91c1bb961 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -500,8 +500,8 @@ else () "${OpenMW_BINARY_DIR}/openmw.cfg") endif () -configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg - "${OpenMW_BINARY_DIR}" "openmw-cs.cfg") +pack_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg + "${OpenMW_BINARY_DIR}" "defaults-cs.bin") # Needs the copy version because the configure version assumes the end of the file has been reached when a null character is reached and there are no CMake expressions to evaluate. copy_resource_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters @@ -1039,7 +1039,7 @@ elseif(NOT APPLE) # End of tes3mp addition IF(BUILD_OPENCS) - INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") + INSTALL(FILES "${INSTALL_SOURCE}/defaults-cs.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install resources diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 19c32df60..88c4233c9 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -158,7 +158,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(APPLE) set (OPENCS_MAC_ICON "${CMAKE_SOURCE_DIR}/files/mac/openmw-cs.icns") - set (OPENCS_CFG "${OpenMW_BINARY_DIR}/openmw-cs.cfg") + set (OPENCS_CFG "${OpenMW_BINARY_DIR}/defaults-cs.bin") set (OPENCS_DEFAULT_FILTERS_FILE "${OpenMW_BINARY_DIR}/resources/defaultfilters") set (OPENCS_OPENMW_CFG "${OpenMW_BINARY_DIR}/openmw.cfg") else() @@ -270,7 +270,7 @@ if (WIN32) SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") endif () - INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION ".") + INSTALL(FILES "${INSTALL_SOURCE}/defaults-cs.bin" DESTINATION ".") endif() if (MSVC) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 0958fa8d4..58a0f296e 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -17,15 +17,15 @@ CSMPrefs::State *CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::load() { // default settings file - boost::filesystem::path local = mConfigurationManager.getLocalPath() / mConfigFile; - boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mConfigFile; + boost::filesystem::path local = mConfigurationManager.getLocalPath() / mDefaultConfigFile; + boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mDefaultConfigFile; if (boost::filesystem::exists (local)) mSettings.loadDefault (local.string()); else if (boost::filesystem::exists (global)) mSettings.loadDefault (global.string()); else - throw std::runtime_error ("No default settings file found! Make sure the file \"openmw-cs.cfg\" was properly installed."); + throw std::runtime_error ("No default settings file found! Make sure the file \"" + mDefaultConfigFile + "\" was properly installed."); // user settings file boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; @@ -641,7 +641,7 @@ void CSMPrefs::State::setDefault (const std::string& key, const std::string& def } CSMPrefs::State::State (const Files::ConfigurationManager& configurationManager) -: mConfigFile ("openmw-cs.cfg"), mConfigurationManager (configurationManager), +: mConfigFile ("openmw-cs.cfg"), mDefaultConfigFile("defaults-cs.bin"), mConfigurationManager (configurationManager), mCurrentCategory (mCategories.end()) { if (sThis) diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index aa63de595..7c9fcbecd 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -48,6 +48,7 @@ namespace CSMPrefs private: const std::string mConfigFile; + const std::string mDefaultConfigFile; const Files::ConfigurationManager& mConfigurationManager; ShortcutManager mShortcutManager; Settings::Manager mSettings; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 523949179..8ad944751 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -55,6 +55,11 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const if (mTargetActorId == -1) { + if (mTargetActorRefId.empty()) + { + mTargetActorId = -2; + return MWWorld::Ptr(); + } MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); if (target.isEmpty()) { diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 38719deb4..565b728ed 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -32,8 +32,8 @@ namespace MWPhysics { -Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler) - : mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false) +Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) + : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) , mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) @@ -159,11 +159,13 @@ void Actor::updatePosition() mPositionOffset = osg::Vec3f(); mStandingOnPtr = nullptr; mSkipCollisions = true; + mSkipSimulation = true; } void Actor::setSimulationPosition(const osg::Vec3f& position) { - mSimulationPosition = position; + if (!std::exchange(mSkipSimulation, false)) + mSimulationPosition = position; } osg::Vec3f Actor::getSimulationPosition() const @@ -180,18 +182,20 @@ void Actor::updateCollisionObjectPosition() { std::scoped_lock lock(mPositionMutex); mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); - osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); - osg::Vec3f newPosition = scaledTranslation + mPosition; - mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition)); - mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation)); - mCollisionObject->setWorldTransform(mLocalTransform); + osg::Vec3f newPosition = getScaledMeshTranslation() + mPosition; + + auto& trans = mCollisionObject->getWorldTransform(); + trans.setOrigin(Misc::Convert::toBullet(newPosition)); + trans.setRotation(Misc::Convert::toBullet(mRotation)); + mCollisionObject->setWorldTransform(trans); + mWorldPositionChanged = false; } osg::Vec3f Actor::getCollisionObjectPosition() const { std::scoped_lock lock(mPositionMutex); - return Misc::Convert::toOsg(mLocalTransform.getOrigin()); + return getScaledMeshTranslation() + mPosition; } bool Actor::setPosition(const osg::Vec3f& position) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 7b53e8812..99f625394 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -27,7 +27,7 @@ namespace MWPhysics class Actor final : public PtrHolder { public: - Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler); + Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk); ~Actor() override; /** @@ -74,9 +74,6 @@ namespace MWPhysics */ osg::Vec3f getOriginalHalfExtents() const; - /// Returns the mesh translation, scaled and rotated as necessary - osg::Vec3f getScaledMeshTranslation() const; - /** * Returns the position of the collision body * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. @@ -181,6 +178,9 @@ namespace MWPhysics void addCollisionMask(int collisionMask); int getCollisionMask() const; + /// Returns the mesh translation, scaled and rotated as necessary + osg::Vec3f getScaledMeshTranslation() const; + bool mCanWaterWalk; std::atomic mWalkingOnWater; @@ -204,7 +204,7 @@ namespace MWPhysics osg::Vec3f mVelocity; bool mWorldPositionChanged; bool mSkipCollisions; - btTransform mLocalTransform; + bool mSkipSimulation; mutable std::mutex mPositionMutex; unsigned int mStuckFrames; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 9b98e7e8f..29d1e0b7c 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -599,7 +599,7 @@ namespace MWPhysics if (!mRemainingSteps) return; for (auto& data : mActorsFrameData) - if (data.mActor.lock()) + if (const auto actor = data.mActor.lock()) { std::unique_lock lock(mCollisionWorldMutex); MovementSolver::unstuck(data, mCollisionWorld); diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 636306532..1e69136ab 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -27,8 +27,8 @@ namespace MWPhysics mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); - setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); - setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); + setRotation(ptr.getRefData().getBaseNode()->getAttitude()); + updatePosition(); commitPositionChange(); mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); @@ -51,17 +51,17 @@ namespace MWPhysics mScaleUpdatePending = true; } - void Object::setRotation(const btQuaternion& quat) + void Object::setRotation(const osg::Quat& quat) { std::unique_lock lock(mPositionMutex); - mLocalTransform.setRotation(quat); + mRotation = quat; mTransformUpdatePending = true; } - void Object::setOrigin(const btVector3& vec) + void Object::updatePosition() { std::unique_lock lock(mPositionMutex); - mLocalTransform.setOrigin(vec); + mPosition = mPtr.getRefData().getPosition().asVec3(); mTransformUpdatePending = true; } @@ -75,7 +75,10 @@ namespace MWPhysics } if (mTransformUpdatePending) { - mCollisionObject->setWorldTransform(mLocalTransform); + btTransform trans; + trans.setOrigin(Misc::Convert::toBullet(mPosition)); + trans.setRotation(Misc::Convert::toBullet(mRotation)); + mCollisionObject->setWorldTransform(trans); mTransformUpdatePending = false; } } @@ -93,7 +96,10 @@ namespace MWPhysics btTransform Object::getTransform() const { std::unique_lock lock(mPositionMutex); - return mLocalTransform; + btTransform trans; + trans.setOrigin(Misc::Convert::toBullet(mPosition)); + trans.setRotation(Misc::Convert::toBullet(mRotation)); + return trans; } bool Object::isSolid() const diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index cae877180..c640318c7 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -16,7 +16,6 @@ namespace Resource } class btCollisionObject; -class btQuaternion; class btVector3; namespace MWPhysics @@ -31,8 +30,8 @@ namespace MWPhysics const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); - void setRotation(const btQuaternion& quat); - void setOrigin(const btVector3& vec); + void setRotation(const osg::Quat& quat); + void updatePosition(); void commitPositionChange(); btCollisionObject* getCollisionObject(); const btCollisionObject* getCollisionObject() const; @@ -51,7 +50,8 @@ namespace MWPhysics std::map mRecIndexToNodePath; bool mSolid; btVector3 mScale; - btTransform mLocalTransform; + osg::Vec3f mPosition; + osg::Quat mRotation; bool mScaleUpdatePending; bool mTransformUpdatePending; mutable std::mutex mPositionMutex; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 79a7fafc8..83a6a924a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -654,7 +654,7 @@ namespace MWPhysics ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + found->second->setRotation(ptr.getRefData().getBaseNode()->getAttitude()); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -675,7 +675,7 @@ namespace MWPhysics ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); + found->second->updatePosition(); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -705,7 +705,15 @@ namespace MWPhysics if (!shape) return; - auto actor = std::make_shared(ptr, shape, mTaskScheduler.get()); + // check if Actor should spawn above water + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + const bool canWaterWalk = effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; + + auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk); + + // check if Actor is on the ground or in the air + traceDown(ptr, ptr.getRefData().getPosition().asVec3(), 10.f); + mActors.emplace(ptr, std::move(actor)); } diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 252da0a68..0e233562c 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -3,14 +3,11 @@ #include #include -#include - #include #include "../mwworld/class.hpp" #include "collisiontype.hpp" -#include "memory" #include "mtphysics.hpp" #include "projectile.hpp" @@ -55,7 +52,9 @@ void Projectile::commitPositionChange() std::scoped_lock lock(mMutex); if (mTransformUpdatePending) { - mCollisionObject->setWorldTransform(mLocalTransform); + auto& trans = mCollisionObject->getWorldTransform(); + trans.setOrigin(Misc::Convert::toBullet(mPosition)); + mCollisionObject->setWorldTransform(trans); mTransformUpdatePending = false; } } @@ -63,14 +62,14 @@ void Projectile::commitPositionChange() void Projectile::setPosition(const osg::Vec3f &position) { std::scoped_lock lock(mMutex); - mLocalTransform.setOrigin(Misc::Convert::toBullet(position)); + mPosition = position; mTransformUpdatePending = true; } osg::Vec3f Projectile::getPosition() const { std::scoped_lock lock(mMutex); - return Misc::Convert::toOsg(mLocalTransform.getOrigin()); + return mPosition; } bool Projectile::canTraverseWater() const diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 81c33d2a5..2ce1a72d5 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -6,12 +6,13 @@ #include #include +#include + #include "ptrholder.hpp" class btCollisionObject; class btCollisionShape; class btConvexShape; -class btVector3; namespace osg { @@ -76,7 +77,6 @@ namespace MWPhysics btConvexShape* mConvexShape; std::unique_ptr mCollisionObject; - btTransform mLocalTransform; bool mTransformUpdatePending; bool mCanCrossWaterSurface; bool mCrossedWaterSurface; @@ -84,6 +84,7 @@ namespace MWPhysics MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; std::optional mWaterHitPosition; + osg::Vec3f mPosition; btVector3 mHitPosition; btVector3 mHitNormal; diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 8e29f0af4..6ab7ac4ce 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -364,6 +364,7 @@ namespace MWRender osg::ref_ptr stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet; stateset->setAttribute(m); stateset->addUniform(new osg::Uniform("colorMode", 0)); + stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); node.setStateSet(stateset); } }; @@ -477,8 +478,7 @@ namespace MWRender constexpr auto copyMask = ~Mask_UpdateVisitor; AnalyzeVisitor analyzeVisitor(copyMask); - osg::Vec3f center3 = { center.x(), center.y(), 0.f }; - analyzeVisitor.mCurrentDistance = (viewPoint - center3).length2(); + analyzeVisitor.mCurrentDistance = (viewPoint - worldCenter).length2(); float minSize = mMinSize; if (mMinSizeMergeFactor) minSize *= mMinSizeMergeFactor; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 7c9dedbd1..0a659fdda 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -478,12 +478,6 @@ namespace MWWorld const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - // By default the player is grounded, with the scene fully loaded, we validate and correct this. - if (player.mCell == cell) // Only run once, during initial cell load. - { - mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); - } - navigator->update(player.getRefData().getPosition().asVec3()); if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 857847cac..54798a193 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1427,7 +1427,7 @@ namespace MWWorld if (movePhysics) { if (const auto object = mPhysics->getObject(ptr)) - updateNavigatorObject(object); + updateNavigatorObject(*object); } } @@ -1486,7 +1486,7 @@ namespace MWWorld if (mPhysics->getActor(ptr)) mNavigator->addAgent(getPathfindingHalfExtents(ptr)); else if (const auto object = mPhysics->getObject(ptr)) - mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator; + updateNavigatorObject(*object); } void World::rotateObjectImp(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags) @@ -1535,7 +1535,7 @@ namespace MWWorld mWorldScene->updateObjectRotation(ptr, order); if (const auto object = mPhysics->getObject(ptr)) - updateNavigatorObject(object); + updateNavigatorObject(*object); } } @@ -1627,7 +1627,7 @@ namespace MWWorld mPhysics->updateRotation(ptr); if (const auto object = mPhysics->getObject(ptr)) - updateNavigatorObject(object); + updateNavigatorObject(*object); } } @@ -1775,14 +1775,11 @@ namespace MWWorld void World::updateNavigator() { - mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) - { - mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator; - }); + mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) { updateNavigatorObject(*object); }); for (const auto& door : mDoorStates) if (const auto object = mPhysics->getObject(door.first)) - mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator; + updateNavigatorObject(*object); if (mShouldUpdateNavigator) { @@ -1791,13 +1788,14 @@ namespace MWWorld } } - bool World::updateNavigatorObject(const MWPhysics::Object* object) + void World::updateNavigatorObject(const MWPhysics::Object& object) { const DetourNavigator::ObjectShapes shapes { - *object->getShapeInstance()->getCollisionShape(), - object->getShapeInstance()->getAvoidCollisionShape() + *object.getShapeInstance()->getCollisionShape(), + object.getShapeInstance()->getAvoidCollisionShape() }; - return mNavigator->updateObject(DetourNavigator::ObjectId(object), shapes, object->getTransform()); + mShouldUpdateNavigator = mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform()) + || mShouldUpdateNavigator; } const MWPhysics::RayCastingInterface* World::getRayCasting() const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 30f2a2645..3827f83eb 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -167,7 +167,7 @@ namespace MWWorld void updateNavigator(); - bool updateNavigatorObject(const MWPhysics::Object* object); + void updateNavigatorObject(const MWPhysics::Object& object); void ensureNeededRecords(); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index ab25fd744..702ab3366 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2011,6 +2011,11 @@ namespace NifOsg { osg::ref_ptr blendFunc (new osg::BlendFunc(getBlendMode((alphaprop->flags>>1)&0xf), getBlendMode((alphaprop->flags>>5)&0xf))); + // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. + // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. + // Either way, D3D8.1 doesn't do that, so adapt the destination factor. + if (blendFunc->getDestination() == GL_DST_ALPHA) + blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 88d44d2f6..745d93daf 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -904,6 +904,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh program->addShader(castingVertexShader); program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"}, + {"adjustCoverage", "1"}, {"useGPUShader4", useGPUShader4} }, osg::Shader::FRAGMENT)); } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index c21593f35..9550c903a 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -22,6 +22,68 @@ namespace Shader { + class AddedState : public osg::Object + { + public: + AddedState() = default; + AddedState(const AddedState& rhs, const osg::CopyOp& copyOp) + : osg::Object(rhs, copyOp) + , mUniforms(rhs.mUniforms) + , mModes(rhs.mModes) + , mAttributes(rhs.mAttributes) + { + } + + void addUniform(const std::string& name) { mUniforms.emplace(name); } + void setMode(osg::StateAttribute::GLMode mode) { mModes.emplace(mode); } + void setAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { mAttributes.emplace(typeMemberPair); } + + void setAttribute(const osg::StateAttribute* attribute) + { + mAttributes.emplace(attribute->getTypeMemberPair()); + } + template + void setAttribute(osg::ref_ptr attribute) { setAttribute(attribute.get()); } + + void setAttributeAndModes(const osg::StateAttribute* attribute) + { + setAttribute(attribute); + InterrogateModesHelper helper(this); + attribute->getModeUsage(helper); + } + template + void setAttributeAndModes(osg::ref_ptr attribute) { setAttributeAndModes(attribute.get()); } + + bool hasUniform(const std::string& name) { return mUniforms.count(name); } + bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } + bool hasAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { return mAttributes.count(typeMemberPair); } + bool hasAttribute(osg::StateAttribute::Type type, unsigned int member) { return hasAttribute(osg::StateAttribute::TypeMemberPair(type, member)); } + + const std::set& getAttributes() { return mAttributes; } + + bool empty() + { + return mUniforms.empty() && mModes.empty() && mAttributes.empty(); + } + + META_Object(Shader, AddedState) + + private: + class InterrogateModesHelper : public osg::StateAttribute::ModeUsage + { + public: + InterrogateModesHelper(AddedState* tracker) : mTracker(tracker) {} + void usesMode(osg::StateAttribute::GLMode mode) override { mTracker->setMode(mode); } + void usesTextureMode(osg::StateAttribute::GLMode mode) override {} + + private: + AddedState* mTracker; + }; + + std::unordered_set mUniforms; + std::unordered_set mModes; + std::set mAttributes; + }; ShaderVisitor::ShaderRequirements::ShaderRequirements() : mShaderRequired(false) @@ -105,14 +167,32 @@ namespace Shader return static_cast(stateSet.getUserDataContainer()->getUserObject("removedState")); } - void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* stateSet) + void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* removedState) { unsigned int index = userData.getUserObjectIndex("removedState"); if (index < userData.getNumUserObjects()) - userData.setUserObject(index, stateSet); + userData.setUserObject(index, removedState); else - userData.addUserObject(stateSet); - stateSet->setName("removedState"); + userData.addUserObject(removedState); + removedState->setName("removedState"); + } + + AddedState* getAddedState(osg::StateSet& stateSet) + { + if (!stateSet.getUserDataContainer()) + return nullptr; + + return static_cast(stateSet.getUserDataContainer()->getUserObject("addedState")); + } + + void updateAddedState(osg::UserDataContainer& userData, AddedState* addedState) + { + unsigned int index = userData.getUserObjectIndex("addedState"); + if (index < userData.getNumUserObjects()) + userData.setUserObject(index, addedState); + else + userData.addUserObject(addedState); + addedState->setName("addedState"); } const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" }; @@ -280,11 +360,13 @@ namespace Shader osg::StateSet::AttributeList removedAttributes; if (osg::ref_ptr removedState = getRemovedState(*stateset)) removedAttributes = removedState->getAttributeList(); + osg::ref_ptr addedState = getAddedState(*stateset); + for (const auto* attributeMap : std::initializer_list{ &attributes, &removedAttributes }) { for (osg::StateSet::AttributeList::const_iterator it = attributeMap->begin(); it != attributeMap->end(); ++it) { - if (attributeMap != &removedAttributes && removedAttributes.count(it->first)) + if (addedState && attributeMap != &removedAttributes && addedState->hasAttribute(it->first)) continue; if (it->first.first == osg::StateAttribute::MATERIAL) { @@ -296,9 +378,6 @@ namespace Shader const osg::Material* mat = static_cast(it->second.first.get()); - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - int colorMode; switch (mat->getColorMode()) { @@ -376,6 +455,10 @@ namespace Shader writableStateSet = node.getOrCreateStateSet(); else writableStateSet = getWritableStateSet(node); + osg::ref_ptr addedState = new AddedState; + osg::ref_ptr previousAddedState = getAddedState(*writableStateSet); + if (!previousAddedState) + previousAddedState = new AddedState; ShaderManager::DefineMap defineMap; for (unsigned int i=0; iaddUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + addedState->addUniform("colorMode"); defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); @@ -403,26 +487,35 @@ namespace Shader removedState = new osg::StateSet(); defineMap["alphaToCoverage"] = "0"; + defineMap["adjustCoverage"] = "0"; if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) { writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); + addedState->addUniform("alphaRef"); if (!removedState->getAttributePair(osg::StateAttribute::ALPHAFUNC)) { const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); - if (alphaFunc) + if (alphaFunc && !previousAddedState->hasAttribute(osg::StateAttribute::ALPHAFUNC, 0)) removedState->setAttribute(alphaFunc->first, alphaFunc->second); } // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + addedState->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc)); // Blending won't work with A2C as we use the alpha channel for coverage. gl_SampleCoverage from ARB_sample_shading would save the day, but requires GLSL 130 if (mConvertAlphaTestToAlphaToCoverage && !reqs.mAlphaBlend) { writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); + addedState->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); defineMap["alphaToCoverage"] = "1"; } + // Adjusting coverage isn't safe with blending on as blending requires the alpha to be intact. + // Maybe we could also somehow (e.g. userdata) detect when the diffuse map has coverage-preserving mip maps in the future + if (!reqs.mAlphaBlend) + defineMap["adjustCoverage"] = "1"; + // Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size osg::ref_ptr exts = osg::GLExtensions::Get(0, false); if (exts && exts->isGpuShader4Supported) @@ -430,10 +523,11 @@ namespace Shader // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } - if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); // This disables the deprecated fixed-function alpha test writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); + addedState->setMode(GL_ALPHA_TEST); if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) { @@ -447,6 +541,18 @@ namespace Shader updateRemovedState(*writableUserData, removedState); } + if (!addedState->empty()) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getOrCreateUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + updateAddedState(*writableUserData, addedState); + } + defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; std::string shaderPrefix; @@ -458,11 +564,14 @@ namespace Shader if (vertexShader && fragmentShader) { - writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); + auto program = mShaderManager.getProgram(vertexShader, fragmentShader); + writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); + addedState->setAttributeAndModes(program); for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { writableStateSet->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); + addedState->addUniform(texIt->second); } } } @@ -477,24 +586,57 @@ namespace Shader else writableStateSet = getWritableStateSet(node); - writableStateSet->removeAttribute(osg::StateAttribute::PROGRAM); + // user data is normally shallow copied so shared with the original stateset - we'll need to copy before edits + osg::ref_ptr writableUserData; + + if (osg::ref_ptr addedState = getAddedState(*writableStateSet)) + { + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + unsigned int index = writableUserData->getUserObjectIndex("addedState"); + writableUserData->removeUserObject(index); + + // O(n log n) to use StateSet::removeX, but this is O(n) + for (auto itr = writableStateSet->getUniformList().begin(); itr != writableStateSet->getUniformList().end();) + { + if (addedState->hasUniform(itr->first)) + writableStateSet->getUniformList().erase(itr++); + else + ++itr; + } + + for (auto itr = writableStateSet->getModeList().begin(); itr != writableStateSet->getModeList().end();) + { + if (addedState->hasMode(itr->first)) + writableStateSet->getModeList().erase(itr++); + else + ++itr; + } + + // StateAttributes track the StateSets they're attached to + // We don't have access to the function to do that, and can't call removeAttribute with an iterator + for (const auto& [type, member] : addedState->getAttributes()) + writableStateSet->removeAttribute(type, member); + } + if (osg::ref_ptr removedState = getRemovedState(*writableStateSet)) { - // user data is normally shallow copied so shared with the original stateset - osg::ref_ptr writableUserData; + if (!writableUserData) + { if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet); + } + unsigned int index = writableUserData->getUserObjectIndex("removedState"); writableUserData->removeUserObject(index); - for (const auto& [mode, value] : removedState->getModeList()) - writableStateSet->setMode(mode, value); - - for (const auto& attribute : removedState->getAttributeList()) - writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + writableStateSet->merge(*removedState); } } diff --git a/extern/Base64/Base64.h b/extern/Base64/Base64.h index 4e9f51747..49b2d29e3 100644 --- a/extern/Base64/Base64.h +++ b/extern/Base64/Base64.h @@ -95,6 +95,12 @@ class Base64 { size_t in_len = input.size(); if (in_len % 4 != 0) return "Input data size is not a multiple of 4"; + if (in_len == 0) + { + out = ""; + return ""; + } + size_t out_len = in_len / 4 * 3; if (input[in_len - 1] == '=') out_len--; if (input[in_len - 2] == '=') out_len--; diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 05be801e9..46b748236 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -22,7 +22,7 @@ float mipmapLevel(vec2 scaleduv) float coveragePreservingAlphaScale(sampler2D diffuseMap, vec2 uv) { - #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER + #if @adjustCoverage vec2 textureSize; #if @useGPUShader4 textureSize = textureSize2D(diffuseMap, 0);