From 911271f15663a24bd416429aff3afd9851bc5865 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 22 Jul 2020 14:37:43 +0200 Subject: [PATCH 01/26] shareable shadow maps --- components/sceneutil/mwshadowtechnique.cpp | 313 +++++++++++++++------ components/sceneutil/mwshadowtechnique.hpp | 47 +++- 2 files changed, 272 insertions(+), 88 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index fa38da54e..e221e7a36 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -900,44 +900,115 @@ MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(os return vdd.release(); } -void MWShadowTechnique::update(osg::NodeVisitor& nv) +MWShadowTechnique::ViewDependentData* MWShadowTechnique::getSharedVdd(const SharedShadowMapConfig& config) { - OSG_INFO<<"MWShadowTechnique::update(osg::NodeVisitor& "<<&nv<<")"<osg::Group::traverse(nv); + auto it = _viewDependentDataShareMap.find(config._id); + if (it != _viewDependentDataShareMap.end()) + return it->second; + + return nullptr; } -void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) +void MWShadowTechnique::addSharedVdd(const SharedShadowMapConfig& config, ViewDependentData* vdd) { - if (!_enableShadows) + _viewDependentDataShareMap[config._id] = vdd; +} + +void SceneUtil::MWShadowTechnique::shareShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs) +{ + // Prepare for rendering shadows using the shadow map owned by rhs. + + // To achieve this i first copy all data that is not specific to this cv's camera and thus read-only, + // trusting openmw and osg won't overwrite that data before this frame is done rendering. + // This works due to the double buffering of CullVisitors by osg, but also requires that cull passes are serialized (relative to one another). + // Then initialize new copies of the data that will be written with view-specific data + // (the stateset and the texgens). + + lhs->_viewDependentShadowMap = rhs->_viewDependentShadowMap; + lhs->_stateset->clear(); + lhs->_lightDataList = rhs->_lightDataList; + lhs->_numValidShadows = rhs->_numValidShadows; + + ShadowDataList& sdl = lhs->getShadowDataList(); + ShadowDataList previous_sdl; + previous_sdl.swap(sdl); + for (auto rhs_sd : rhs->getShadowDataList()) + { + osg::ref_ptr lhs_sd; + + if (previous_sdl.empty()) + { + OSG_INFO << "Create new ShadowData" << std::endl; + lhs_sd = new ShadowData(lhs); + } + else + { + OSG_INFO << "Taking ShadowData from from of previous_sdl" << std::endl; + lhs_sd = previous_sdl.front(); + previous_sdl.erase(previous_sdl.begin()); + } + lhs_sd->_camera = rhs_sd->_camera; + lhs_sd->_textureUnit = rhs_sd->_textureUnit; + lhs_sd->_texture = rhs_sd->_texture; + sdl.push_back(lhs_sd); + } +} + +bool MWShadowTechnique::trySharedShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* vdd) +{ + auto* sharedConfig = dynamic_cast(cv.getCurrentCamera()->getUserData()); + if (!sharedConfig) + { + return false; + } + + if (sharedConfig->_master) + { + addSharedVdd(*sharedConfig, vdd); + if(sharedConfig->_projection) + cv.pushProjectionMatrix(sharedConfig->_projection); + if(sharedConfig->_modelView) + cv.pushModelViewMatrix(sharedConfig->_modelView, sharedConfig->_referenceFrame); + return false; + } + else + { + auto* sharedVdd = getSharedVdd(*sharedConfig); + if (sharedVdd) + { + OSG_INFO << "Using shared shadow map" << std::endl; + shareShadowMap(cv, vdd, sharedVdd); + return true; + } + else + { + OSG_WARN << "Warning, view configured to reuse shared shadow map but no shadow map has been shared. Shadows will be generated instead." << std::endl; + } + } + + return false; +} + +void SceneUtil::MWShadowTechnique::endSharedShadowMap(osgUtil::CullVisitor& cv) +{ + auto* sharedConfig = dynamic_cast(cv.getCurrentCamera()->getUserData()); + if (!sharedConfig) { - _shadowedScene->osg::Group::traverse(cv); return; } - OSG_INFO<_master) { - OSG_INFO<<"Warning, init() has not yet been called so ShadowCastingStateSet has not been setup yet, unable to create shadows."<osg::Group::traverse(cv); - return; - } - - ViewDependentData* vdd = getViewDependentData(&cv); - - if (!vdd) - { - OSG_INFO<<"Warning, now ViewDependentData created, unable to create shadows."<osg::Group::traverse(cv); - return; + if (sharedConfig->_projection) + cv.popProjectionMatrix(); + if (sharedConfig->_modelView) + cv.popModelViewMatrix(); } +} +void SceneUtil::MWShadowTechnique::castShadows(osgUtil::CullVisitor& cv, ViewDependentData* vdd) +{ ShadowSettings* settings = getShadowedScene()->getShadowSettings(); - - OSG_INFO<<"cv->getProjectionMatrix()="<<*cv.getProjectionMatrix()<getMaximumShadowMapDistance(),maxZFar); - if (minZNear>maxZFar) minZNear = maxZFar*settings->getMinimumShadowMapNearFarRatio(); - + computeProjectionNearFar(cv, orthographicViewFrustum, minZNear, maxZFar); //OSG_NOTICE<<"maxZFar "< vertexArray = new osg::Vec3Array(); - for (osg::Vec3d &vertex : frustum.corners) - vertexArray->push_back((osg::Vec3)vertex); - _debugHud->setFrustumVertices(vertexArray, cv.getTraversalNumber()); - } - + // Reduce near/far as much as possible double reducedNear, reducedFar; if (cv.getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { @@ -1015,8 +1039,14 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) reducedFar = maxZFar; } - // return compute near far mode back to it's original settings - cv.setComputeNearFarMode(cachedNearFarMode); + Frustum frustum(&cv, minZNear, maxZFar); + if (_debugHud) + { + osg::ref_ptr vertexArray = new osg::Vec3Array(); + for (osg::Vec3d& vertex : frustum.corners) + vertexArray->push_back((osg::Vec3)vertex); + _debugHud->setFrustumVertices(vertexArray, cv.getTraversalNumber()); + } OSG_INFO<<"frustum.eye="<osg::Group::traverse(nv); +} + +void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) +{ + if (!_enableShadows) + { + _shadowedScene->osg::Group::traverse(cv); + return; + } + + OSG_INFO<osg::Group::traverse(cv); + return; + } + + ViewDependentData* vdd = getViewDependentData(&cv); + + if (!vdd) + { + OSG_INFO<<"Warning, now ViewDependentData created, unable to create shadows."<osg::Group::traverse(cv); + return; + } + + ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + osg::CullSettings::ComputeNearFarMode cachedNearFarMode = cv.getComputeNearFarMode(); + + OSG_INFO<<"cv->getProjectionMatrix()="<<*cv.getProjectionMatrix()<getComputeNearFarModeOverride()!=osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + { + cv.setComputeNearFarMode(settings->getComputeNearFarModeOverride()); + } + + // 1. Traverse main scene graph + osg::ref_ptr decoratorStateGraph = cullShadowReceivingScene(&cv); + + if (cv.getComputeNearFarMode()!=osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + { + OSG_INFO<<"Just done main subgraph traversak"<_numValidShadows>0) { decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd)); } @@ -2935,27 +3070,31 @@ bool MWShadowTechnique::assignTexGenSettings(osgUtil::CullVisitor* cv, osg::Came // Place texgen with modelview which removes big offsets (making it float friendly) osg::ref_ptr refMatrix = - new osg::RefMatrix( camera->getInverseViewMatrix() * (*(cv->getModelViewMatrix())) ); + new osg::RefMatrix( camera->getInverseViewMatrix() * (*cv->getModelViewMatrix()) ); osgUtil::RenderStage* currentStage = cv->getCurrentRenderBin()->getStage(); currentStage->getPositionalStateContainer()->addPositionedTextureAttribute( textureUnit, refMatrix.get(), texgen ); return true; } -void MWShadowTechnique::cullShadowReceivingScene(osgUtil::CullVisitor* cv) const +osg::ref_ptr MWShadowTechnique::cullShadowReceivingScene(osgUtil::CullVisitor* cv) const { OSG_INFO<<"cullShadowReceivingScene()"<getTraversalMask(); + + cv->pushStateSet(_shadowRecievingPlaceholderStateSet.get()); + osg::ref_ptr decoratorStateGraph = cv->getCurrentStateGraph(); cv->setTraversalMask( traversalMask & _shadowedScene->getShadowSettings()->getReceivesShadowTraversalMask() ); _shadowedScene->osg::Group::traverse(*cv); cv->setTraversalMask( traversalMask ); + cv->popStateSet(); - return; + return decoratorStateGraph; } void MWShadowTechnique::cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 77d0bd2b2..8bfe34c11 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -141,6 +141,28 @@ namespace SceneUtil { // forward declare class ViewDependentData; + /// Configuration of shadow maps shared by multiple views + struct SharedShadowMapConfig : public osg::Referenced + { + virtual ~SharedShadowMapConfig() {} + + /// String identifier of the shared shadow map + std::string _id{ "" }; + + /// If true, this camera will generate the shadow map + bool _master{ false }; + + /// If set, will override projection matrix of camera when generating shadow map. + osg::ref_ptr _projection{ nullptr }; + + /// If set, will override model view matrix of camera when generating shadow map. + osg::ref_ptr _modelView{ nullptr }; + + /// Reference frame of the view matrix + osg::Transform::ReferenceFrame + _referenceFrame{ osg::Transform::ABSOLUTE_RF }; + }; + struct LightData : public osg::Referenced { LightData(ViewDependentData* vdd); @@ -195,7 +217,12 @@ namespace SceneUtil { virtual void releaseGLObjects(osg::State* = 0) const; + unsigned int numValidShadows(void) const { return _numValidShadows; } + + void setNumValidShadows(unsigned int numValidShadows) { _numValidShadows = numValidShadows; } + protected: + friend class MWShadowTechnique; virtual ~ViewDependentData() {} MWShadowTechnique* _viewDependentShadowMap; @@ -204,13 +231,29 @@ namespace SceneUtil { LightDataList _lightDataList; ShadowDataList _shadowDataList; + + unsigned int _numValidShadows; }; virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv); ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); + ViewDependentData* getSharedVdd(const SharedShadowMapConfig& config); + void addSharedVdd(const SharedShadowMapConfig& config, ViewDependentData* vdd); + + void shareShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs); + + bool trySharedShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* vdd); + + void endSharedShadowMap(osgUtil::CullVisitor& cv); + + void castShadows(osgUtil::CullVisitor& cv, ViewDependentData* vdd); + + void assignTexGenSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd); + + void computeProjectionNearFar(osgUtil::CullVisitor& cv, bool orthographicViewFrustum, double& znear, double& zfar); virtual void createShaders(); @@ -226,7 +269,7 @@ namespace SceneUtil { virtual bool assignTexGenSettings(osgUtil::CullVisitor* cv, osg::Camera* camera, unsigned int textureUnit, osg::TexGen* texgen); - virtual void cullShadowReceivingScene(osgUtil::CullVisitor* cv) const; + virtual osg::ref_ptr cullShadowReceivingScene(osgUtil::CullVisitor* cv) const; virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; @@ -237,7 +280,9 @@ namespace SceneUtil { typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr > ViewDependentDataMap; mutable std::mutex _viewDependentDataMapMutex; + typedef std::map< std::string, osg::ref_ptr > ViewDependentDataShareMap; ViewDependentDataMap _viewDependentDataMap; + ViewDependentDataShareMap _viewDependentDataShareMap; osg::ref_ptr _shadowRecievingPlaceholderStateSet; From b3f237387542756c0bd482557ab8f61f301f4436 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Thu, 6 Aug 2020 00:54:45 +0200 Subject: [PATCH 02/26] working commit --- apps/openmw/mwrender/renderingmanager.cpp | 25 +++++++++- apps/openmw/mwrender/renderingmanager.hpp | 2 + components/resource/scenemanager.cpp | 2 +- components/shader/shadervisitor.cpp | 7 ++- components/shader/shadervisitor.hpp | 3 +- components/terrain/material.cpp | 4 +- files/shaders/CMakeLists.txt | 4 ++ files/shaders/interface_util.glsl | 12 +++++ files/shaders/objects_geometry.glsl | 17 +++++++ files/shaders/objects_vertex.glsl | 2 + files/shaders/shadows_geometry.glsl | 0 files/shaders/terrain_fragment.glsl | 2 + files/shaders/terrain_geometry.glsl | 61 +++++++++++++++++++++++ files/shaders/terrain_vertex.glsl | 33 ++++++------ 14 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 files/shaders/interface_util.glsl create mode 100644 files/shaders/objects_geometry.glsl create mode 100644 files/shaders/shadows_geometry.glsl create mode 100644 files/shaders/terrain_geometry.glsl diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d9739e844..3c47cee09 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -207,8 +208,9 @@ namespace MWRender resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); // Shadows and radial fog have problems with fixed-function mode - bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows"); - resourceSystem->getSceneManager()->setForceShaders(forceShaders); + //bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows"); + //resourceSystem->getSceneManager()->setForceShaders(forceShaders); + resourceSystem->getSceneManager()->setForceShaders(true); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); @@ -370,11 +372,27 @@ namespace MWRender mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); + ////// Indexed viewports and related uniforms + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoViewOffsets", 2)); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoProjections", 2)); + mUniformStereoViewOffsets = sceneRoot->getOrCreateStateSet()->getUniform("stereoViewOffsets"); + mUniformStereoProjections = sceneRoot->getOrCreateStateSet()->getUniform("stereoProjections"); + mUniformStereoViewOffsets->setElement(0, osg::Matrix::identity()); + mUniformStereoViewOffsets->setElement(1, osg::Matrix::identity()); + + auto width = mViewer->getCamera()->getViewport()->width(); + auto height = mViewer->getCamera()->getViewport()->height(); + + sceneRoot->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height)); + sceneRoot->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(1, width / 2, 0, width / 2, height)); + + ////// Near far uniforms mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); + updateProjectionMatrix(); } @@ -1225,6 +1243,9 @@ namespace MWRender fov = mFieldOfViewOverride; mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); + mUniformStereoProjections->setElement(0, mViewer->getCamera()->getProjectionMatrix()); + mUniformStereoProjections->setElement(1, mViewer->getCamera()->getProjectionMatrix()); + mUniformNear->set(mNearClip); mUniformFar->set(mViewDistance); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index d6a0f89c3..6de660d51 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -108,6 +108,8 @@ namespace MWRender osg::Uniform* mUniformNear; osg::Uniform* mUniformFar; + osg::Uniform* mUniformStereoViewOffsets; + osg::Uniform* mUniformStereoProjections; void preloadCommonAssets(); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index ca6c7c895..3946fd5bf 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -763,7 +763,7 @@ namespace Resource Shader::ShaderVisitor *SceneManager::createShaderVisitor() { - Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); + Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl", "objects_geometry.glsl"); shaderVisitor->setForceShaders(mForceShaders); shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps); shaderVisitor->setNormalMapPattern(mNormalMapPattern); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index c30307f29..4ec1baf65 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -37,7 +37,7 @@ namespace Shader } - ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) + ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate, const std::string& defaultGsTemplate) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mForceShaders(false) , mAllowedToModifyStateSets(true) @@ -47,6 +47,7 @@ namespace Shader , mImageManager(imageManager) , mDefaultVsTemplate(defaultVsTemplate) , mDefaultFsTemplate(defaultFsTemplate) + , mDefaultGsTemplate(defaultGsTemplate) { mRequirements.push_back(ShaderRequirements()); } @@ -349,10 +350,12 @@ namespace Shader osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); + osg::ref_ptr geometryShader (mShaderManager.getShader(mDefaultGsTemplate, defineMap, osg::Shader::GEOMETRY)); + //osg::ref_ptr geometryShader = nullptr; if (vertexShader && fragmentShader) { - writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); + writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader, geometryShader), osg::StateAttribute::ON); for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 8e35f1d9c..719f0b923 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -17,7 +17,7 @@ namespace Shader class ShaderVisitor : public osg::NodeVisitor { public: - ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); + ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate, const std::string& defaultGsTemplate); /// By default, only bump mapped objects will have a shader added to them. /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. @@ -89,6 +89,7 @@ namespace Shader std::string mDefaultVsTemplate; std::string mDefaultFsTemplate; + std::string mDefaultGsTemplate; void createProgram(const ShaderRequirements& reqs); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index e662f4439..4e7e77e63 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -234,6 +234,8 @@ namespace Terrain defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; osg::ref_ptr vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); + osg::ref_ptr geometryShader = shaderManager->getShader("terrain_geometry.glsl", defineMap, osg::Shader::GEOMETRY); + //osg::ref_ptr geometryShader = nullptr; osg::ref_ptr fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); if (!vertexShader || !fragmentShader) { @@ -241,7 +243,7 @@ namespace Terrain return createPasses(false, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); } - stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); + stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader, geometryShader)); stateset->addUniform(new osg::Uniform("colorMode", 2)); } else diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 8012c2bc1..cdc7689fa 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -11,17 +11,21 @@ set(SHADER_FILES water_fragment.glsl water_nm.png objects_vertex.glsl + objects_geometry.glsl objects_fragment.glsl terrain_vertex.glsl + terrain_geometry.glsl terrain_fragment.glsl lighting.glsl parallax.glsl s360_fragment.glsl s360_vertex.glsl shadows_vertex.glsl + shadows_geometry.glsl shadows_fragment.glsl shadowcasting_vertex.glsl shadowcasting_fragment.glsl + interface_util.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/interface_util.glsl b/files/shaders/interface_util.glsl new file mode 100644 index 000000000..e325c9090 --- /dev/null +++ b/files/shaders/interface_util.glsl @@ -0,0 +1,12 @@ +// Because GLSL is an abomination we have to mangle the names of all +// vertex outputs when using a geometry shader. +#ifndef INTERFACE_UTIL_GLSL +#define INTERFACE_UTIL_GLSL + +#if 1 // Placeholder +#define VS_NAME(name) vertex_##name +#else +#define VS_NAME(name) name +#endif + +#endif // INTERFACE_UTIL_GLSL \ No newline at end of file diff --git a/files/shaders/objects_geometry.glsl b/files/shaders/objects_geometry.glsl new file mode 100644 index 000000000..965f1937e --- /dev/null +++ b/files/shaders/objects_geometry.glsl @@ -0,0 +1,17 @@ +#version 330 core +#extension GL_NV_viewport_array : enable +#extension GL_ARB_gpu_shader5 : enable +layout (triangles, invocations = 2) in; +layout (triangle_strip, max_vertices = 3) out; + +#include "interface_util.glsl" + +void main() { + for(int i = 0; i < gl_in.length(); i++) + { + gl_ViewportIndex = gl_InvocationID; + gl_Position = gl_in[i].gl_Position; + EmitVertex(); + } + EndPrimitive(); +} \ No newline at end of file diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 40c448de9..c884e4870 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -1,5 +1,7 @@ #version 120 +#include "interface_util.glsl" + #if @diffuseMap varying vec2 diffuseMapUV; #endif diff --git a/files/shaders/shadows_geometry.glsl b/files/shaders/shadows_geometry.glsl new file mode 100644 index 000000000..e69de29bb diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index 7409ce045..99da45c97 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -110,4 +110,6 @@ void main() gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); applyShadowDebugOverlay(); + + //gl_FragData[0] = vec4(passNormal,1); } diff --git a/files/shaders/terrain_geometry.glsl b/files/shaders/terrain_geometry.glsl new file mode 100644 index 000000000..bdb7fa2f7 --- /dev/null +++ b/files/shaders/terrain_geometry.glsl @@ -0,0 +1,61 @@ +#version 330 core +#extension GL_NV_viewport_array : enable +#extension GL_ARB_gpu_shader5 : enable +layout (triangles, invocations = 2) in; +layout (triangle_strip, max_vertices = 3) out; + +#include "interface_util.glsl" + +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +centroid in vec4 VS_NAME(lighting)[]; +centroid in vec3 VS_NAME(shadowDiffuseLighting)[]; +centroid out vec4 lighting; +centroid out vec3 shadowDiffuseLighting; +#endif + +// TERRAIN INPUT +in vec2 VS_NAME(uv)[]; +in float VS_NAME(euclideanDepth)[]; +in float VS_NAME(linearDepth)[]; +centroid in vec4 VS_NAME(passColor)[]; +in vec3 VS_NAME(passViewPos)[]; +in vec3 VS_NAME(passNormal)[]; + +#if(@shadows_enabled) +#endif + +// TERRAIN OUTPUT +out vec2 uv; +out float euclideanDepth; +out float linearDepth; +centroid out vec4 passColor; +out vec3 passViewPos; +out vec3 passNormal; + +void main() { + for(int i = 0; i < gl_in.length(); i++) + { + gl_ViewportIndex = gl_InvocationID; + gl_Position = gl_in[i].gl_Position; + uv = VS_NAME(uv)[i]; + euclideanDepth = VS_NAME(euclideanDepth)[i]; + linearDepth = VS_NAME(linearDepth)[i]; + +#if !PER_PIXEL_LIGHTING + lighting = VS_NAME(lighting)[i]; + shadowDiffuseLighting = VS_NAME(shadowDiffuseLighting)[i]; +#endif + + passColor = VS_NAME(passColor)[i]; + passViewPos = VS_NAME(passViewPos)[i]; + passNormal = VS_NAME(passNormal)[i]; + +#if(@shadows_enabled) +#endif + + EmitVertex(); + } + EndPrimitive(); +} \ No newline at end of file diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index bf337cf54..35cb426c2 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,44 +1,47 @@ #version 120 -varying vec2 uv; -varying float euclideanDepth; -varying float linearDepth; +#include "interface_util.glsl" + +varying vec2 VS_NAME(uv); +varying float VS_NAME(euclideanDepth); +varying float VS_NAME(linearDepth); #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING -centroid varying vec4 lighting; -centroid varying vec3 shadowDiffuseLighting; +centroid varying vec4 VS_NAME(lighting); +centroid varying vec3 VS_NAME(shadowDiffuseLighting); #endif -centroid varying vec4 passColor; -varying vec3 passViewPos; -varying vec3 passNormal; +centroid varying vec4 VS_NAME(passColor); +varying vec3 VS_NAME(passViewPos); +varying vec3 VS_NAME(passNormal); #include "shadows_vertex.glsl" #include "lighting.glsl" + void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; - euclideanDepth = length(viewPos.xyz); - linearDepth = gl_Position.z; + VS_NAME(euclideanDepth) = length(viewPos.xyz); + VS_NAME(linearDepth) = gl_Position.z; #if (!PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); #endif #if !PER_PIXEL_LIGHTING - lighting = doLighting(viewPos.xyz, viewNormal, gl_Color, shadowDiffuseLighting); + VS_NAME(lighting) = doLighting(viewPos.xyz, viewNormal, gl_Color, VS_NAME(shadowDiffuseLighting)); #endif - passColor = gl_Color; - passNormal = gl_Normal.xyz; - passViewPos = viewPos.xyz; + VS_NAME(passColor) = gl_Color; + VS_NAME(passNormal) = gl_Normal.xyz; + VS_NAME(passViewPos) = viewPos.xyz; - uv = gl_MultiTexCoord0.xy; + VS_NAME(uv) = gl_MultiTexCoord0.xy; #if (@shadows_enabled) setupShadowCoords(viewPos, viewNormal); From 9c171869cbc97cee6a1f593ee0c95e754afaccbd Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 14 Sep 2020 22:17:07 +0200 Subject: [PATCH 03/26] Geometry shader stereo functional --- apps/openmw/engine.cpp | 23 + apps/openmw/engine.hpp | 9 + apps/openmw/mwrender/renderingmanager.cpp | 17 - apps/openmw/mwrender/water.cpp | 11 +- components/CMakeLists.txt | 2 +- components/misc/stereo.cpp | 463 +++++++++++++++++++++ components/misc/stereo.hpp | 126 ++++++ components/resource/scenemanager.cpp | 30 +- components/sceneutil/mwshadowtechnique.cpp | 5 + components/shader/shadermanager.cpp | 280 +++++++++++-- components/shader/shadermanager.hpp | 11 + components/shader/shadervisitor.cpp | 8 +- components/shader/shadervisitor.hpp | 3 +- components/terrain/material.cpp | 5 +- files/shaders/CMakeLists.txt | 4 - files/shaders/interface_util.glsl | 12 - files/shaders/objects_geometry.glsl | 17 - files/shaders/objects_vertex.glsl | 2 - files/shaders/shadows_geometry.glsl | 0 files/shaders/terrain_fragment.glsl | 2 - files/shaders/terrain_geometry.glsl | 61 --- files/shaders/terrain_vertex.glsl | 35 +- 22 files changed, 925 insertions(+), 201 deletions(-) create mode 100644 components/misc/stereo.cpp create mode 100644 components/misc/stereo.hpp delete mode 100644 files/shaders/interface_util.glsl delete mode 100644 files/shaders/objects_geometry.glsl delete mode 100644 files/shaders/shadows_geometry.glsl delete mode 100644 files/shaders/terrain_geometry.glsl diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7cbc1c99f..6f3475e9b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -23,12 +23,16 @@ #include #include +#include + #include #include #include #include +#include + #include #include @@ -692,12 +696,31 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) window->playVideo(logo, true); } + mStereoEnabled = true; //!< TODO: TEMP + + // geometry shader must be enabled before the RenderingManager sets up any shaders + // therefore this part is separate from the rest of stereo setup. + if (mStereoEnabled) + { + mResourceSystem->getSceneManager()->getShaderManager().enableGeometryShader(true); + } + // Create the world mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); + // Set up stereo + if (mStereoEnabled) + { + // Mask in everything that does not currently use shaders. + // Remove that altogether when the sky finally uses them. + auto noShaderMask = MWRender::VisMask::Mask_Sky | MWRender::VisMask::Mask_Sun | MWRender::VisMask::Mask_WeatherParticles; + auto geometryShaderMask = mViewer->getCamera()->getCullMask() & ~noShaderMask; + mStereoView.reset(new Misc::StereoView(mViewer, geometryShaderMask, noShaderMask | MWRender::VisMask::Mask_Scene)); + } + window->setStore(mEnvironment.getWorld()->getStore()); window->initUI(); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 3dd1a69b2..d6ccf83a5 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -33,6 +33,11 @@ namespace Compiler class Context; } +namespace Misc +{ + class StereoView; +} + namespace MWScript { class ScriptManager; @@ -85,6 +90,10 @@ namespace OMW osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; + + bool mStereoEnabled; + std::unique_ptr mStereoView; + bool mSkipMenu; bool mUseSound; bool mCompileAll; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3c47cee09..a34b29653 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -372,20 +372,6 @@ namespace MWRender mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); - ////// Indexed viewports and related uniforms - sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoViewOffsets", 2)); - sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoProjections", 2)); - mUniformStereoViewOffsets = sceneRoot->getOrCreateStateSet()->getUniform("stereoViewOffsets"); - mUniformStereoProjections = sceneRoot->getOrCreateStateSet()->getUniform("stereoProjections"); - mUniformStereoViewOffsets->setElement(0, osg::Matrix::identity()); - mUniformStereoViewOffsets->setElement(1, osg::Matrix::identity()); - - auto width = mViewer->getCamera()->getViewport()->width(); - auto height = mViewer->getCamera()->getViewport()->height(); - - sceneRoot->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height)); - sceneRoot->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(1, width / 2, 0, width / 2, height)); - ////// Near far uniforms mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); @@ -1243,9 +1229,6 @@ namespace MWRender fov = mFieldOfViewOverride; mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); - mUniformStereoProjections->setElement(0, mViewer->getCamera()->getProjectionMatrix()); - mUniformStereoProjections->setElement(1, mViewer->getCamera()->getProjectionMatrix()); - mUniformNear->set(mNearClip); mUniformFar->set(mViewDistance); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c9d16b728..58bbb8f37 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -29,6 +30,7 @@ #include #include +#include #include @@ -246,6 +248,7 @@ public: setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); + Misc::enableStereoForCamera(this, true); // No need for Update traversal since the scene is already updated as part of the main scene graph // A double update would mess with the light collection (in addition to being plain redundant) @@ -341,6 +344,8 @@ public: unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); setViewport(0, 0, rttSize, rttSize); + Misc::enableStereoForCamera(this, true); + // No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph // A double update would mess with the light collection (in addition to being plain redundant) @@ -443,6 +448,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900); mWaterGeom->setDrawCallback(new DepthClampCallback); mWaterGeom->setNodeMask(Mask_Water); + mWaterGeom->setDataVariance(osg::Object::STATIC); mWaterNode = new osg::PositionAttitudeTransform; mWaterNode->setName("Water Root"); @@ -592,6 +598,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R // use a define map to conditionally compile the shader std::map defineMap; defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0"))); + defineMap["geometryShader"] = "1"; Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr vertexShader (shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); @@ -637,9 +644,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R shaderStateset->addUniform(mRainIntensityUniform.get()); - osg::ref_ptr program (new osg::Program); - program->addShader(vertexShader); - program->addShader(fragmentShader); + auto program = shaderMgr.getProgram(vertexShader, fragmentShader); shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); node->setStateSet(shaderStateset); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 86d657792..40e406a2a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -86,7 +86,7 @@ add_component_dir (esmterrain ) add_component_dir (misc - gcd constants utf8stream stringops resourcehelpers rng messageformatparser weakcache + gcd constants utf8stream stringops resourcehelpers rng messageformatparser weakcache stereo ) add_component_dir (debug diff --git a/components/misc/stereo.cpp b/components/misc/stereo.cpp new file mode 100644 index 000000000..de7cdd531 --- /dev/null +++ b/components/misc/stereo.cpp @@ -0,0 +1,463 @@ +#include "stereo.hpp" + +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +namespace Misc +{ + Pose Pose::operator+(const Pose& rhs) + { + Pose pose = *this; + pose.position += this->orientation * rhs.position; + pose.orientation = rhs.orientation * this->orientation; + return pose; + } + + const Pose& Pose::operator+=(const Pose& rhs) + { + *this = *this + rhs; + return *this; + } + + Pose Pose::operator*(float scalar) + { + Pose pose = *this; + pose.position *= scalar; + return pose; + } + + const Pose& Pose::operator*=(float scalar) + { + *this = *this * scalar; + return *this; + } + + Pose Pose::operator/(float scalar) + { + Pose pose = *this; + pose.position /= scalar; + return pose; + } + const Pose& Pose::operator/=(float scalar) + { + *this = *this / scalar; + return *this; + } + + bool Pose::operator==(const Pose& rhs) const + { + return position == rhs.position && orientation == rhs.orientation; + } + + osg::Matrix Pose::viewMatrix(bool useGLConventions) + { + if (useGLConventions) + { + // When applied as an offset to an existing view matrix, + // that view matrix will already convert points to a camera space + // with opengl conventions. So we need to convert offsets to opengl + // conventions. + float y = position.y(); + float z = position.z(); + position.y() = z; + position.z() = -y; + + y = orientation.y(); + z = orientation.z(); + orientation.y() = z; + orientation.z() = -y; + + osg::Matrix viewMatrix; + viewMatrix.setTrans(-position); + viewMatrix.postMultRotate(orientation.conj()); + return viewMatrix; + } + else + { + osg::Vec3d forward = orientation * osg::Vec3d(0, 1, 0); + osg::Vec3d up = orientation * osg::Vec3d(0, 0, 1); + osg::Matrix viewMatrix; + viewMatrix.makeLookAt(position, position + forward, up); + + return viewMatrix; + } + } + + bool FieldOfView::operator==(const FieldOfView& rhs) const + { + return angleDown == rhs.angleDown + && angleUp == rhs.angleUp + && angleLeft == rhs.angleLeft + && angleRight == rhs.angleRight; + } + + // near and far named with an underscore because of windows' headers galaxy brain defines. + osg::Matrix FieldOfView::perspectiveMatrix(float near_, float far_) + { + const float tanLeft = tanf(angleLeft); + const float tanRight = tanf(angleRight); + const float tanDown = tanf(angleDown); + const float tanUp = tanf(angleUp); + + const float tanWidth = tanRight - tanLeft; + const float tanHeight = tanUp - tanDown; + + const float offset = near_; + + float matrix[16] = {}; + + matrix[0] = 2 / tanWidth; + matrix[4] = 0; + matrix[8] = (tanRight + tanLeft) / tanWidth; + matrix[12] = 0; + + matrix[1] = 0; + matrix[5] = 2 / tanHeight; + matrix[9] = (tanUp + tanDown) / tanHeight; + matrix[13] = 0; + + if (far_ <= near_) { + matrix[2] = 0; + matrix[6] = 0; + matrix[10] = -1; + matrix[14] = -(near_ + offset); + } + else { + matrix[2] = 0; + matrix[6] = 0; + matrix[10] = -(far_ + offset) / (far_ - near_); + matrix[14] = -(far_ * (near_ + offset)) / (far_ - near_); + } + + matrix[3] = 0; + matrix[7] = 0; + matrix[11] = -1; + matrix[15] = 0; + + return osg::Matrix(matrix); + } + + bool View::operator==(const View& rhs) const + { + return pose == rhs.pose && fov == rhs.fov; + } + + std::ostream& operator <<( + std::ostream& os, + const Pose& pose) + { + os << "position=" << pose.position << ", orientation=" << pose.orientation; + return os; + } + + std::ostream& operator <<( + std::ostream& os, + const FieldOfView& fov) + { + os << "left=" << fov.angleLeft << ", right=" << fov.angleRight << ", down=" << fov.angleDown << ", up=" << fov.angleUp; + return os; + } + + std::ostream& operator <<( + std::ostream& os, + const View& view) + { + os << "pose=< " << view.pose << " >, fov=< " << view.fov << " >"; + return os; + } + + /// Why are you like this + class TestCullCallback : public osg::NodeCallback + { + public: + TestCullCallback() {} + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + //Log(Debug::Verbose) << "Cull: " << node->getName(); + osgUtil::CullVisitor* cv = static_cast(nv); + traverse(node, nv); + } + }; + + class StereoUpdateCallback : public osg::Callback + { + public: + StereoUpdateCallback(StereoView* node) : mNode(node) {} + + bool run(osg::Object* object, osg::Object* data) override + { + //Log(Debug::Verbose) << "StereoUpdateCallback"; + auto b = traverse(object, data); + //mNode->update(); + return b; + } + + StereoView* mNode; + }; + + class StereoUpdater : public SceneUtil::StateSetUpdater + { + public: + StereoUpdater(StereoView* view) + : stereoView(view) + { + } + + protected: + virtual void setDefaults(osg::StateSet* stateset) + { + auto stereoViewMatrixUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoViewMatrices", 2); + stateset->addUniform(stereoViewMatrixUniform, osg::StateAttribute::OVERRIDE); + auto stereoViewProjectionsUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoViewProjections", 2); + stateset->addUniform(stereoViewProjectionsUniform); + auto geometryPassthroughUniform = new osg::Uniform("geometryPassthrough", false); + stateset->addUniform(geometryPassthroughUniform); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + stereoView->update(stateset); + } + + private: + StereoView* stereoView; + }; + + StereoView::StereoView(osgViewer::Viewer* viewer, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask bruteForceMask) + : osg::Group() + , mViewer(viewer) + , mMainCamera(mViewer->getCamera()) + , mRoot(viewer->getSceneData()->asGroup()) + , mGeometryShaderMask(geometryShaderMask) + , mBruteForceMask(bruteForceMask) + { + SceneUtil::FindByNameVisitor findScene("Scene Root"); + mRoot->accept(findScene); + mScene = findScene.mFoundNode; + if (!mScene) + throw std::logic_error("Couldn't find scene root"); + + setName("Sky Root"); + mRoot->setDataVariance(osg::Object::STATIC); + setDataVariance(osg::Object::STATIC); + mLeftCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + mLeftCamera->setProjectionResizePolicy(osg::Camera::FIXED); + mLeftCamera->setProjectionMatrix(osg::Matrix::identity()); + mLeftCamera->setViewMatrix(osg::Matrix::identity()); + mLeftCamera->setRenderOrder(osg::Camera::NESTED_RENDER); + mLeftCamera->setClearMask(GL_NONE); + mLeftCamera->setCullMask(bruteForceMask); + mLeftCamera->setName("Stereo Left"); + mLeftCamera->setDataVariance(osg::Object::STATIC); + mRightCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + mRightCamera->setProjectionResizePolicy(osg::Camera::FIXED); + mRightCamera->setProjectionMatrix(osg::Matrix::identity()); + mRightCamera->setViewMatrix(osg::Matrix::identity()); + mRightCamera->setRenderOrder(osg::Camera::NESTED_RENDER); + mRightCamera->setClearMask(GL_NONE); + mRightCamera->setCullMask(bruteForceMask); + mRightCamera->setName("Stereo Right"); + mRightCamera->setDataVariance(osg::Object::STATIC); + + mMainCamera->setCullMask(geometryShaderMask); + + // Inject self as the root of the scene graph, and split into geometry-shader stereo and brute force stereo. + addChild(mStereoGeometryShaderRoot); + mStereoGeometryShaderRoot->addChild(mRoot); + addChild(mStereoBruteForceRoot); + mStereoBruteForceRoot->addChild(mLeftCamera); + mLeftCamera->addChild(mScene); // Use scene directly to avoid redundant shadow computation. + mStereoBruteForceRoot->addChild(mRightCamera); + mRightCamera->addChild(mScene); + viewer->setSceneData(this); + addCullCallback(new StereoUpdater(this)); + + // Do a blank double buffering of camera statesets on update. StereoView::Update() apply actual changes during cull; + mLeftCamera->setUpdateCallback(new SceneUtil::StateSetUpdater()); + mRightCamera->setUpdateCallback(new SceneUtil::StateSetUpdater()); + } + + void StereoView::update(osg::StateSet* stateset) + { + auto viewMatrix = mViewer->getCamera()->getViewMatrix(); + auto projectionMatrix = mViewer->getCamera()->getProjectionMatrix(); + + View left{}; + View right{}; + double near = 1.f; + double far = 10000.f; + if (!cb) + { + Log(Debug::Error) << "No update view callback. Stereo rendering will not work."; + } + cb->updateView(left, right, near, far); + + osg::Vec3d leftEye = left.pose.position; + osg::Vec3d rightEye = right.pose.position; + + osg::Matrix leftViewOffset = left.pose.viewMatrix(true); + osg::Matrix rightViewOffset = right.pose.viewMatrix(true); + + osg::Matrix leftViewMatrix = viewMatrix * leftViewOffset; + osg::Matrix rightViewMatrix = viewMatrix * rightViewOffset; + + osg::Matrix leftProjectionMatrix = left.fov.perspectiveMatrix(near, far); + osg::Matrix rightProjectionMatrix = right.fov.perspectiveMatrix(near, far); + + mRightCamera->setViewMatrix(leftViewMatrix); + mLeftCamera->setViewMatrix(rightViewMatrix); + mRightCamera->setProjectionMatrix(leftProjectionMatrix); + mLeftCamera->setProjectionMatrix(rightProjectionMatrix); + + // Manage viewports in update to automatically catch window/resolution changes. + auto width = mMainCamera->getViewport()->width(); + auto height = mMainCamera->getViewport()->height(); + mLeftCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height), osg::StateAttribute::OVERRIDE); + mRightCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, width / 2, 0, width / 2, height), osg::StateAttribute::OVERRIDE); + stateset->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height)); + stateset->setAttribute(new osg::ViewportIndexed(1, width / 2, 0, width / 2, height)); + + // The persepctive frustum will be computed from a position P slightly behind the eyes L and R + // where it creates the minimum frustum encompassing both eyes' frustums. + // NOTE: I make an assumption that the eyes lie in a horizontal plane relative to the base view, + // and lie mirrored around the Y axis (straight ahead). + // Re-think this if that turns out to be a bad assumption + View frustumView; + + // Compute Frustum angles. A simple min/max. + /* Example values for reference: + Left: + angleLeft -0.767549932 float + angleRight 0.620896876 float + angleDown -0.837898076 float + angleUp 0.726982594 float + + Right: + angleLeft -0.620896876 float + angleRight 0.767549932 float + angleDown -0.837898076 float + angleUp 0.726982594 float + */ + frustumView.fov.angleLeft = std::min(left.fov.angleLeft, right.fov.angleLeft); + frustumView.fov.angleRight = std::max(left.fov.angleRight, right.fov.angleRight); + frustumView.fov.angleDown = std::min(left.fov.angleDown, right.fov.angleDown); + frustumView.fov.angleUp = std::max(left.fov.angleUp, right.fov.angleUp); + + // Check that the case works for this approach + auto maxAngle = std::max(frustumView.fov.angleRight - frustumView.fov.angleLeft, frustumView.fov.angleUp - frustumView.fov.angleDown); + if (maxAngle > osg::PI) + { + Log(Debug::Error) << "Total FOV exceeds 180 degrees. Case cannot be culled in single-pass VR. Disabling culling to cope. Consider switching to dual-pass VR."; + mMainCamera->setCullingActive(false); + return; + // TODO: An explicit frustum projection could cope, so implement that later. Guarantee you there will be VR headsets with total fov > 180 in the future. Maybe already. + } + + // Use the law of sines on the triangle spanning PLR to determine P + double angleLeft = std::abs(frustumView.fov.angleLeft); + double angleRight = std::abs(frustumView.fov.angleRight); + double lengthRL = (rightEye - leftEye).length(); + double ratioRL = lengthRL / std::sin(osg::PI - angleLeft - angleRight); + double lengthLP = ratioRL * std::sin(angleRight); + + osg::Vec3d directionLP = osg::Vec3(std::cos(-angleLeft), std::sin(-angleLeft), 0); + osg::Vec3d LP = directionLP * lengthLP; + frustumView.pose.position = leftEye + LP; + + // Base view position is 0.0, by definition. + // The length of the vector P is therefore the required offset to near/far. + auto nearFarOffset = frustumView.pose.position.length(); + + // Generate the frustum matrices + auto frustumViewMatrix = viewMatrix * frustumView.pose.viewMatrix(true); + auto frustumProjectionMatrix = frustumView.fov.perspectiveMatrix(near + nearFarOffset, far + nearFarOffset); + auto frustumViewMatrixInverse = osg::Matrix::inverse(projectionMatrix) * osg::Matrix::inverse(viewMatrix); + + // Update camera with frustum matrices + mMainCamera->setViewMatrix(frustumViewMatrix); + mMainCamera->setProjectionMatrix(frustumProjectionMatrix); + + mStereoGeometryShaderRoot->setStateSet(stateset); + + + // Create and/or update stereo uniforms + auto* stereoViewMatrixUniform = stateset->getUniform("stereoViewMatrices"); + auto* stereoViewProjectionsUniform = stateset->getUniform("stereoViewProjections"); + + stereoViewMatrixUniform->setElement(1, frustumViewMatrixInverse * leftViewMatrix); + stereoViewMatrixUniform->setElement(0, frustumViewMatrixInverse * rightViewMatrix); + stereoViewProjectionsUniform->setElement(1, frustumViewMatrixInverse * rightViewMatrix * leftProjectionMatrix); + stereoViewProjectionsUniform->setElement(0, frustumViewMatrixInverse * rightViewMatrix * rightProjectionMatrix); + } + + void StereoView::setUpdateViewCallback(std::shared_ptr cb) + { + this->cb = cb; + } + + void StereoView::useSlaveCameraAtIndex(int index) + { + if (mViewer->getNumSlaves() <= index) + { + Log(Debug::Error) << "Requested slave at index " << index << " but no such slave exists"; + return; + } + + mMainCamera = mViewer->getSlave(index)._camera; + mMainCamera->setCullMask(mGeometryShaderMask); + } + + void disableStereoForCamera(osg::Camera* camera) + { + auto* viewport = camera->getViewport(); + camera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, viewport->x(), viewport->y(), viewport->width(), viewport->height()), osg::StateAttribute::OVERRIDE); + camera->getOrCreateStateSet()->addUniform(new osg::Uniform("geometryPassthrough", true), osg::StateAttribute::OVERRIDE); + } + + void enableStereoForCamera(osg::Camera* camera, bool horizontalSplit) + { + auto* viewport = camera->getViewport(); + auto x1 = viewport->x(); + auto y1 = viewport->y(); + auto width = viewport->width(); + auto height = viewport->height(); + + auto x2 = x1; + auto y2 = y1; + + if (horizontalSplit) + { + width /= 2; + x2 += width; + } + else + { + height /= 2; + y2 += height; + } + + camera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, x1, y1, width, height)); + camera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(1, x2, y2, width, height)); + camera->getOrCreateStateSet()->addUniform(new osg::Uniform("geometryPassthrough", false)); + } + void StereoView::DefaultUpdateViewCallback::updateView(View& left, View& right, double& near, double& far) + { + left.pose.position = osg::Vec3(-2.2, 0, 0); + right.pose.position = osg::Vec3(2.2, 0, 0); + left.fov = { -0.767549932, 0.620896876, -0.837898076, 0.726982594 }; + right.fov = { -0.620896876, 0.767549932, -0.837898076, 0.726982594 }; + near = 1; + far = 10000; + } +} diff --git a/components/misc/stereo.hpp b/components/misc/stereo.hpp new file mode 100644 index 000000000..2d2567f1c --- /dev/null +++ b/components/misc/stereo.hpp @@ -0,0 +1,126 @@ +#ifndef MISC_STEREO_H +#define MISC_STEREO_H + +#include +#include +#include +#include + +// Some cursed headers like to define these +#if defined(near) || defined(far) +#undef near +#undef far +#endif + +namespace osgViewer +{ + class Viewer; +} + +namespace Misc +{ + //! Represents the relative pose in space of some object + struct Pose + { + //! Position in space + osg::Vec3 position{ 0,0,0 }; + //! Orientation in space. + osg::Quat orientation{ 0,0,0,1 }; + + //! Add one pose to another + Pose operator+(const Pose& rhs); + const Pose& operator+=(const Pose& rhs); + + //! Scale a pose (does not affect orientation) + Pose operator*(float scalar); + const Pose& operator*=(float scalar); + Pose operator/(float scalar); + const Pose& operator/=(float scalar); + + bool operator==(const Pose& rhs) const; + + osg::Matrix viewMatrix(bool useGLConventions); + }; + + //! Fov that defines all 4 angles from center + struct FieldOfView { + float angleLeft{ -osg::PI_2 }; + float angleRight{ osg::PI_2 }; + float angleDown{ -osg::PI_2 }; + float angleUp{ osg::PI_2 }; + + bool operator==(const FieldOfView& rhs) const; + + //! Generate a perspective matrix from this fov + osg::Matrix perspectiveMatrix(float near, float far); + }; + + //! Represents an eye including both pose and fov. + struct View + { + Pose pose; + FieldOfView fov; + bool operator==(const View& rhs) const; + }; + + //! Represent two eyes. The eyes are in relative terms, and are assumed to lie on the horizon plane. + struct StereoView : public osg::Group + { + struct UpdateViewCallback + { + //! Called during the update traversal of every frame to source updated stereo values. + virtual void updateView(View& left, View& right, double& near, double& far) = 0; + }; + + //! Default implementation of UpdateViewCallback that just provides some hardcoded values for debugging purposes + struct DefaultUpdateViewCallback : public UpdateViewCallback + { + virtual void updateView(View& left, View& right, double& near, double& far); + }; + + //! Adds two cameras in stereo to the mainCamera. + //! All nodes matching the mask are rendered in stereo using brute force via two camera transforms, the rest are rendered in stereo via a geometry shader. + //! \note The mask is removed from the mainCamera, so do not put Scene in this mask. + //! \note Brute force does not support shadows. But that's fine because currently this only applies to things that don't use shaders and that's only the sky, which will use shaders in the future. + StereoView(osgViewer::Viewer* viewer, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask bruteForceMask); + + //! Updates uniforms with the view and projection matrices of each stereo view, and replaces the camera's view and projection matrix + //! with a view and projection that closely envelopes the frustums of the two eyes. + void update(osg::StateSet* stateset); + + //! Callback that updates stereo configuration during the update pass + void setUpdateViewCallback(std::shared_ptr cb); + + //! Use the slave camera at index instead of the main viewer camera. + void useSlaveCameraAtIndex(int index); + + osg::ref_ptr mViewer; + osg::ref_ptr mMainCamera; + osg::ref_ptr mRoot; + osg::ref_ptr mScene; + + // Keeps state relevant to doing stereo via the geometry shader + osg::ref_ptr mStereoGeometryShaderRoot{ new osg::Group }; + osg::Node::NodeMask mGeometryShaderMask; + + // Keeps state and cameras relevant to doing stereo via brute force + osg::ref_ptr mStereoBruteForceRoot{ new osg::Group }; + osg::Node::NodeMask mBruteForceMask; + osg::ref_ptr mLeftCamera{ new osg::Camera }; + osg::ref_ptr mRightCamera{ new osg::Camera }; + + // Camera viewports + bool flipViewOrder{ true }; + + // Updates stereo configuration during the update pass + std::shared_ptr cb{ new DefaultUpdateViewCallback }; + }; + + //! Overrides all stereo-related states/uniforms to disable stereo for the scene rendered by camera + void disableStereoForCamera(osg::Camera* camera); + + //! Overrides all stereo-related states/uniforms to enable stereo for the scene rendered by camera + void enableStereoForCamera(osg::Camera* camera, bool horizontalSplit); +} + +#endif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 3946fd5bf..b01b5d158 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -382,18 +382,30 @@ namespace Resource return false; static std::vector reservedNames; - if (reservedNames.empty()) + static std::mutex reservedNamesMutex; { - const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", - "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", - "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera"}; + std::lock_guard lock(reservedNamesMutex); + if (reservedNames.empty()) + { + // This keeps somehow accessing garbage so i rewrote it using safer types. + //const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", + // "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", + // "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera"}; - reservedNames = std::vector(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); + //reservedNames = std::vector(reserved, reserved + sizeof(reserved)/sizeof(const char*)); - for (unsigned int i=0; i r = { "Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", + "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", + "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera" }; + reservedNames = std::vector(r.begin(), r.end()); + for (auto& reservedName : r) + reservedNames.emplace_back(std::string("Tri ") + reservedName); + + std::sort(reservedNames.begin(), reservedNames.end(), Misc::StringUtils::ciLess); + } } std::vector::iterator it = Misc::StringUtils::partialBinarySearch(reservedNames.begin(), reservedNames.end(), name); @@ -763,7 +775,7 @@ namespace Resource Shader::ShaderVisitor *SceneManager::createShaderVisitor() { - Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl", "objects_geometry.glsl"); + Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); shaderVisitor->setForceShaders(mForceShaders); shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps); shaderVisitor->setNormalMapPattern(mNormalMapPattern); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index fa38da54e..564ad22b4 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -18,6 +18,8 @@ #include "mwshadowtechnique.hpp" +#include + #include #include #include @@ -575,6 +577,9 @@ MWShadowTechnique::ShadowData::ShadowData(MWShadowTechnique::ViewDependentData* // set viewport _camera->setViewport(0,0,textureSize.x(),textureSize.y()); + // Shadow casting should not obey indexed viewports + Misc::disableStereoForCamera(_camera); + if (debug) { diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 490c9d438..447f08879 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -15,7 +16,7 @@ namespace Shader { - void ShaderManager::setShaderPath(const std::string &path) + void ShaderManager::setShaderPath(const std::string& path) { mPath = path; } @@ -34,6 +35,9 @@ namespace Shader foundPos = source.find_first_of("\n\r", foundPos); foundPos = source.find_first_not_of("\n\r", foundPos); + if (foundPos == std::string::npos) + break; + size_t lineDirectivePosition = source.rfind("#line", foundPos); int lineNumber; if (lineDirectivePosition != std::string::npos) @@ -58,45 +62,39 @@ namespace Shader return true; } - bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& templateName) + // Recursively replaces include statements with the actual source of the included files. + // Adjusts #line statements accordingly and detects cyclic includes. + // includingFiles is the set of files that include this file directly or indirectly, and is intentionally not a reference to allow automatic cleanup. + static bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& fileName, int& fileNumber, std::set includingFiles) { + // An include is cyclic if it is being included by itself + if (includingFiles.insert(shaderPath / fileName).second == false) + { + Log(Debug::Error) << "Shader " << fileName << " error: Detected cyclic #includes"; + return false; + } + Misc::StringUtils::replaceAll(source, "\r\n", "\n"); - std::set includedFiles; size_t foundPos = 0; - int fileNumber = 1; while ((foundPos = source.find("#include")) != std::string::npos) { size_t start = source.find('"', foundPos); - if (start == std::string::npos || start == source.size()-1) + if (start == std::string::npos || start == source.size() - 1) { - Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include"; + Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include"; return false; } - size_t end = source.find('"', start+1); + size_t end = source.find('"', start + 1); if (end == std::string::npos) { - Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include"; + Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include"; return false; } - std::string includeFilename = source.substr(start+1, end-(start+1)); + std::string includeFilename = source.substr(start + 1, end - (start + 1)); boost::filesystem::path includePath = shaderPath / includeFilename; - boost::filesystem::ifstream includeFstream; - includeFstream.open(includePath); - if (includeFstream.fail()) - { - Log(Debug::Error) << "Shader " << templateName << " error: Failed to open include " << includePath.string(); - return false; - } - - std::stringstream buffer; - buffer << includeFstream.rdbuf(); - std::string stringRepresentation = buffer.str(); - addLineDirectivesAfterConditionalBlocks(stringRepresentation); - - // insert #line directives so we get correct line numbers in compiler errors - int includedFileNumber = fileNumber++; + // Determine the line number that will be used for the #line directive following the included source size_t lineDirectivePosition = source.rfind("#line", foundPos); int lineNumber; if (lineDirectivePosition != std::string::npos) @@ -113,20 +111,182 @@ namespace Shader } lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); + // Include the file recursively + boost::filesystem::ifstream includeFstream; + includeFstream.open(includePath); + if (includeFstream.fail()) + { + Log(Debug::Error) << "Shader " << fileName << " error: Failed to open include " << includePath.string(); + return false; + } + int includedFileNumber = fileNumber++; + + std::stringstream buffer; + buffer << includeFstream.rdbuf(); + std::string stringRepresentation = buffer.str(); + if (!addLineDirectivesAfterConditionalBlocks(stringRepresentation) + || !parseIncludes(shaderPath, stringRepresentation, includeFilename, fileNumber, includingFiles)) + { + Log(Debug::Error) << "In file included from " << fileName << "." << lineNumber; + return false; + } + std::stringstream toInsert; toInsert << "#line 0 " << includedFileNumber << "\n" << stringRepresentation << "\n#line " << lineNumber << " 0\n"; - source.replace(foundPos, (end-foundPos+1), toInsert.str()); - - if (includedFiles.insert(includePath).second == false) - { - Log(Debug::Error) << "Shader " << templateName << " error: Detected cyclic #includes"; - return false; - } + source.replace(foundPos, (end - foundPos + 1), toInsert.str()); } return true; } + struct DeclarationMeta + { + std::string interpolationType; + std::string interfaceKeyword; + std::string type; + std::string identifier; + std::string mangledIdentifier; + }; + + // Mangle identifiers of the interface declarations of this shader source, updating all identifiers, and returning a list of the declarations for use in generating + // a geometry shader. + // IN/OUT source: The source to mangle + // IN interfaceKeywordPattern: A regular expression matching all interface keywords to look for (e.g. "out|varying" when mangling output variables). Must not contain subexpressions. + // IN mangleString: Identifiers are mangled by prepending this string. Must be a valid identifier prefix. + // OUT declarations: All mangled declarations are added to this vector. Includes interpolation, interface, and type information as well as both the mangled and unmangled identifier. + static void mangleInterface(std::string& source, const std::string& interfaceKeywordPattern, const std::string& mangleString, std::vector& declarations) + { + std::string commentPattern = "//.*"; + std::regex commentRegex(commentPattern); + std::string commentlessSource = std::regex_replace(source, commentRegex, ""); + + std::string identifierPattern = "[a-zA-Z_][0-9a-zA-Z_]*"; + std::string declarationPattern = "(centroid|flat)?\\s*\\b(" + interfaceKeywordPattern + ")\\s+(" + identifierPattern + ")\\s+(" + identifierPattern + ")\\s*;"; + std::regex declarationRegex(declarationPattern); + + std::vector matches(std::sregex_iterator(commentlessSource.begin(), commentlessSource.end(), declarationRegex), std::sregex_iterator()); + std::string replacementPattern; + for (auto& match : matches) + { + declarations.emplace_back(DeclarationMeta{ match[1].str(), match[2].str(), match[3].str(), match[4].str(), mangleString + match[4].str() }); + if (!replacementPattern.empty()) + replacementPattern += "|"; + replacementPattern = replacementPattern + "(" + declarations.back().identifier + "\\b)"; + } + + if (!replacementPattern.empty()) + { + std::regex replacementRegex(replacementPattern); + source = std::regex_replace(source, replacementRegex, mangleString + "$&"); + } + } + + static std::string generateGeometryShader(const std::vector& declarations) + { + static const char* geometryTemplate = + "#version 150 compatibility\n" + "#extension GL_NV_viewport_array : enable\n" + "#extension GL_ARB_gpu_shader5 : enable\n" + "layout (triangles, invocations = 2) in;\n" + "layout (triangle_strip, max_vertices = 3) out;\n" + "\n" + "// Geometry Shader Inputs\n" + "@INPUTS\n" + "\n" + "// Geometry Shader Outputs\n" + "@OUTPUTS\n" + "\n" + "// Stereo matrices\n" + "uniform mat4 stereoViewMatrices[2];\n" + "uniform mat4 stereoViewProjections[2];\n" + "\n" + "void main() {\n" + " for(int i = 0; i < gl_in.length(); i++)\n" + " {\n" + " gl_ViewportIndex = gl_InvocationID;\n" + " // Re-project\n" + " gl_Position = stereoViewProjections[gl_InvocationID] * gl_in[i].gl_Position;\n" + " vec4 viewPos = stereoViewMatrices[gl_InvocationID] * gl_in[i].gl_Position;\n" + " gl_ClipVertex = vec4(viewPos.xyz,1);\n" + "\n" + " // Input -> output\n" + "@FORWARDING\n" + "\n" + " // TODO: deal with passNormal, depths, etc.\n" + "@EXTRA\n" + " EmitVertex();\n" + " }\n" + "\n" + " EndPrimitive();\n" + "}\n" + ; + + static std::map overriddenForwardStatements = + { + {"linearDepth", "linearDepth = gl_Position.z;"}, + {"euclideanDepth", "euclideanDepth = length(viewPos.xyz);"}, + {"passViewPos", "passViewPos = viewPos.xyz;"}, + }; + + std::stringstream ssInputDeclarations; + std::stringstream ssOutputDeclarations; + std::stringstream ssForwardStatements; + std::stringstream ssExtraStatements; + std::set identifiers; + for (auto& declaration : declarations) + { + if (!declaration.interpolationType.empty()) + { + ssInputDeclarations << declaration.interpolationType << " "; + ssOutputDeclarations << declaration.interpolationType << " "; + } + ssInputDeclarations << "in " << declaration.type << " " << declaration.mangledIdentifier << "[];\n"; + ssOutputDeclarations << "out " << declaration.type << " " << declaration.identifier << ";\n"; + + if (overriddenForwardStatements.count(declaration.identifier) > 0) + ssForwardStatements << overriddenForwardStatements[declaration.identifier] << ";\n"; + else + ssForwardStatements << " " << declaration.identifier << " = " << declaration.mangledIdentifier << "[i];\n"; + + identifiers.insert(declaration.identifier); + } + + Log(Debug::Verbose) << "Forward statements: \n" << ssForwardStatements.str(); + + // passViewPos output is required + //if (identifiers.find("passViewPos") == identifiers.end()) + //{ + // Log(Debug::Error) << "Vertex shader is missing 'vec3 passViewPos' on its interface. Geometry shader will NOT work."; + // return ""; + //} + + if (identifiers.find("screenCoordsPassthrough") != identifiers.end()) + { + // TODO: This corrects basic sampling but the screenCoordsOffset value in the fragment shader is still fucked. + static const char* screenCordsAssignmentCode = + " mat4 scalemat = mat4(0.25, 0.0, 0.0, 0.0,\n" + " 0.0, -0.5, 0.0, 0.0,\n" + " 0.0, 0.0, 0.5, 0.0,\n" + " 0.25, 0.5, 0.5, 1.0);\n" + " vec4 texcoordProj = ((scalemat) * (gl_Position));\n" + " screenCoordsPassthrough = texcoordProj.xyw;\n" + " if(gl_InvocationID == 1)\n" + " screenCoordsPassthrough.x += 0.5 * screenCoordsPassthrough.z;\n" + ; + ssExtraStatements << screenCordsAssignmentCode; + } + + //if() + + std::string geometryShader = geometryTemplate; + geometryShader = std::regex_replace(geometryShader, std::regex("@INPUTS"), ssInputDeclarations.str()); + geometryShader = std::regex_replace(geometryShader, std::regex("@OUTPUTS"), ssOutputDeclarations.str()); + geometryShader = std::regex_replace(geometryShader, std::regex("@FORWARDING"), ssForwardStatements.str()); + geometryShader = std::regex_replace(geometryShader, std::regex("@EXTRA"), ssExtraStatements.str()); + + return geometryShader; + } + bool parseFors(std::string& source, const std::string& templateName) { const char escapeCharacter = '$'; @@ -165,7 +325,7 @@ namespace Shader std::string list = source.substr(listStart, listEnd - listStart); std::vector listElements; if (list != "") - Misc::StringUtils::split (list, listElements, ","); + Misc::StringUtils::split(list, listElements, ","); size_t contentStart = source.find_first_not_of("\n\r", listEnd); size_t contentEnd = source.find("$endforeach", contentStart); @@ -224,7 +384,7 @@ namespace Shader Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } - std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); + std::string define = source.substr(foundPos + 1, endPos - (foundPos + 1)); ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); ShaderManager::DefineMap::const_iterator globalDefineFound = globalDefines.find(define); if (define == "foreach") @@ -271,7 +431,7 @@ namespace Shader return true; } - osg::ref_ptr ShaderManager::getShader(const std::string &templateName, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) + osg::ref_ptr ShaderManager::getShader(const std::string& templateName, const ShaderManager::DefineMap& defines, osg::Shader::Type shaderType) { std::lock_guard lock(mMutex); @@ -279,21 +439,22 @@ namespace Shader TemplateMap::iterator templateIt = mShaderTemplates.find(templateName); if (templateIt == mShaderTemplates.end()) { - boost::filesystem::path p = (boost::filesystem::path(mPath) / templateName); + boost::filesystem::path path = (boost::filesystem::path(mPath) / templateName); boost::filesystem::ifstream stream; - stream.open(p); + stream.open(path); if (stream.fail()) { - Log(Debug::Error) << "Failed to open " << p.string(); + Log(Debug::Error) << "Failed to open " << path.string(); return nullptr; } std::stringstream buffer; buffer << stream.rdbuf(); // parse includes + int fileNumber = 1; std::string source = buffer.str(); if (!addLineDirectivesAfterConditionalBlocks(source) - || !parseIncludes(boost::filesystem::path(mPath), source, templateName)) + || !parseIncludes(boost::filesystem::path(mPath), source, templateName, fileNumber, {})) return nullptr; templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first; @@ -310,12 +471,31 @@ namespace Shader return nullptr; } - osg::ref_ptr shader (new osg::Shader(shaderType)); - shader->setShaderSource(shaderSource); + osg::ref_ptr shader(new osg::Shader(shaderType)); // Assign a unique name to allow the SharedStateManager to compare shaders efficiently static unsigned int counter = 0; shader->setName(std::to_string(counter++)); + if (mGeometryShadersEnabled && defines.count("geometryShader") && defines.find("geometryShader")->second == "1" && shaderType == osg::Shader::VERTEX) + { + std::vector declarations; + mangleInterface(shaderSource, "out|varying", "vertex_", declarations); + std::string geometryShaderSource = generateGeometryShader(declarations); + if (!geometryShaderSource.empty()) + { + osg::ref_ptr geometryShader(new osg::Shader(osg::Shader::GEOMETRY)); + geometryShader->setShaderSource(geometryShaderSource); + geometryShader->setName(shader->getName() + ".geom"); + mGeometryShaders[shader] = geometryShader; + } + else + { + Log(Debug::Error) << "Failed to generate geometry shader for " << templateName; + } + } + + shader->setShaderSource(shaderSource); + shaderIt = mShaders.insert(std::make_pair(std::make_pair(templateName, defines), shader)).first; } return shaderIt->second; @@ -327,9 +507,16 @@ namespace Shader ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); if (found == mPrograms.end()) { - osg::ref_ptr program (new osg::Program); + osg::ref_ptr program(new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); + + auto git = mGeometryShaders.find(vertexShader); + if (git != mGeometryShaders.end()) + { + program->addShader(git->second); + } + found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; @@ -340,10 +527,15 @@ namespace Shader return DefineMap(mGlobalDefines); } - void ShaderManager::setGlobalDefines(DefineMap & globalDefines) + void ShaderManager::enableGeometryShader(bool enabled) + { + mGeometryShadersEnabled = enabled; + } + + void ShaderManager::setGlobalDefines(DefineMap& globalDefines) { mGlobalDefines = globalDefines; - for (auto shaderMapElement: mShaders) + for (auto shaderMapElement : mShaders) { std::string templateId = shaderMapElement.first.first; ShaderManager::DefineMap defines = shaderMapElement.first.second; @@ -360,7 +552,7 @@ namespace Shader } } - void ShaderManager::releaseGLObjects(osg::State *state) + void ShaderManager::releaseGLObjects(osg::State* state) { std::lock_guard lock(mMutex); for (auto shader : mShaders) diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index ed5bbc907..4e77c91ac 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -36,6 +36,13 @@ namespace Shader /// Get (a copy of) the DefineMap used to construct all shaders DefineMap getGlobalDefines(); + /// Enable or disable automatic stereo geometry shader. + /// If enabled, a stereo geometry shader will be automatically generated for any vertex shader + /// whose defines include "geometryShader" set to "1". + /// This geometry shader is automatically included in any program using that vertex shader. + /// \note Does not affect programs that have already been created, set this during startup. + void enableGeometryShader(bool enabled); + /// Set the DefineMap used to construct all shaders /// @param defines The DefineMap to use /// @note This will change the source code for any shaders already created, potentially causing problems if they're being used to render a frame. It is recommended that any associated Viewers have their threading stopped while this function is running if any shaders are in use. @@ -59,6 +66,10 @@ namespace Shader typedef std::map > ShaderMap; ShaderMap mShaders; + typedef std::map, osg::ref_ptr > GeometryShaderMap; + GeometryShaderMap mGeometryShaders; + bool mGeometryShadersEnabled{ false }; + typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; ProgramMap mPrograms; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 4ec1baf65..c690ace25 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -37,7 +37,7 @@ namespace Shader } - ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate, const std::string& defaultGsTemplate) + ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mForceShaders(false) , mAllowedToModifyStateSets(true) @@ -47,7 +47,6 @@ namespace Shader , mImageManager(imageManager) , mDefaultVsTemplate(defaultVsTemplate) , mDefaultFsTemplate(defaultFsTemplate) - , mDefaultGsTemplate(defaultGsTemplate) { mRequirements.push_back(ShaderRequirements()); } @@ -345,17 +344,16 @@ namespace Shader } defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; + defineMap["geometryShader"] = "1"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); - osg::ref_ptr geometryShader (mShaderManager.getShader(mDefaultGsTemplate, defineMap, osg::Shader::GEOMETRY)); - //osg::ref_ptr geometryShader = nullptr; if (vertexShader && fragmentShader) { - writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader, geometryShader), osg::StateAttribute::ON); + writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 719f0b923..8e35f1d9c 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -17,7 +17,7 @@ namespace Shader class ShaderVisitor : public osg::NodeVisitor { public: - ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate, const std::string& defaultGsTemplate); + ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); /// By default, only bump mapped objects will have a shader added to them. /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. @@ -89,7 +89,6 @@ namespace Shader std::string mDefaultVsTemplate; std::string mDefaultFsTemplate; - std::string mDefaultGsTemplate; void createProgram(const ShaderRequirements& reqs); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 4e7e77e63..0062825ba 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -232,10 +232,9 @@ namespace Terrain defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; + defineMap["geometryShader"] = "1"; osg::ref_ptr vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); - osg::ref_ptr geometryShader = shaderManager->getShader("terrain_geometry.glsl", defineMap, osg::Shader::GEOMETRY); - //osg::ref_ptr geometryShader = nullptr; osg::ref_ptr fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); if (!vertexShader || !fragmentShader) { @@ -243,7 +242,7 @@ namespace Terrain return createPasses(false, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); } - stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader, geometryShader)); + stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); stateset->addUniform(new osg::Uniform("colorMode", 2)); } else diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index cdc7689fa..8012c2bc1 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -11,21 +11,17 @@ set(SHADER_FILES water_fragment.glsl water_nm.png objects_vertex.glsl - objects_geometry.glsl objects_fragment.glsl terrain_vertex.glsl - terrain_geometry.glsl terrain_fragment.glsl lighting.glsl parallax.glsl s360_fragment.glsl s360_vertex.glsl shadows_vertex.glsl - shadows_geometry.glsl shadows_fragment.glsl shadowcasting_vertex.glsl shadowcasting_fragment.glsl - interface_util.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/interface_util.glsl b/files/shaders/interface_util.glsl deleted file mode 100644 index e325c9090..000000000 --- a/files/shaders/interface_util.glsl +++ /dev/null @@ -1,12 +0,0 @@ -// Because GLSL is an abomination we have to mangle the names of all -// vertex outputs when using a geometry shader. -#ifndef INTERFACE_UTIL_GLSL -#define INTERFACE_UTIL_GLSL - -#if 1 // Placeholder -#define VS_NAME(name) vertex_##name -#else -#define VS_NAME(name) name -#endif - -#endif // INTERFACE_UTIL_GLSL \ No newline at end of file diff --git a/files/shaders/objects_geometry.glsl b/files/shaders/objects_geometry.glsl deleted file mode 100644 index 965f1937e..000000000 --- a/files/shaders/objects_geometry.glsl +++ /dev/null @@ -1,17 +0,0 @@ -#version 330 core -#extension GL_NV_viewport_array : enable -#extension GL_ARB_gpu_shader5 : enable -layout (triangles, invocations = 2) in; -layout (triangle_strip, max_vertices = 3) out; - -#include "interface_util.glsl" - -void main() { - for(int i = 0; i < gl_in.length(); i++) - { - gl_ViewportIndex = gl_InvocationID; - gl_Position = gl_in[i].gl_Position; - EmitVertex(); - } - EndPrimitive(); -} \ No newline at end of file diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index c884e4870..40c448de9 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -1,7 +1,5 @@ #version 120 -#include "interface_util.glsl" - #if @diffuseMap varying vec2 diffuseMapUV; #endif diff --git a/files/shaders/shadows_geometry.glsl b/files/shaders/shadows_geometry.glsl deleted file mode 100644 index e69de29bb..000000000 diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index 99da45c97..7409ce045 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -110,6 +110,4 @@ void main() gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); applyShadowDebugOverlay(); - - //gl_FragData[0] = vec4(passNormal,1); } diff --git a/files/shaders/terrain_geometry.glsl b/files/shaders/terrain_geometry.glsl deleted file mode 100644 index bdb7fa2f7..000000000 --- a/files/shaders/terrain_geometry.glsl +++ /dev/null @@ -1,61 +0,0 @@ -#version 330 core -#extension GL_NV_viewport_array : enable -#extension GL_ARB_gpu_shader5 : enable -layout (triangles, invocations = 2) in; -layout (triangle_strip, max_vertices = 3) out; - -#include "interface_util.glsl" - -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) - -#if !PER_PIXEL_LIGHTING -centroid in vec4 VS_NAME(lighting)[]; -centroid in vec3 VS_NAME(shadowDiffuseLighting)[]; -centroid out vec4 lighting; -centroid out vec3 shadowDiffuseLighting; -#endif - -// TERRAIN INPUT -in vec2 VS_NAME(uv)[]; -in float VS_NAME(euclideanDepth)[]; -in float VS_NAME(linearDepth)[]; -centroid in vec4 VS_NAME(passColor)[]; -in vec3 VS_NAME(passViewPos)[]; -in vec3 VS_NAME(passNormal)[]; - -#if(@shadows_enabled) -#endif - -// TERRAIN OUTPUT -out vec2 uv; -out float euclideanDepth; -out float linearDepth; -centroid out vec4 passColor; -out vec3 passViewPos; -out vec3 passNormal; - -void main() { - for(int i = 0; i < gl_in.length(); i++) - { - gl_ViewportIndex = gl_InvocationID; - gl_Position = gl_in[i].gl_Position; - uv = VS_NAME(uv)[i]; - euclideanDepth = VS_NAME(euclideanDepth)[i]; - linearDepth = VS_NAME(linearDepth)[i]; - -#if !PER_PIXEL_LIGHTING - lighting = VS_NAME(lighting)[i]; - shadowDiffuseLighting = VS_NAME(shadowDiffuseLighting)[i]; -#endif - - passColor = VS_NAME(passColor)[i]; - passViewPos = VS_NAME(passViewPos)[i]; - passNormal = VS_NAME(passNormal)[i]; - -#if(@shadows_enabled) -#endif - - EmitVertex(); - } - EndPrimitive(); -} \ No newline at end of file diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 35cb426c2..71046a683 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,47 +1,44 @@ -#version 120 +#version 130 -#include "interface_util.glsl" - -varying vec2 VS_NAME(uv); -varying float VS_NAME(euclideanDepth); -varying float VS_NAME(linearDepth); +varying vec2 uv; +varying float euclideanDepth; +varying float linearDepth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING -centroid varying vec4 VS_NAME(lighting); -centroid varying vec3 VS_NAME(shadowDiffuseLighting); +centroid varying vec4 lighting; +centroid varying vec3 shadowDiffuseLighting; #endif -centroid varying vec4 VS_NAME(passColor); -varying vec3 VS_NAME(passViewPos); -varying vec3 VS_NAME(passNormal); +centroid varying vec4 passColor; +varying vec3 passViewPos; +varying vec3 passNormal; #include "shadows_vertex.glsl" #include "lighting.glsl" - void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; - VS_NAME(euclideanDepth) = length(viewPos.xyz); - VS_NAME(linearDepth) = gl_Position.z; + euclideanDepth = length(viewPos.xyz); + linearDepth = gl_Position.z; #if (!PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); #endif #if !PER_PIXEL_LIGHTING - VS_NAME(lighting) = doLighting(viewPos.xyz, viewNormal, gl_Color, VS_NAME(shadowDiffuseLighting)); + lighting = doLighting(viewPos.xyz, viewNormal, gl_Color, shadowDiffuseLighting); #endif - VS_NAME(passColor) = gl_Color; - VS_NAME(passNormal) = gl_Normal.xyz; - VS_NAME(passViewPos) = viewPos.xyz; + passColor = gl_Color; + passNormal = gl_Normal.xyz; + passViewPos = viewPos.xyz; - VS_NAME(uv) = gl_MultiTexCoord0.xy; + uv = gl_MultiTexCoord0.xy; #if (@shadows_enabled) setupShadowCoords(viewPos, viewNormal); From 943fe061738ad94804ccb1d5ee16817f33ddd21e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 14 Sep 2020 23:09:44 +0200 Subject: [PATCH 04/26] Cleaned up update callbacks. Rescued floating point accuracy of gl_Position --- components/misc/stereo.cpp | 85 ++++++++++++++--------------- components/misc/stereo.hpp | 3 +- components/shader/shadermanager.cpp | 44 ++++++--------- files/shaders/water_vertex.glsl | 5 +- 4 files changed, 64 insertions(+), 73 deletions(-) diff --git a/components/misc/stereo.cpp b/components/misc/stereo.cpp index de7cdd531..214bfd0cd 100644 --- a/components/misc/stereo.cpp +++ b/components/misc/stereo.cpp @@ -176,40 +176,27 @@ namespace Misc return os; } - /// Why are you like this - class TestCullCallback : public osg::NodeCallback - { - public: - TestCullCallback() {} - - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - //Log(Debug::Verbose) << "Cull: " << node->getName(); - osgUtil::CullVisitor* cv = static_cast(nv); - traverse(node, nv); - } - }; - + // Update stereo view/projection during update class StereoUpdateCallback : public osg::Callback { public: - StereoUpdateCallback(StereoView* node) : mNode(node) {} + StereoUpdateCallback(StereoView* stereoView) : stereoView(stereoView) {} bool run(osg::Object* object, osg::Object* data) override { - //Log(Debug::Verbose) << "StereoUpdateCallback"; auto b = traverse(object, data); - //mNode->update(); + stereoView->update(); return b; } - StereoView* mNode; + StereoView* stereoView; }; - class StereoUpdater : public SceneUtil::StateSetUpdater + // Update states during cull + class StereoStatesetUpdateCallback : public SceneUtil::StateSetUpdater { public: - StereoUpdater(StereoView* view) + StereoStatesetUpdateCallback(StereoView* view) : stereoView(view) { } @@ -227,7 +214,7 @@ namespace Misc virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) { - stereoView->update(stateset); + stereoView->updateStateset(stateset); } private: @@ -281,14 +268,21 @@ namespace Misc mStereoBruteForceRoot->addChild(mRightCamera); mRightCamera->addChild(mScene); viewer->setSceneData(this); - addCullCallback(new StereoUpdater(this)); - // Do a blank double buffering of camera statesets on update. StereoView::Update() apply actual changes during cull; + // Do a blank double buffering of camera statesets on update. Actual stateset updates are performed in StereoView::Update() mLeftCamera->setUpdateCallback(new SceneUtil::StateSetUpdater()); mRightCamera->setUpdateCallback(new SceneUtil::StateSetUpdater()); + + // Update stereo statesets/matrices, but after the main camera updates. + auto mainCameraCB = mMainCamera->getUpdateCallback(); + mMainCamera->removeUpdateCallback(mainCameraCB); + mMainCamera->addUpdateCallback(new StereoUpdateCallback(this)); + mMainCamera->addUpdateCallback(mainCameraCB); + + addCullCallback(new StereoStatesetUpdateCallback(this)); } - void StereoView::update(osg::StateSet* stateset) + void StereoView::update() { auto viewMatrix = mViewer->getCamera()->getViewMatrix(); auto projectionMatrix = mViewer->getCamera()->getProjectionMatrix(); @@ -315,18 +309,10 @@ namespace Misc osg::Matrix leftProjectionMatrix = left.fov.perspectiveMatrix(near, far); osg::Matrix rightProjectionMatrix = right.fov.perspectiveMatrix(near, far); - mRightCamera->setViewMatrix(leftViewMatrix); - mLeftCamera->setViewMatrix(rightViewMatrix); - mRightCamera->setProjectionMatrix(leftProjectionMatrix); - mLeftCamera->setProjectionMatrix(rightProjectionMatrix); - - // Manage viewports in update to automatically catch window/resolution changes. - auto width = mMainCamera->getViewport()->width(); - auto height = mMainCamera->getViewport()->height(); - mLeftCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height), osg::StateAttribute::OVERRIDE); - mRightCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, width / 2, 0, width / 2, height), osg::StateAttribute::OVERRIDE); - stateset->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height)); - stateset->setAttribute(new osg::ViewportIndexed(1, width / 2, 0, width / 2, height)); + mRightCamera->setViewMatrix(rightViewMatrix); + mLeftCamera->setViewMatrix(leftViewMatrix); + mRightCamera->setProjectionMatrix(rightProjectionMatrix); + mLeftCamera->setProjectionMatrix(leftProjectionMatrix); // The persepctive frustum will be computed from a position P slightly behind the eyes L and R // where it creates the minimum frustum encompassing both eyes' frustums. @@ -374,6 +360,7 @@ namespace Misc osg::Vec3d directionLP = osg::Vec3(std::cos(-angleLeft), std::sin(-angleLeft), 0); osg::Vec3d LP = directionLP * lengthLP; frustumView.pose.position = leftEye + LP; + //frustumView.pose.position.x() += 1000; // Base view position is 0.0, by definition. // The length of the vector P is therefore the required offset to near/far. @@ -382,23 +369,35 @@ namespace Misc // Generate the frustum matrices auto frustumViewMatrix = viewMatrix * frustumView.pose.viewMatrix(true); auto frustumProjectionMatrix = frustumView.fov.perspectiveMatrix(near + nearFarOffset, far + nearFarOffset); - auto frustumViewMatrixInverse = osg::Matrix::inverse(projectionMatrix) * osg::Matrix::inverse(viewMatrix); // Update camera with frustum matrices mMainCamera->setViewMatrix(frustumViewMatrix); mMainCamera->setProjectionMatrix(frustumProjectionMatrix); - mStereoGeometryShaderRoot->setStateSet(stateset); + auto width = mMainCamera->getViewport()->width(); + auto height = mMainCamera->getViewport()->height(); + mLeftCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height), osg::StateAttribute::OVERRIDE); + mRightCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, width / 2, 0, width / 2, height), osg::StateAttribute::OVERRIDE); + } + void StereoView::updateStateset(osg::StateSet * stateset) + { + // Manage viewports in update to automatically catch window/resolution changes. + auto width = mMainCamera->getViewport()->width(); + auto height = mMainCamera->getViewport()->height(); + stateset->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height)); + stateset->setAttribute(new osg::ViewportIndexed(1, width / 2, 0, width / 2, height)); - // Create and/or update stereo uniforms + // Update stereo uniforms + auto frustumViewMatrixInverse = osg::Matrix::inverse(mMainCamera->getViewMatrix()); + //auto frustumViewProjectionMatrixInverse = osg::Matrix::inverse(mMainCamera->getProjectionMatrix()) * osg::Matrix::inverse(mMainCamera->getViewMatrix()); auto* stereoViewMatrixUniform = stateset->getUniform("stereoViewMatrices"); auto* stereoViewProjectionsUniform = stateset->getUniform("stereoViewProjections"); - stereoViewMatrixUniform->setElement(1, frustumViewMatrixInverse * leftViewMatrix); - stereoViewMatrixUniform->setElement(0, frustumViewMatrixInverse * rightViewMatrix); - stereoViewProjectionsUniform->setElement(1, frustumViewMatrixInverse * rightViewMatrix * leftProjectionMatrix); - stereoViewProjectionsUniform->setElement(0, frustumViewMatrixInverse * rightViewMatrix * rightProjectionMatrix); + stereoViewMatrixUniform->setElement(0, frustumViewMatrixInverse * mLeftCamera->getViewMatrix()); + stereoViewMatrixUniform->setElement(1, frustumViewMatrixInverse * mRightCamera->getViewMatrix()); + stereoViewProjectionsUniform->setElement(0, frustumViewMatrixInverse * mLeftCamera->getViewMatrix() * mLeftCamera->getProjectionMatrix()); + stereoViewProjectionsUniform->setElement(1, frustumViewMatrixInverse * mRightCamera->getViewMatrix() * mRightCamera->getProjectionMatrix()); } void StereoView::setUpdateViewCallback(std::shared_ptr cb) diff --git a/components/misc/stereo.hpp b/components/misc/stereo.hpp index 2d2567f1c..a3ff227ed 100644 --- a/components/misc/stereo.hpp +++ b/components/misc/stereo.hpp @@ -86,7 +86,8 @@ namespace Misc //! Updates uniforms with the view and projection matrices of each stereo view, and replaces the camera's view and projection matrix //! with a view and projection that closely envelopes the frustums of the two eyes. - void update(osg::StateSet* stateset); + void update(); + void updateStateset(osg::StateSet* stateset); //! Callback that updates stereo configuration during the update pass void setUpdateViewCallback(std::shared_ptr cb); diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 447f08879..561971cda 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -205,15 +205,13 @@ namespace Shader " {\n" " gl_ViewportIndex = gl_InvocationID;\n" " // Re-project\n" - " gl_Position = stereoViewProjections[gl_InvocationID] * gl_in[i].gl_Position;\n" - " vec4 viewPos = stereoViewMatrices[gl_InvocationID] * gl_in[i].gl_Position;\n" + " gl_Position = stereoViewProjections[gl_InvocationID] * vec4(vertex_passViewPos[i],1);\n" + " vec4 viewPos = stereoViewMatrices[gl_InvocationID] * vec4(vertex_passViewPos[i],1);\n" " gl_ClipVertex = vec4(viewPos.xyz,1);\n" "\n" " // Input -> output\n" "@FORWARDING\n" "\n" - " // TODO: deal with passNormal, depths, etc.\n" - "@EXTRA\n" " EmitVertex();\n" " }\n" "\n" @@ -226,6 +224,17 @@ namespace Shader {"linearDepth", "linearDepth = gl_Position.z;"}, {"euclideanDepth", "euclideanDepth = length(viewPos.xyz);"}, {"passViewPos", "passViewPos = viewPos.xyz;"}, + { + "screenCoordsPassthrough", + " mat4 scalemat = mat4(0.25, 0.0, 0.0, 0.0,\n" + " 0.0, -0.5, 0.0, 0.0,\n" + " 0.0, 0.0, 0.5, 0.0,\n" + " 0.25, 0.5, 0.5, 1.0);\n" + " vec4 texcoordProj = ((scalemat) * (gl_Position));\n" + " screenCoordsPassthrough = texcoordProj.xyw;\n" + " if(gl_InvocationID == 1)\n" + " screenCoordsPassthrough.x += 0.5 * screenCoordsPassthrough.z;\n" + } }; std::stringstream ssInputDeclarations; @@ -251,38 +260,17 @@ namespace Shader identifiers.insert(declaration.identifier); } - Log(Debug::Verbose) << "Forward statements: \n" << ssForwardStatements.str(); - // passViewPos output is required - //if (identifiers.find("passViewPos") == identifiers.end()) - //{ - // Log(Debug::Error) << "Vertex shader is missing 'vec3 passViewPos' on its interface. Geometry shader will NOT work."; - // return ""; - //} - - if (identifiers.find("screenCoordsPassthrough") != identifiers.end()) + if (identifiers.find("passViewPos") == identifiers.end()) { - // TODO: This corrects basic sampling but the screenCoordsOffset value in the fragment shader is still fucked. - static const char* screenCordsAssignmentCode = - " mat4 scalemat = mat4(0.25, 0.0, 0.0, 0.0,\n" - " 0.0, -0.5, 0.0, 0.0,\n" - " 0.0, 0.0, 0.5, 0.0,\n" - " 0.25, 0.5, 0.5, 1.0);\n" - " vec4 texcoordProj = ((scalemat) * (gl_Position));\n" - " screenCoordsPassthrough = texcoordProj.xyw;\n" - " if(gl_InvocationID == 1)\n" - " screenCoordsPassthrough.x += 0.5 * screenCoordsPassthrough.z;\n" - ; - ssExtraStatements << screenCordsAssignmentCode; + Log(Debug::Error) << "Vertex shader is missing 'vec3 passViewPos' on its interface. Geometry shader will NOT work."; + return ""; } - //if() - std::string geometryShader = geometryTemplate; geometryShader = std::regex_replace(geometryShader, std::regex("@INPUTS"), ssInputDeclarations.str()); geometryShader = std::regex_replace(geometryShader, std::regex("@OUTPUTS"), ssOutputDeclarations.str()); geometryShader = std::regex_replace(geometryShader, std::regex("@FORWARDING"), ssForwardStatements.str()); - geometryShader = std::regex_replace(geometryShader, std::regex("@EXTRA"), ssExtraStatements.str()); return geometryShader; } diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index 02a395f95..c099e4210 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -3,12 +3,15 @@ varying vec3 screenCoordsPassthrough; varying vec4 position; varying float linearDepth; +varying vec3 passViewPos; #include "shadows_vertex.glsl" void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + vec4 viewPos = gl_ModelViewMatrix * gl_Vertex; + passViewPos = viewPos.xyz; mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, @@ -22,5 +25,5 @@ void main(void) linearDepth = gl_Position.z; - setupShadowCoords(gl_ModelViewMatrix * gl_Vertex, normalize((gl_NormalMatrix * gl_Normal).xyz)); + setupShadowCoords(viewPos, normalize((gl_NormalMatrix * gl_Normal).xyz)); } From 29524631129a1b0851aa37e6f677b84db4466e41 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 19 Sep 2020 20:55:06 +0200 Subject: [PATCH 05/26] Configurability. Brute force option. --- apps/opencs/view/render/scenewidget.cpp | 7 +- apps/openmw/engine.cpp | 11 +- apps/openmw/engine.hpp | 1 + apps/openmw/mwrender/water.cpp | 16 +- components/misc/stereo.cpp | 267 ++++++++++++++--------- components/misc/stereo.hpp | 25 ++- components/sdlutil/sdlgraphicswindow.cpp | 19 ++ components/sdlutil/sdlgraphicswindow.hpp | 3 + components/sdlutil/sdlinputwrapper.cpp | 3 +- components/shader/shadermanager.cpp | 7 +- components/shader/shadermanager.hpp | 3 +- files/settings-default.cfg | 11 + 12 files changed, 255 insertions(+), 118 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index f3186e76a..9ef068a28 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "../widget/scenetoolmode.hpp" @@ -106,7 +107,7 @@ RenderWidget::~RenderWidget() // before OSG 3.6.4, the default font was a static object, and if it wasn't attached to the scene when a graphics context was destroyed, it's program wouldn't be released. // 3.6.4 moved it into the object cache, which meant it usually got released, but not here. // 3.6.5 improved cleanup with osgViewer::CompositeViewer::removeView so it more reliably released associated state for objects in the object cache. - osg::ref_ptr graphicsContext = mView->getCamera()->getGraphicsContext(); + osg::ref_ptr graphicsContext = SDLUtil::GraphicsWindowSDL2::findContext(*mView); osgText::Font::getDefaultFont()->releaseGLObjects(graphicsContext->getState()); #endif } @@ -134,7 +135,7 @@ osg::Camera *RenderWidget::getCamera() void RenderWidget::toggleRenderStats() { osgViewer::GraphicsWindow* window = - static_cast(mView->getCamera()->getGraphicsContext()); + static_cast(SDLUtil::GraphicsWindowSDL2::findContext(*mView)); window->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_S); window->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_S); @@ -246,7 +247,7 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste SceneWidget::~SceneWidget() { // Since we're holding on to the resources past the existence of this graphics context, we'll need to manually release the created objects - mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); + mResourceSystem->releaseGLObjects(SDLUtil::GraphicsWindowSDL2::findContext(*mView)->getState()); } void SceneWidget::setLighting(Lighting *lighting) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 6f3475e9b..ba2476754 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -365,6 +365,8 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(nullptr) , mScreenCaptureOperation(nullptr) + , mStereoEnabled(false) + , mStereoOverride(false) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) @@ -398,6 +400,8 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) OMW::Engine::~Engine() { + mStereoView = nullptr; + mEnvironment.cleanup(); delete mScriptContext; @@ -696,13 +700,14 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) window->playVideo(logo, true); } - mStereoEnabled = true; //!< TODO: TEMP + // VR mode will override this setting by setting mStereoOverride. + mStereoEnabled = mStereoOverride || Settings::Manager::getBool("stereo enabled", "Stereo"); // geometry shader must be enabled before the RenderingManager sets up any shaders // therefore this part is separate from the rest of stereo setup. if (mStereoEnabled) { - mResourceSystem->getSceneManager()->getShaderManager().enableGeometryShader(true); + mResourceSystem->getSceneManager()->getShaderManager().setStereoGeometryShaderEnabled(Misc::getStereoTechnique() == Misc::StereoView::Technique::GeometryShader_IndexedViewports); } // Create the world @@ -718,7 +723,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Remove that altogether when the sky finally uses them. auto noShaderMask = MWRender::VisMask::Mask_Sky | MWRender::VisMask::Mask_Sun | MWRender::VisMask::Mask_WeatherParticles; auto geometryShaderMask = mViewer->getCamera()->getCullMask() & ~noShaderMask; - mStereoView.reset(new Misc::StereoView(mViewer, geometryShaderMask, noShaderMask | MWRender::VisMask::Mask_Scene)); + mStereoView.reset(new Misc::StereoView(mViewer, Misc::getStereoTechnique(), geometryShaderMask, noShaderMask | MWRender::VisMask::Mask_Scene)); } window->setStore(mEnvironment.getWorld()->getStore()); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index d6ccf83a5..82e85934e 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -92,6 +92,7 @@ namespace OMW std::vector mContentFiles; bool mStereoEnabled; + bool mStereoOverride; std::unique_ptr mStereoView; bool mSkipMenu; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 58bbb8f37..c1c3e84a9 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -248,7 +248,6 @@ public: setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); - Misc::enableStereoForCamera(this, true); // No need for Update traversal since the scene is already updated as part of the main scene graph // A double update would mess with the light collection (in addition to being plain redundant) @@ -344,7 +343,6 @@ public: unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); setViewport(0, 0, rttSize, rttSize); - Misc::enableStereoForCamera(this, true); // No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph @@ -598,7 +596,19 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R // use a define map to conditionally compile the shader std::map defineMap; defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0"))); - defineMap["geometryShader"] = "1"; + + if (mResourceSystem->getSceneManager()->getShaderManager().stereoGeometryShaderEnabled()) + { + defineMap["geometryShader"] = "1"; + if (reflection) + { + Misc::enableStereoForCamera(reflection, true); + } + if (refraction) + { + Misc::enableStereoForCamera(refraction, true); + } + } Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr vertexShader (shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); diff --git a/components/misc/stereo.cpp b/components/misc/stereo.cpp index 214bfd0cd..b57c71b01 100644 --- a/components/misc/stereo.cpp +++ b/components/misc/stereo.cpp @@ -1,4 +1,5 @@ #include "stereo.hpp" +#include "stringops.hpp" #include #include @@ -10,9 +11,12 @@ #include #include + #include #include +#include + namespace Misc { Pose Pose::operator+(const Pose& rhs) @@ -221,65 +225,110 @@ namespace Misc StereoView* stereoView; }; - StereoView::StereoView(osgViewer::Viewer* viewer, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask bruteForceMask) + StereoView::StereoView(osgViewer::Viewer* viewer, Technique technique, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask noShaderMask) : osg::Group() , mViewer(viewer) , mMainCamera(mViewer->getCamera()) , mRoot(viewer->getSceneData()->asGroup()) + , mTechnique(technique) , mGeometryShaderMask(geometryShaderMask) - , mBruteForceMask(bruteForceMask) + , mNoShaderMask(noShaderMask) { + if (technique == Technique::None) + // Do nothing + return; + SceneUtil::FindByNameVisitor findScene("Scene Root"); mRoot->accept(findScene); mScene = findScene.mFoundNode; if (!mScene) throw std::logic_error("Couldn't find scene root"); - setName("Sky Root"); + setName("Stereo Root"); mRoot->setDataVariance(osg::Object::STATIC); setDataVariance(osg::Object::STATIC); mLeftCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); mLeftCamera->setProjectionResizePolicy(osg::Camera::FIXED); mLeftCamera->setProjectionMatrix(osg::Matrix::identity()); mLeftCamera->setViewMatrix(osg::Matrix::identity()); - mLeftCamera->setRenderOrder(osg::Camera::NESTED_RENDER); - mLeftCamera->setClearMask(GL_NONE); - mLeftCamera->setCullMask(bruteForceMask); mLeftCamera->setName("Stereo Left"); mLeftCamera->setDataVariance(osg::Object::STATIC); mRightCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); mRightCamera->setProjectionResizePolicy(osg::Camera::FIXED); mRightCamera->setProjectionMatrix(osg::Matrix::identity()); mRightCamera->setViewMatrix(osg::Matrix::identity()); - mRightCamera->setRenderOrder(osg::Camera::NESTED_RENDER); - mRightCamera->setClearMask(GL_NONE); - mRightCamera->setCullMask(bruteForceMask); mRightCamera->setName("Stereo Right"); mRightCamera->setDataVariance(osg::Object::STATIC); - mMainCamera->setCullMask(geometryShaderMask); - - // Inject self as the root of the scene graph, and split into geometry-shader stereo and brute force stereo. - addChild(mStereoGeometryShaderRoot); - mStereoGeometryShaderRoot->addChild(mRoot); - addChild(mStereoBruteForceRoot); - mStereoBruteForceRoot->addChild(mLeftCamera); - mLeftCamera->addChild(mScene); // Use scene directly to avoid redundant shadow computation. - mStereoBruteForceRoot->addChild(mRightCamera); - mRightCamera->addChild(mScene); - viewer->setSceneData(this); - - // Do a blank double buffering of camera statesets on update. Actual stateset updates are performed in StereoView::Update() - mLeftCamera->setUpdateCallback(new SceneUtil::StateSetUpdater()); - mRightCamera->setUpdateCallback(new SceneUtil::StateSetUpdater()); - // Update stereo statesets/matrices, but after the main camera updates. auto mainCameraCB = mMainCamera->getUpdateCallback(); mMainCamera->removeUpdateCallback(mainCameraCB); mMainCamera->addUpdateCallback(new StereoUpdateCallback(this)); mMainCamera->addUpdateCallback(mainCameraCB); + // Do a blank double buffering of camera statesets on update. Actual state updates are performed in StereoView::Update() + mLeftCamera->setUpdateCallback(new SceneUtil::StateSetUpdater()); + mRightCamera->setUpdateCallback(new SceneUtil::StateSetUpdater()); + + + if (mTechnique == Technique::GeometryShader_IndexedViewports) + { + setupGeometryShaderIndexedViewportTechnique(); + } + else + { + setupBruteForceTechnique(); + } + } + + void StereoView::setupBruteForceTechnique() + { + mLeftCamera->setRenderOrder(osg::Camera::NESTED_RENDER); + mLeftCamera->setClearColor(mMainCamera->getClearColor()); + mLeftCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + mLeftCamera->setCullMask(mMainCamera->getCullMask()); + mRightCamera->setRenderOrder(osg::Camera::NESTED_RENDER); + mRightCamera->setClearColor(mMainCamera->getClearColor()); + mRightCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + mRightCamera->setCullMask(mMainCamera->getCullMask()); + + // Slave cameras must have their viewports defined immediately + auto width = mMainCamera->getViewport()->width(); + auto height = mMainCamera->getViewport()->height(); + mLeftCamera->setViewport(0, 0, width / 2, height); + mRightCamera->setViewport(width / 2, 0, width / 2, height); + + mViewer->stopThreading(); + mViewer->addSlave(mLeftCamera, true); + mViewer->addSlave(mRightCamera, true); + mRightCamera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext()); + mLeftCamera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext()); + mViewer->getCamera()->setGraphicsContext(nullptr); + mViewer->realize(); + } + + void StereoView::setupGeometryShaderIndexedViewportTechnique() + { + mLeftCamera->setRenderOrder(osg::Camera::NESTED_RENDER); + mLeftCamera->setClearMask(GL_NONE); + mLeftCamera->setCullMask(mNoShaderMask); + mRightCamera->setRenderOrder(osg::Camera::NESTED_RENDER); + mRightCamera->setClearMask(GL_NONE); + mRightCamera->setCullMask(mNoShaderMask); + mMainCamera->setCullMask(mGeometryShaderMask); + + addChild(mStereoGeometryShaderRoot); + mStereoGeometryShaderRoot->addChild(mRoot); + addChild(mStereoBruteForceRoot); + mStereoBruteForceRoot->addChild(mLeftCamera); + mLeftCamera->addChild(mScene); // Use scene directly to avoid redundant shadow computation. + mStereoBruteForceRoot->addChild(mRightCamera); + mRightCamera->addChild(mScene); + addCullCallback(new StereoStatesetUpdateCallback(this)); + + // Inject self as the root of the scene graph + mViewer->setSceneData(this); } void StereoView::update() @@ -314,70 +363,83 @@ namespace Misc mRightCamera->setProjectionMatrix(rightProjectionMatrix); mLeftCamera->setProjectionMatrix(leftProjectionMatrix); - // The persepctive frustum will be computed from a position P slightly behind the eyes L and R - // where it creates the minimum frustum encompassing both eyes' frustums. - // NOTE: I make an assumption that the eyes lie in a horizontal plane relative to the base view, - // and lie mirrored around the Y axis (straight ahead). - // Re-think this if that turns out to be a bad assumption - View frustumView; - - // Compute Frustum angles. A simple min/max. - /* Example values for reference: - Left: - angleLeft -0.767549932 float - angleRight 0.620896876 float - angleDown -0.837898076 float - angleUp 0.726982594 float - - Right: - angleLeft -0.620896876 float - angleRight 0.767549932 float - angleDown -0.837898076 float - angleUp 0.726982594 float - */ - frustumView.fov.angleLeft = std::min(left.fov.angleLeft, right.fov.angleLeft); - frustumView.fov.angleRight = std::max(left.fov.angleRight, right.fov.angleRight); - frustumView.fov.angleDown = std::min(left.fov.angleDown, right.fov.angleDown); - frustumView.fov.angleUp = std::max(left.fov.angleUp, right.fov.angleUp); - - // Check that the case works for this approach - auto maxAngle = std::max(frustumView.fov.angleRight - frustumView.fov.angleLeft, frustumView.fov.angleUp - frustumView.fov.angleDown); - if (maxAngle > osg::PI) - { - Log(Debug::Error) << "Total FOV exceeds 180 degrees. Case cannot be culled in single-pass VR. Disabling culling to cope. Consider switching to dual-pass VR."; - mMainCamera->setCullingActive(false); - return; - // TODO: An explicit frustum projection could cope, so implement that later. Guarantee you there will be VR headsets with total fov > 180 in the future. Maybe already. - } - - // Use the law of sines on the triangle spanning PLR to determine P - double angleLeft = std::abs(frustumView.fov.angleLeft); - double angleRight = std::abs(frustumView.fov.angleRight); - double lengthRL = (rightEye - leftEye).length(); - double ratioRL = lengthRL / std::sin(osg::PI - angleLeft - angleRight); - double lengthLP = ratioRL * std::sin(angleRight); - - osg::Vec3d directionLP = osg::Vec3(std::cos(-angleLeft), std::sin(-angleLeft), 0); - osg::Vec3d LP = directionLP * lengthLP; - frustumView.pose.position = leftEye + LP; - //frustumView.pose.position.x() += 1000; - - // Base view position is 0.0, by definition. - // The length of the vector P is therefore the required offset to near/far. - auto nearFarOffset = frustumView.pose.position.length(); - - // Generate the frustum matrices - auto frustumViewMatrix = viewMatrix * frustumView.pose.viewMatrix(true); - auto frustumProjectionMatrix = frustumView.fov.perspectiveMatrix(near + nearFarOffset, far + nearFarOffset); - - // Update camera with frustum matrices - mMainCamera->setViewMatrix(frustumViewMatrix); - mMainCamera->setProjectionMatrix(frustumProjectionMatrix); - auto width = mMainCamera->getViewport()->width(); auto height = mMainCamera->getViewport()->height(); - mLeftCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height), osg::StateAttribute::OVERRIDE); - mRightCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, width / 2, 0, width / 2, height), osg::StateAttribute::OVERRIDE); + + if (mTechnique == Technique::GeometryShader_IndexedViewports) + { + // To correctly cull when drawing stereo using the geometry shader, the main camera must + // draw a fake view+perspective that includes the full frustums of both the left and right eyes. + // This frustum will be computed as a perspective frustum from a position P slightly behind the eyes L and R + // where it creates the minimum frustum encompassing both eyes' frustums. + // NOTE: I make an assumption that the eyes lie in a horizontal plane relative to the base view, + // and lie mirrored around the Y axis (straight ahead). + // Re-think this if that turns out to be a bad assumption. + View frustumView; + + // Compute Frustum angles. A simple min/max. + /* Example values for reference: + Left: + angleLeft -0.767549932 float + angleRight 0.620896876 float + angleDown -0.837898076 float + angleUp 0.726982594 float + + Right: + angleLeft -0.620896876 float + angleRight 0.767549932 float + angleDown -0.837898076 float + angleUp 0.726982594 float + */ + frustumView.fov.angleLeft = std::min(left.fov.angleLeft, right.fov.angleLeft); + frustumView.fov.angleRight = std::max(left.fov.angleRight, right.fov.angleRight); + frustumView.fov.angleDown = std::min(left.fov.angleDown, right.fov.angleDown); + frustumView.fov.angleUp = std::max(left.fov.angleUp, right.fov.angleUp); + + // Check that the case works for this approach + auto maxAngle = std::max(frustumView.fov.angleRight - frustumView.fov.angleLeft, frustumView.fov.angleUp - frustumView.fov.angleDown); + if (maxAngle > osg::PI) + { + Log(Debug::Error) << "Total FOV exceeds 180 degrees. Case cannot be culled in single-pass VR. Disabling culling to cope. Consider switching to dual-pass VR."; + mMainCamera->setCullingActive(false); + return; + // TODO: An explicit frustum projection could cope, so implement that later. Guarantee you there will be VR headsets with total fov > 180 in the future. Maybe already. + } + + // Use the law of sines on the triangle spanning PLR to determine P + double angleLeft = std::abs(frustumView.fov.angleLeft); + double angleRight = std::abs(frustumView.fov.angleRight); + double lengthRL = (rightEye - leftEye).length(); + double ratioRL = lengthRL / std::sin(osg::PI - angleLeft - angleRight); + double lengthLP = ratioRL * std::sin(angleRight); + + osg::Vec3d directionLP = osg::Vec3(std::cos(-angleLeft), std::sin(-angleLeft), 0); + osg::Vec3d LP = directionLP * lengthLP; + frustumView.pose.position = leftEye + LP; + //frustumView.pose.position.x() += 1000; + + // Base view position is 0.0, by definition. + // The length of the vector P is therefore the required offset to near/far. + auto nearFarOffset = frustumView.pose.position.length(); + + // Generate the frustum matrices + auto frustumViewMatrix = viewMatrix * frustumView.pose.viewMatrix(true); + auto frustumProjectionMatrix = frustumView.fov.perspectiveMatrix(near + nearFarOffset, far + nearFarOffset); + + // Update camera with frustum matrices + mMainCamera->setViewMatrix(frustumViewMatrix); + mMainCamera->setProjectionMatrix(frustumProjectionMatrix); + mLeftCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height), osg::StateAttribute::OVERRIDE); + mRightCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, width / 2, 0, width / 2, height), osg::StateAttribute::OVERRIDE); + } + else + { + mLeftCamera->setClearColor(mMainCamera->getClearColor()); + mRightCamera->setClearColor(mMainCamera->getClearColor()); + + mLeftCamera->setViewport(0, 0, width / 2, height); + mRightCamera->setViewport(width / 2, 0, width / 2, height); + } } void StereoView::updateStateset(osg::StateSet * stateset) @@ -405,18 +467,6 @@ namespace Misc this->cb = cb; } - void StereoView::useSlaveCameraAtIndex(int index) - { - if (mViewer->getNumSlaves() <= index) - { - Log(Debug::Error) << "Requested slave at index " << index << " but no such slave exists"; - return; - } - - mMainCamera = mViewer->getSlave(index)._camera; - mMainCamera->setCullMask(mGeometryShaderMask); - } - void disableStereoForCamera(osg::Camera* camera) { auto* viewport = camera->getViewport(); @@ -450,6 +500,23 @@ namespace Misc camera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(1, x2, y2, width, height)); camera->getOrCreateStateSet()->addUniform(new osg::Uniform("geometryPassthrough", false)); } + + StereoView::Technique getStereoTechnique(void) + { + auto stereoMethodString = Settings::Manager::getString("stereo method", "Stereo"); + auto stereoMethodStringLowerCase = Misc::StringUtils::lowerCase(stereoMethodString); + if (stereoMethodStringLowerCase == "geometryshader") + { + return Misc::StereoView::Technique::GeometryShader_IndexedViewports; + } + if (stereoMethodStringLowerCase == "bruteforce") + { + return Misc::StereoView::Technique::BruteForce; + } + Log(Debug::Warning) << "Unknown stereo technique \"" << stereoMethodString << "\", defaulting to BruteForce"; + return StereoView::Technique::BruteForce; + } + void StereoView::DefaultUpdateViewCallback::updateView(View& left, View& right, double& near, double& far) { left.pose.position = osg::Vec3(-2.2, 0, 0); @@ -457,6 +524,6 @@ namespace Misc left.fov = { -0.767549932, 0.620896876, -0.837898076, 0.726982594 }; right.fov = { -0.620896876, 0.767549932, -0.837898076, 0.726982594 }; near = 1; - far = 10000; + far = 6656; } } diff --git a/components/misc/stereo.hpp b/components/misc/stereo.hpp index a3ff227ed..09fde0f36 100644 --- a/components/misc/stereo.hpp +++ b/components/misc/stereo.hpp @@ -78,11 +78,19 @@ namespace Misc virtual void updateView(View& left, View& right, double& near, double& far); }; + enum class Technique + { + None = 0, //!< Stereo disabled (do nothing). + BruteForce, //!< Two slave cameras culling and drawing everything. + GeometryShader_IndexedViewports, //!< Frustum camera culls and draws stereo into indexed viewports using an automatically generated geometry shader. + }; + //! Adds two cameras in stereo to the mainCamera. //! All nodes matching the mask are rendered in stereo using brute force via two camera transforms, the rest are rendered in stereo via a geometry shader. - //! \note The mask is removed from the mainCamera, so do not put Scene in this mask. - //! \note Brute force does not support shadows. But that's fine because currently this only applies to things that don't use shaders and that's only the sky, which will use shaders in the future. - StereoView(osgViewer::Viewer* viewer, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask bruteForceMask); + //! \param geometryShaderMask should mask in all nodes that use shaders. + //! \param noShaderMask mask in all nodes that do not use shaders and must be rendered brute force. + //! \note the masks apply only to the GeometryShader_IndexdViewports technique and can be 0 for the BruteForce technique. + StereoView(osgViewer::Viewer* viewer, Technique technique, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask noShaderMask); //! Updates uniforms with the view and projection matrices of each stereo view, and replaces the camera's view and projection matrix //! with a view and projection that closely envelopes the frustums of the two eyes. @@ -92,21 +100,23 @@ namespace Misc //! Callback that updates stereo configuration during the update pass void setUpdateViewCallback(std::shared_ptr cb); - //! Use the slave camera at index instead of the main viewer camera. - void useSlaveCameraAtIndex(int index); + private: + void setupBruteForceTechnique(); + void setupGeometryShaderIndexedViewportTechnique(); osg::ref_ptr mViewer; osg::ref_ptr mMainCamera; osg::ref_ptr mRoot; osg::ref_ptr mScene; + Technique mTechnique; // Keeps state relevant to doing stereo via the geometry shader osg::ref_ptr mStereoGeometryShaderRoot{ new osg::Group }; osg::Node::NodeMask mGeometryShaderMask; + osg::Node::NodeMask mNoShaderMask; // Keeps state and cameras relevant to doing stereo via brute force osg::ref_ptr mStereoBruteForceRoot{ new osg::Group }; - osg::Node::NodeMask mBruteForceMask; osg::ref_ptr mLeftCamera{ new osg::Camera }; osg::ref_ptr mRightCamera{ new osg::Camera }; @@ -122,6 +132,9 @@ namespace Misc //! Overrides all stereo-related states/uniforms to enable stereo for the scene rendered by camera void enableStereoForCamera(osg::Camera* camera, bool horizontalSplit); + + //! Reads settings to determine stereo technique + StereoView::Technique getStereoTechnique(void); } #endif diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index cd5e80c31..b6ed4d941 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -1,5 +1,7 @@ #include "sdlgraphicswindow.hpp" +#include + #include namespace SDLUtil @@ -202,6 +204,23 @@ void GraphicsWindowSDL2::setSyncToVBlank(bool on) SDL_GL_MakeCurrent(oldWin, oldCtx); } +osg::GraphicsContext* GraphicsWindowSDL2::findContext(osgViewer::View& view) +{ + view.getCamera(); + if (view.getCamera()->getGraphicsContext()) + { + return view.getCamera()->getGraphicsContext(); + } + + for (auto i = 0; i < view.getNumSlaves(); i++) + { + if (view.getSlave(i)._camera->getGraphicsContext()) + return view.getSlave(i)._camera->getGraphicsContext(); + } + + return nullptr; +} + void GraphicsWindowSDL2::setSwapInterval(bool enable) { if (enable) diff --git a/components/sdlutil/sdlgraphicswindow.hpp b/components/sdlutil/sdlgraphicswindow.hpp index dd8076776..1b86a3b49 100644 --- a/components/sdlutil/sdlgraphicswindow.hpp +++ b/components/sdlutil/sdlgraphicswindow.hpp @@ -81,6 +81,9 @@ public: SDL_Window *mWindow; }; + /** Convenience function for finding the context among the main camera or slaves */ + static osg::GraphicsContext* findContext(osgViewer::View& view); + private: void setSwapInterval(bool enable); }; diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 8d6a124e2..99e2b5ae2 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -1,4 +1,5 @@ #include "sdlinputwrapper.hpp" +#include "sdlgraphicswindow.hpp" #include #include @@ -213,7 +214,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v SDL_GetWindowSize(mSDLWindow, &w, &h); int x,y; SDL_GetWindowPosition(mSDLWindow, &x,&y); - mViewer->getCamera()->getGraphicsContext()->resized(x,y,w,h); + GraphicsWindowSDL2::findContext(*mViewer)->resized(x,y,w,h); mViewer->getEventQueue()->windowResize(x,y,w,h); diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 561971cda..e3db52a3f 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -515,11 +515,16 @@ namespace Shader return DefineMap(mGlobalDefines); } - void ShaderManager::enableGeometryShader(bool enabled) + void ShaderManager::setStereoGeometryShaderEnabled(bool enabled) { mGeometryShadersEnabled = enabled; } + bool ShaderManager::stereoGeometryShaderEnabled() const + { + return mGeometryShadersEnabled; + } + void ShaderManager::setGlobalDefines(DefineMap& globalDefines) { mGlobalDefines = globalDefines; diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 4e77c91ac..d76f82fa9 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -41,7 +41,8 @@ namespace Shader /// whose defines include "geometryShader" set to "1". /// This geometry shader is automatically included in any program using that vertex shader. /// \note Does not affect programs that have already been created, set this during startup. - void enableGeometryShader(bool enabled); + void setStereoGeometryShaderEnabled(bool enabled); + bool stereoGeometryShaderEnabled() const; /// Set the DefineMap used to construct all shaders /// @param defines The DefineMap to use diff --git a/files/settings-default.cfg b/files/settings-default.cfg index a9777fc42..cc5a87cf4 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -901,3 +901,14 @@ object shadows = false # Allow shadows indoors. Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting. enable indoor shadows = true + +[Stereo] + +# Enable/disable stereo view. This setting is ignored in VR. +stereo enabled = false + +# Method used to render stereo if enabled +# Must be one of the following: BruteForce, GeometryShader +# BruteForce: Generates stereo using two cameras and two cull/render passes. Choose this if your game is GPU-bound. +# GeometryShader: Generates stereo in a single pass using automatically generated geometry shaders. May break custom shaders. Choose this if your game is CPU-bound. +stereo method = GeometryShader From 801f1ccc2ed1ff08dc3a7c731325f6b8a6732f5c Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 7 Oct 2020 20:25:51 +0200 Subject: [PATCH 06/26] The GL_ARB_gpu_shader5 geometry shader path is broken on Vega 56, default to slightly slower old fashioned path for now. --- components/shader/shadermanager.cpp | 63 ++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index e3db52a3f..9786d1ca1 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -185,10 +185,20 @@ namespace Shader { static const char* geometryTemplate = "#version 150 compatibility\n" - "#extension GL_NV_viewport_array : enable\n" - "#extension GL_ARB_gpu_shader5 : enable\n" - "layout (triangles, invocations = 2) in;\n" - "layout (triangle_strip, max_vertices = 3) out;\n" + "#extension GL_ARB_viewport_array : require\n" + //"#ifdef GL_ARB_gpu_shader5\n" // Ref: AnyOldName3: This slightly faster path is broken on Vega 56 + "#if 0\n" + " #extension GL_ARB_gpu_shader5 : enable\n" + " #define ENABLE_GL_ARB_gpu_shader5\n" + "#endif\n" + "\n" + "#ifdef ENABLE_GL_ARB_gpu_shader5\n" + " layout (triangles, invocations = 2) in;\n" + " layout (triangle_strip, max_vertices = 3) out;\n" + "#else\n" + " layout (triangles) in;\n" + " layout (triangle_strip, max_vertices = 6) out;\n" + "#endif\n" "\n" "// Geometry Shader Inputs\n" "@INPUTS\n" @@ -200,22 +210,37 @@ namespace Shader "uniform mat4 stereoViewMatrices[2];\n" "uniform mat4 stereoViewProjections[2];\n" "\n" - "void main() {\n" - " for(int i = 0; i < gl_in.length(); i++)\n" - " {\n" - " gl_ViewportIndex = gl_InvocationID;\n" - " // Re-project\n" - " gl_Position = stereoViewProjections[gl_InvocationID] * vec4(vertex_passViewPos[i],1);\n" - " vec4 viewPos = stereoViewMatrices[gl_InvocationID] * vec4(vertex_passViewPos[i],1);\n" - " gl_ClipVertex = vec4(viewPos.xyz,1);\n" - "\n" - " // Input -> output\n" + "void perVertex(int vertex, int viewport)\n" + "{\n" + " gl_ViewportIndex = viewport;\n" + " // Re-project\n" + " gl_Position = stereoViewProjections[viewport] * vec4(vertex_passViewPos[vertex],1);\n" + " vec4 viewPos = stereoViewMatrices[viewport] * vec4(vertex_passViewPos[vertex],1);\n" + " gl_ClipVertex = vec4(viewPos.xyz,1);\n" + " \n" + " // Input -> output\n" "@FORWARDING\n" + " \n" + " EmitVertex();\n" + "}\n" "\n" - " EmitVertex();\n" - " }\n" + "void perViewport(int viewport)\n" + "{\n" + " for(int vertex = 0; vertex < gl_in.length(); vertex++)\n" + " {\n" + " perVertex(vertex, viewport);\n" + " }\n" + " EndPrimitive();\n" + "}\n" + "\n" + "void main() {\n" + "#ifdef ENABLE_GL_ARB_gpu_shader5\n" + " int viewport = gl_InvocationID;\n" + "#else\n" + " for(int viewport = 0; viewport < 2; viewport++)\n" + "#endif\n" + " perViewport(viewport);\n" "\n" - " EndPrimitive();\n" "}\n" ; @@ -232,7 +257,7 @@ namespace Shader " 0.25, 0.5, 0.5, 1.0);\n" " vec4 texcoordProj = ((scalemat) * (gl_Position));\n" " screenCoordsPassthrough = texcoordProj.xyw;\n" - " if(gl_InvocationID == 1)\n" + " if(viewport == 1)\n" " screenCoordsPassthrough.x += 0.5 * screenCoordsPassthrough.z;\n" } }; @@ -255,7 +280,7 @@ namespace Shader if (overriddenForwardStatements.count(declaration.identifier) > 0) ssForwardStatements << overriddenForwardStatements[declaration.identifier] << ";\n"; else - ssForwardStatements << " " << declaration.identifier << " = " << declaration.mangledIdentifier << "[i];\n"; + ssForwardStatements << " " << declaration.identifier << " = " << declaration.mangledIdentifier << "[vertex];\n"; identifiers.insert(declaration.identifier); } From d4d899a56b41e9c76a628b1fad98fb3cf7f3bba9 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 7 Oct 2020 21:48:15 +0200 Subject: [PATCH 07/26] Separate geometry template to file. Explicitly size geometry shader input arrays. --- components/shader/shadermanager.cpp | 128 +++++++++------------------- components/shader/shadermanager.hpp | 2 + files/shaders/CMakeLists.txt | 1 + files/shaders/stereo_geometry.glsl | 58 +++++++++++++ 4 files changed, 100 insertions(+), 89 deletions(-) create mode 100644 files/shaders/stereo_geometry.glsl diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 9786d1ca1..c659a0e47 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -181,68 +181,10 @@ namespace Shader } } - static std::string generateGeometryShader(const std::vector& declarations) + static std::string generateGeometryShader(const std::string& geometryTemplate, const std::vector& declarations) { - static const char* geometryTemplate = - "#version 150 compatibility\n" - "#extension GL_ARB_viewport_array : require\n" - //"#ifdef GL_ARB_gpu_shader5\n" // Ref: AnyOldName3: This slightly faster path is broken on Vega 56 - "#if 0\n" - " #extension GL_ARB_gpu_shader5 : enable\n" - " #define ENABLE_GL_ARB_gpu_shader5\n" - "#endif\n" - "\n" - "#ifdef ENABLE_GL_ARB_gpu_shader5\n" - " layout (triangles, invocations = 2) in;\n" - " layout (triangle_strip, max_vertices = 3) out;\n" - "#else\n" - " layout (triangles) in;\n" - " layout (triangle_strip, max_vertices = 6) out;\n" - "#endif\n" - "\n" - "// Geometry Shader Inputs\n" - "@INPUTS\n" - "\n" - "// Geometry Shader Outputs\n" - "@OUTPUTS\n" - "\n" - "// Stereo matrices\n" - "uniform mat4 stereoViewMatrices[2];\n" - "uniform mat4 stereoViewProjections[2];\n" - "\n" - "void perVertex(int vertex, int viewport)\n" - "{\n" - " gl_ViewportIndex = viewport;\n" - " // Re-project\n" - " gl_Position = stereoViewProjections[viewport] * vec4(vertex_passViewPos[vertex],1);\n" - " vec4 viewPos = stereoViewMatrices[viewport] * vec4(vertex_passViewPos[vertex],1);\n" - " gl_ClipVertex = vec4(viewPos.xyz,1);\n" - " \n" - " // Input -> output\n" - "@FORWARDING\n" - " \n" - " EmitVertex();\n" - "}\n" - "\n" - "void perViewport(int viewport)\n" - "{\n" - " for(int vertex = 0; vertex < gl_in.length(); vertex++)\n" - " {\n" - " perVertex(vertex, viewport);\n" - " }\n" - " EndPrimitive();\n" - "}\n" - "\n" - "void main() {\n" - "#ifdef ENABLE_GL_ARB_gpu_shader5\n" - " int viewport = gl_InvocationID;\n" - "#else\n" - " for(int viewport = 0; viewport < 2; viewport++)\n" - "#endif\n" - " perViewport(viewport);\n" - "\n" - "}\n" - ; + if (geometryTemplate.empty()) + return ""; static std::map overriddenForwardStatements = { @@ -274,7 +216,7 @@ namespace Shader ssInputDeclarations << declaration.interpolationType << " "; ssOutputDeclarations << declaration.interpolationType << " "; } - ssInputDeclarations << "in " << declaration.type << " " << declaration.mangledIdentifier << "[];\n"; + ssInputDeclarations << "in " << declaration.type << " " << declaration.mangledIdentifier << "[3];\n"; ssOutputDeclarations << "out " << declaration.type << " " << declaration.identifier << ";\n"; if (overriddenForwardStatements.count(declaration.identifier) > 0) @@ -448,35 +390,13 @@ namespace Shader { std::lock_guard lock(mMutex); - // read the template if we haven't already - TemplateMap::iterator templateIt = mShaderTemplates.find(templateName); - if (templateIt == mShaderTemplates.end()) - { - boost::filesystem::path path = (boost::filesystem::path(mPath) / templateName); - boost::filesystem::ifstream stream; - stream.open(path); - if (stream.fail()) - { - Log(Debug::Error) << "Failed to open " << path.string(); - return nullptr; - } - std::stringstream buffer; - buffer << stream.rdbuf(); - - // parse includes - int fileNumber = 1; - std::string source = buffer.str(); - if (!addLineDirectivesAfterConditionalBlocks(source) - || !parseIncludes(boost::filesystem::path(mPath), source, templateName, fileNumber, {})) - return nullptr; - - templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first; - } - ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(templateName, defines)); if (shaderIt == mShaders.end()) { - std::string shaderSource = templateIt->second; + std::string shaderSource = getTemplateSource(templateName); + if (shaderSource.empty()) + return nullptr; + if (!parseDefines(shaderSource, defines, mGlobalDefines, templateName) || !parseFors(shaderSource, templateName)) { // Add to the cache anyway to avoid logging the same error over and over. @@ -493,7 +413,8 @@ namespace Shader { std::vector declarations; mangleInterface(shaderSource, "out|varying", "vertex_", declarations); - std::string geometryShaderSource = generateGeometryShader(declarations); + std::string geometryTemplate = getTemplateSource("stereo_geometry.glsl"); + std::string geometryShaderSource = generateGeometryShader(geometryTemplate, declarations); if (!geometryShaderSource.empty()) { osg::ref_ptr geometryShader(new osg::Shader(osg::Shader::GEOMETRY)); @@ -592,4 +513,33 @@ namespace Shader return mShadowMapAlphaTestDisableUniform; } + std::string ShaderManager::getTemplateSource(const std::string& templateName) + { + // read the template if we haven't already + TemplateMap::iterator templateIt = mShaderTemplates.find(templateName); + if (templateIt == mShaderTemplates.end()) + { + boost::filesystem::path path = (boost::filesystem::path(mPath) / templateName); + boost::filesystem::ifstream stream; + stream.open(path); + if (stream.fail()) + { + Log(Debug::Error) << "Failed to open " << path.string(); + return std::string(); + } + std::stringstream buffer; + buffer << stream.rdbuf(); + + // parse includes + int fileNumber = 1; + std::string source = buffer.str(); + if (!addLineDirectivesAfterConditionalBlocks(source) + || !parseIncludes(boost::filesystem::path(mPath), source, templateName, fileNumber, {})) + return std::string(); + + templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first; + } + return templateIt->second; + } + } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index d76f82fa9..47df0b4e9 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -55,6 +55,8 @@ namespace Shader const osg::ref_ptr getShadowMapAlphaTestDisableUniform(); private: + std::string getTemplateSource(const std::string& templateName); + std::string mPath; DefineMap mGlobalDefines; diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 8012c2bc1..23351760f 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -22,6 +22,7 @@ set(SHADER_FILES shadows_fragment.glsl shadowcasting_vertex.glsl shadowcasting_fragment.glsl + stereo_geometry.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/stereo_geometry.glsl b/files/shaders/stereo_geometry.glsl new file mode 100644 index 000000000..39ad84e5d --- /dev/null +++ b/files/shaders/stereo_geometry.glsl @@ -0,0 +1,58 @@ +#version 150 compatibility +#extension GL_ARB_viewport_array : require +//#ifdef GL_ARB_gpu_shader5 // Ref: AnyOldName3: This slightly faster path is broken on Vega 56 +#if 0 + #extension GL_ARB_gpu_shader5 : enable + #define ENABLE_GL_ARB_gpu_shader5 +#endif + +#ifdef ENABLE_GL_ARB_gpu_shader5 + layout (triangles, invocations = 2) in; + layout (triangle_strip, max_vertices = 3) out; +#else + layout (triangles) in; + layout (triangle_strip, max_vertices = 6) out; +#endif + +// Geometry Shader Inputs +@INPUTS + +// Geometry Shader Outputs +@OUTPUTS + +// Stereo matrices +uniform mat4 stereoViewMatrices[2]; +uniform mat4 stereoViewProjections[2]; + +void perVertex(int vertex, int viewport) +{ + gl_ViewportIndex = viewport; + // Re-project + gl_Position = stereoViewProjections[viewport] * vec4(vertex_passViewPos[vertex],1); + vec4 viewPos = stereoViewMatrices[viewport] * vec4(vertex_passViewPos[vertex],1); + gl_ClipVertex = vec4(viewPos.xyz,1); + + // Input -> output +@FORWARDING + + EmitVertex(); +} + +void perViewport(int viewport) +{ + for(int vertex = 0; vertex < gl_in.length(); vertex++) + { + perVertex(vertex, viewport); + } + EndPrimitive(); +} + +void main() { +#ifdef ENABLE_GL_ARB_gpu_shader5 + int viewport = gl_InvocationID; +#else + for(int viewport = 0; viewport < 2; viewport++) +#endif + perViewport(viewport); + +} \ No newline at end of file From 189fc51811c6a9f7ce8295c393f855d2b30ab4c8 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Thu, 8 Oct 2020 18:46:52 +0200 Subject: [PATCH 08/26] ref_ptr > unique_ptr --- apps/openmw/engine.cpp | 3 ++- apps/openmw/engine.hpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ba2476754..df08b12df 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -367,6 +367,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mScreenCaptureOperation(nullptr) , mStereoEnabled(false) , mStereoOverride(false) + , mStereoView(nullptr) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) @@ -723,7 +724,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Remove that altogether when the sky finally uses them. auto noShaderMask = MWRender::VisMask::Mask_Sky | MWRender::VisMask::Mask_Sun | MWRender::VisMask::Mask_WeatherParticles; auto geometryShaderMask = mViewer->getCamera()->getCullMask() & ~noShaderMask; - mStereoView.reset(new Misc::StereoView(mViewer, Misc::getStereoTechnique(), geometryShaderMask, noShaderMask | MWRender::VisMask::Mask_Scene)); + mStereoView = new Misc::StereoView(mViewer, Misc::getStereoTechnique(), geometryShaderMask, noShaderMask | MWRender::VisMask::Mask_Scene); } window->setStore(mEnvironment.getWorld()->getStore()); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 82e85934e..674ae882c 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -93,7 +93,7 @@ namespace OMW bool mStereoEnabled; bool mStereoOverride; - std::unique_ptr mStereoView; + osg::ref_ptr mStereoView; bool mSkipMenu; bool mUseSound; From 5056ca8cd8aa54983510ba1fe993ae01122b2650 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 11 Oct 2020 18:16:45 +0200 Subject: [PATCH 09/26] missing include --- components/misc/stereo.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/misc/stereo.hpp b/components/misc/stereo.hpp index 09fde0f36..c721f6a1c 100644 --- a/components/misc/stereo.hpp +++ b/components/misc/stereo.hpp @@ -6,6 +6,8 @@ #include #include +#include + // Some cursed headers like to define these #if defined(near) || defined(far) #undef near From 84e1a29700169afd0440ccce9f9061e22a69f711 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Fri, 27 Nov 2020 00:34:07 +0300 Subject: [PATCH 10/26] Make AI cast self-targeted spells at the ground (bug #5695) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aicast.cpp | 40 ++++++++++++++++-------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c6dd5ee..2f38ce17f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval Bug #5688: Water shader broken indoors with enable indoor shadows = false + Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5703: OpenMW-CS menu system crashing on XFCE Feature #390: 3rd person look "over the shoulder" Feature #2386: Distant Statics in the form of Object Paging diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 9ad7b4c56..af3aac340 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -46,27 +46,29 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac { return false; } - - osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); - if (target.getClass().isActor()) - { - osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); - targetPos.z() += halfExtents.z() * 2 * 0.75f; - } - - osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); - osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - actorPos.z() += halfExtents.z() * 2 * 0.75f; - - osg::Vec3f dir = targetPos - actorPos; - - bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f)); - turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f)); - - if (!turned) - return false; } + osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); + // If the target of an on-target spell is an actor that is not the caster + // the target position must be adjusted so that it's not casted at the actor's feet. + if (target != actor && target.getClass().isActor()) + { + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); + targetPos.z() += halfExtents.z() * 2 * 0.75f; + } + + osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + actorPos.z() += halfExtents.z() * 2 * 0.75f; + + osg::Vec3f dir = targetPos - actorPos; + + bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f)); + turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f)); + + if (!turned) + return false; + // Check if the actor is already casting another spell bool isCasting = MWBase::Environment::get().getMechanicsManager()->isCastingSpell(actor); if (isCasting && !mCasting) From 078de86e608b8fe9c1dca53d83fed7bc53a852cf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 4 Dec 2020 18:34:51 +0100 Subject: [PATCH 11/26] Use range based for loops and auto --- apps/openmw/mwgui/spellmodel.cpp | 12 ++--- apps/openmw/mwworld/containerstore.cpp | 64 +++++++++++--------------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 136547c4c..78b9171e5 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -48,9 +48,9 @@ namespace MWGui const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - for (unsigned int i = 0; i < effects.mList.size(); ++i) + for (const auto& effect : effects.mList) { - short effectId = effects.mList[i].mEffectID; + short effectId = effect.mEffectID; if (effectId != -1) { @@ -59,14 +59,14 @@ namespace MWGui std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId); std::string fullEffectName = wm->getGameSettingString(effectIDStr, ""); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effects.mList[i].mSkill != -1) + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effect.mSkill != -1) { - fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effects.mList[i].mSkill], ""); + fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effect.mSkill], ""); } - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effects.mList[i].mAttribute != -1) + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effect.mAttribute != -1) { - fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effects.mList[i].mAttribute], ""); + fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], ""); } std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName); diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 17c35489f..635485dde 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -26,7 +26,7 @@ namespace void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell) { auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts(); - for(const MWWorld::Ptr& ptr : store) + for(const auto&& ptr : store) { const std::string& script = ptr.getClass().getScript(ptr); if(!script.empty()) @@ -43,13 +43,10 @@ namespace { float sum = 0; - for (typename MWWorld::CellRefList::List::const_iterator iter ( - cellRefList.mList.begin()); - iter!=cellRefList.mList.end(); - ++iter) + for (const auto& iter : cellRefList.mList) { - if (iter->mData.getCount()>0) - sum += iter->mData.getCount()*iter->mBase->mData.mWeight; + if (iter.mData.getCount()>0) + sum += iter.mData.getCount()*iter.mBase->mData.mWeight; } return sum; @@ -62,12 +59,11 @@ namespace store->resolve(); std::string id2 = Misc::StringUtils::lowerCase (id); - for (typename MWWorld::CellRefList::List::iterator iter (list.mList.begin()); - iter!=list.mList.end(); ++iter) + for (auto& iter : list.mList) { - if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2) && iter->mData.getCount()) + if (Misc::StringUtils::ciEqual(iter.mBase->mId, id2) && iter.mData.getCount()) { - MWWorld::Ptr ptr (&*iter, nullptr); + MWWorld::Ptr ptr (&iter, nullptr); ptr.setContainerStore (store); return ptr; } @@ -81,7 +77,7 @@ MWWorld::ResolutionListener::~ResolutionListener() { if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty()) { - for(const MWWorld::Ptr& ptr : mStore) + for(const auto&& ptr : mStore) ptr.getRefData().setCount(0); mStore.fillNonRandom(mStore.mPtr.get()->mBase->mInventory, "", mStore.mSeed); addScripts(mStore, mStore.mPtr.mCell); @@ -127,15 +123,14 @@ template void MWWorld::ContainerStore::storeStates (const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const { - for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); - iter!=collection.mList.end(); ++iter) + for (const auto& iter : collection.mList) { - if (iter->mData.getCount() == 0) + if (iter.mData.getCount() == 0) continue; ESM::ObjectState state; - storeState (*iter, state); + storeState (iter, state); if (equipable) - storeEquipmentState(*iter, index, inventory); + storeEquipmentState(iter, index, inventory); inventory.mItems.push_back (state); ++index; } @@ -188,7 +183,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() int MWWorld::ContainerStore::count(const std::string &id) const { int total=0; - for (const auto& iter : *this) + for (const auto&& iter : *this) if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) total += iter.getRefData().getCount(); return total; @@ -430,14 +425,14 @@ void MWWorld::ContainerStore::rechargeItems(float duration) updateRechargingItems(); mRechargingItemsUpToDate = true; } - for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it) + for (auto& it : mRechargingItems) { - if (!MWMechanics::rechargeItem(*it->first, it->second, duration)) + if (!MWMechanics::rechargeItem(*it.first, it.second, duration)) continue; // attempt to restack when fully recharged - if (it->first->getCellRef().getEnchantmentCharge() == it->second) - it->first = restack(*it->first); + if (it.first->getCellRef().getEnchantmentCharge() == it.second) + it.first = restack(*it.first); } } @@ -481,9 +476,9 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const bool MWWorld::ContainerStore::hasVisibleItems() const { - for (auto iter(begin()); iter != end(); ++iter) + for (const auto&& iter : *this) { - if (iter->getClass().showsInInventory(*iter)) + if (iter.getClass().showsInInventory(iter)) return true; } @@ -601,8 +596,8 @@ void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const s void MWWorld::ContainerStore::clear() { - for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) - iter->getRefData().setCount (0); + for (auto&& iter : *this) + iter.getRefData().setCount (0); flagAsModified(); mModified = true; @@ -623,7 +618,7 @@ void MWWorld::ContainerStore::resolve() { if(!mResolved && !mPtr.isEmpty()) { - for(const MWWorld::Ptr& ptr : *this) + for(const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Seed seed{mSeed}; fill(mPtr.get()->mBase->mInventory, "", seed); @@ -644,7 +639,7 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() } if(!mResolved && !mPtr.isEmpty()) { - for(const MWWorld::Ptr& ptr : *this) + for(const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Seed seed{mSeed}; fill(mPtr.get()->mBase->mInventory, "", seed); @@ -727,10 +722,10 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) { MWWorld::Ptr item; int itemHealth = 1; - for (MWWorld::ContainerStoreIterator iter = begin(); iter != end(); ++iter) + for (auto&& iter : *this) { - int iterHealth = iter->getClass().hasItemHealth(*iter) ? iter->getClass().getItemHealth(*iter) : 1; - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) + int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1; + if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) { // Prefer the stack with the lowest remaining uses // Try to get item with zero durability only if there are no other items found @@ -738,7 +733,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) (iterHealth > 0 && iterHealth < itemHealth) || (itemHealth <= 0 && iterHealth > 0)) { - item = *iter; + item = iter; itemHealth = iterHealth; } } @@ -867,11 +862,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) mResolved = true; int index = 0; - for (std::vector::const_iterator - iter (inventory.mItems.begin()); iter!=inventory.mItems.end(); ++iter) + for (const ESM::ObjectState& state : inventory.mItems) { - const ESM::ObjectState& state = *iter; - int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID); int thisIndex = index++; From 7843dad35d772a1c0835dd8eb7dba4040ac3d7ff Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 5 Dec 2020 01:09:43 +0100 Subject: [PATCH 12/26] Don't let the actor "nowhere" after a teleport but move them in their place. This solve the problem where after loading, an empty frame was rendered because the player is "nowhere". --- apps/openmw/mwphysics/mtphysics.cpp | 9 ++++++++- apps/openmw/mwphysics/mtphysics.hpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 9 +-------- apps/openmw/mwphysics/physicssystem.hpp | 3 ++- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index bdffcef44..50cfd808f 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -297,12 +297,19 @@ namespace MWPhysics return mPreviousMovementResults; } - const PtrPositionList& PhysicsTaskScheduler::resetSimulation() + const PtrPositionList& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { std::unique_lock lock(mSimulationMutex); mMovementResults.clear(); mPreviousMovementResults.clear(); mActorsFrameData.clear(); + + for (const auto& [_, actor] : actors) + { + actor->resetPosition(); + actor->setStandingOnPtr(nullptr); + mMovementResults[actor->getPtr()] = actor->getWorldPosition(); + } return mMovementResults; } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index f7a131ca2..3a761829b 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -34,7 +34,7 @@ namespace MWPhysics /// @return new position of each actor const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); - const PtrPositionList& resetSimulation(); + const PtrPositionList& resetSimulation(const ActorMap& actors); // Thread safe wrappers void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index cdbad2cdd..b750970d9 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -677,14 +677,7 @@ namespace MWPhysics mTimeAccum -= numSteps * mPhysicsDt; if (skipSimulation) - { - for (auto& [_, actor] : mActors) - { - actor->resetPosition(); - actor->setStandingOnPtr(nullptr); - } - return mTaskScheduler->resetSimulation(); - } + return mTaskScheduler->resetSimulation(mActors); return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 03ae78993..4137b1e09 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -57,6 +57,8 @@ namespace MWPhysics class Actor; class PhysicsTaskScheduler; + using ActorMap = std::map>; + struct ContactPoint { MWWorld::Ptr mObject; @@ -265,7 +267,6 @@ namespace MWPhysics std::set mAnimatedObjects; // stores pointers to elements in mObjects - using ActorMap = std::map>; ActorMap mActors; using HeightFieldMap = std::map, HeightField *>; From b79f6ac808e71f7ee1fed5cbe46ce718491fc51c Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 6 Dec 2020 13:20:37 +0100 Subject: [PATCH 13/26] Force reset position of actor after snapping to the ground. Otherwise the interpolation calculation would kick in and make the actor goes upward if the spawn point is higher than summoner or downward if lower. The actor would then either jump or fall through terrain. --- apps/openmw/mwworld/worldimp.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 224925dd5..05051f300 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1351,6 +1351,8 @@ namespace MWWorld } moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); + if (force) // force physics to use the new position + mPhysics->getActor(ptr)->resetPosition(); } void World::fixPosition() From 08e73a09ec41fc791166473acdf91a3c0b39c0bb Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 6 Dec 2020 13:24:42 +0100 Subject: [PATCH 14/26] Make the code more compact by mean of std::min / max and ternary operator. --- apps/openmw/mwworld/worldimp.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 05051f300..265e45d37 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1332,22 +1332,15 @@ namespace MWWorld return; } - float terrainHeight = -std::numeric_limits::max(); - if (ptr.getCell()->isExterior()) - terrainHeight = getTerrainHeightAt(pos); - - if (pos.z() < terrainHeight) - pos.z() = terrainHeight; - - pos.z() += 20; // place slightly above. will snap down to ground with code below + const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits::max(); + pos.z() = std::max(pos.z(), terrainHeight + 20); // place slightly above terrain. will snap down to ground with code below // We still should trace down dead persistent actors - they do not use the "swimdeath" animation. bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()); if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr))) { osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits); - if (traced.z() < pos.z()) - pos.z() = traced.z(); + pos.z() = std::min(pos.z(), traced.z()); } moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); From c6c02a6f16384533d1c09a32bb81e5f0ad554cb4 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 6 Dec 2020 13:26:43 +0100 Subject: [PATCH 15/26] Remove useless code. ipos is already initialized with the correct values. --- apps/openmw/mwworld/worldimp.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 265e45d37..49e6a729a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1444,17 +1444,11 @@ namespace MWWorld ipos.pos[1] = spawnPoint.y(); ipos.pos[2] = spawnPoint.z(); - if (!referenceObject.getClass().isActor()) - { - ipos.rot[0] = referenceObject.getRefData().getPosition().rot[0]; - ipos.rot[1] = referenceObject.getRefData().getPosition().rot[1]; - } - else + if (referenceObject.getClass().isActor()) { ipos.rot[0] = 0; ipos.rot[1] = 0; } - ipos.rot[2] = referenceObject.getRefData().getPosition().rot[2]; MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); adjustPosition(placed, true); // snap to ground From 01cc9cb84892e11640df7f0b491d149f50022eba Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 6 Dec 2020 16:04:45 +0000 Subject: [PATCH 16/26] Update sdlgraphicswindow.cpp --- components/sdlutil/sdlgraphicswindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index 39b479f29..9e7ab5f9b 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -1,6 +1,6 @@ #include "sdlgraphicswindow.hpp" -#include +#include #include From e62fff5f2eaae841a4378e6bf18fb6a9b50b3d16 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 7 Dec 2020 19:04:32 +0100 Subject: [PATCH 17/26] Add a setting to disable graphical herbalism --- apps/openmw/engine.cpp | 4 ++-- apps/openmw/mwclass/container.cpp | 10 +++++++++- apps/openmw/mwclass/container.hpp | 5 +++++ files/settings-default.cfg | 3 +++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8092bb770..8f23f710d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -386,8 +386,6 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mNewGame (false) , mCfgMgr(configurationManager) { - MWClass::registerClasses(); - SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR; @@ -838,6 +836,8 @@ void OMW::Engine::go() std::string settingspath; settingspath = loadSettings (settings); + MWClass::registerClasses(); + // Create encoder mEncoder = new ToUTF8::Utf8Encoder(mEncoding); diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index a27e3debd..5ae22cf05 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -59,6 +60,11 @@ namespace MWClass return *this; } + Container::Container() + { + mHarvestEnabled = Settings::Manager::getBool("enable graphic herbalism", "Game"); + } + void Container::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) @@ -72,8 +78,10 @@ namespace MWClass } } - bool canBeHarvested(const MWWorld::ConstPtr& ptr) + bool Container::canBeHarvested(const MWWorld::ConstPtr& ptr) const { + if (!mHarvestEnabled) + return false; const MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (animation == nullptr) return false; diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 57dbf0c76..2dc0c06ca 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -30,11 +30,16 @@ namespace MWClass class Container : public MWWorld::Class { + bool mHarvestEnabled; + void ensureCustomData (const MWWorld::Ptr& ptr) const; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; + bool canBeHarvested(const MWWorld::ConstPtr& ptr) const; + public: + Container(); void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 0b307ba09..fbf646b38 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -361,6 +361,9 @@ trainers training skills based on base skill = false # Make stealing items from NPCs that were knocked down possible during combat. always allow stealing from knocked out actors = false +# Enables visually harvesting plants for models that support it. +enable graphic herbalism = true + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). From 275b9aea4d6d245b69eab91401ba11b8d764a756 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 7 Dec 2020 21:56:41 +0100 Subject: [PATCH 18/26] rename setting --- apps/openmw/mwclass/container.cpp | 2 +- files/settings-default.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 5ae22cf05..28305c394 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -62,7 +62,7 @@ namespace MWClass Container::Container() { - mHarvestEnabled = Settings::Manager::getBool("enable graphic herbalism", "Game"); + mHarvestEnabled = Settings::Manager::getBool("graphic herbalism", "Game"); } void Container::ensureCustomData (const MWWorld::Ptr& ptr) const diff --git a/files/settings-default.cfg b/files/settings-default.cfg index fbf646b38..c8805faa3 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -362,7 +362,7 @@ trainers training skills based on base skill = false always allow stealing from knocked out actors = false # Enables visually harvesting plants for models that support it. -enable graphic herbalism = true +graphic herbalism = true [General] From 242dd8d496b8ec819edb8ace9ff0c107e142de5c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 8 Dec 2020 11:06:30 +0400 Subject: [PATCH 19/26] Use a logging system instead of cout for a couple of missing messages --- apps/openmw/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 3a6dc526a..aca050592 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -153,13 +153,13 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.mergeComposingVariables(variables, composingVariables, desc); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); - std::cout << v.describe() << std::endl; + Log(Debug::Info) << v.describe(); engine.setGrabMouse(!variables["no-grab"].as()); // Font encoding settings std::string encoding(variables["encoding"].as().toStdString()); - std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; + Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); engine.setEncoding(ToUTF8::calculateEncoding(encoding)); // directory settings From dc7b48e92ed2ddfaed796df236811bfc5f7005d8 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Feb 2019 11:30:16 +0400 Subject: [PATCH 20/26] Generate physics collisions for projectiles (bug #3372) Remove redundant now mHit field --- CHANGELOG.md | 1 + apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwphysics/actor.cpp | 2 +- .../closestnotmeconvexresultcallback.cpp | 22 +++- .../closestnotmerayresultcallback.cpp | 14 +- .../closestnotmerayresultcallback.hpp | 3 +- apps/openmw/mwphysics/object.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 54 +++++++- apps/openmw/mwphysics/physicssystem.hpp | 22 +++- apps/openmw/mwphysics/projectile.cpp | 64 +++++++++ apps/openmw/mwphysics/projectile.hpp | 68 ++++++++++ apps/openmw/mwphysics/trace.cpp | 3 + apps/openmw/mwworld/projectilemanager.cpp | 123 ++++++++++++++++-- apps/openmw/mwworld/projectilemanager.hpp | 7 + apps/openmw/mwworld/worldimp.cpp | 5 + apps/openmw/mwworld/worldimp.hpp | 2 + 17 files changed, 372 insertions(+), 26 deletions(-) create mode 100644 apps/openmw/mwphysics/projectile.cpp create mode 100644 apps/openmw/mwphysics/projectile.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f38ce17f..e54515676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug #2473: Unable to overstock merchants Bug #2798: Mutable ESM records Bug #2976 [reopened]: Issues combining settings from the command line and both config files + Bug #3372: Projectiles and magic bolts go through moving targets Bug #3676: NiParticleColorModifier isn't applied properly Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects Bug #3789: Crash in visitEffectSources while in battle diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d943c7836..6b6134dfb 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -66,13 +66,13 @@ add_openmw_dir (mwworld cells localscripts customdata inventorystore ptr actionopen actionread actionharvest actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor - contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager + contentloader esmloader actiontrap cellreflist cellref weather projectilemanager cellpreloader datetimemanager ) add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback - contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver + contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile closestnotmeconvexresultcallback raycasting mtphysics ) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index d4f1d2f8a..c4b3771af 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -610,6 +610,8 @@ namespace MWBase virtual bool isPlayerInJail() const = 0; + virtual void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) = 0; + virtual void rest(double hours) = 0; virtual void rechargeItems(double duration, bool activeOnly) = 0; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 0e557378d..77b07cde5 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -70,7 +70,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setCollisionShape(mShape.get()); - mCollisionObject->setUserPointer(static_cast(this)); + mCollisionObject->setUserPointer(this); updateRotation(); updateScale(); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index ddfdb8a42..3b97a3a34 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -2,6 +2,14 @@ #include +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "collisiontype.hpp" +#include "projectile.hpp" + namespace MWPhysics { ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot) @@ -10,11 +18,23 @@ namespace MWPhysics { } - btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) + btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) return btScalar(1); + if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) + { + Projectile* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); + int projectileId = projectileHolder->getProjectileId(); + PtrHolder* targetHolder = static_cast(mMe->getUserPointer()); + const MWWorld::Ptr target = targetHolder->getPtr(); + + osg::Vec3f pos = Misc::Convert::makeOsgVec3f(convexResult.m_hitPointLocal); + MWBase::Environment::get().getWorld()->manualProjectileHit(projectileId, target, pos); + return btScalar(1); + } + btVector3 hitNormalWorld; if (normalInWorldSpace) hitNormalWorld = convexResult.m_hitNormalLocal; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 86763a793..43e1d5c62 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -6,13 +6,14 @@ #include "../mwworld/class.hpp" +#include "projectile.hpp" #include "ptrholder.hpp" namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to, int projId) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me), mTargets(targets) + , mMe(me), mTargets(targets), mProjectileId(projId) { } @@ -29,6 +30,15 @@ namespace MWPhysics return 1.f; } } + + if (mProjectileId >= 0) + { + PtrHolder* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); + Projectile* projectile = dynamic_cast(holder); + if (projectile && projectile->getProjectileId() == mProjectileId) + return 1.f; + } + return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index 23d52998c..d27d64c53 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -12,12 +12,13 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to); + ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to, int projId=-1); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: const btCollisionObject* mMe; const std::vector mTargets; + const int mProjectileId; }; } diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index c822bbcbe..910f7bf15 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -24,7 +24,7 @@ namespace MWPhysics mCollisionObject.reset(new btCollisionObject); mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); - mCollisionObject->setUserPointer(static_cast(this)); + mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index b750970d9..8933c9d02 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -46,6 +46,8 @@ #include "collisiontype.hpp" #include "actor.hpp" + +#include "projectile.hpp" #include "trace.h" #include "object.hpp" #include "heightfield.hpp" @@ -64,6 +66,7 @@ namespace MWPhysics , mResourceSystem(resourceSystem) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) + , mProjectileId(0) , mWaterHeight(0) , mWaterEnabled(false) , mParentNode(parentNode) @@ -113,6 +116,10 @@ namespace MWPhysics mObjects.clear(); mActors.clear(); + for (ProjectileMap::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) + { + delete it->second; + } } void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) @@ -248,7 +255,7 @@ namespace MWPhysics return 0.f; } - RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const + PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group, int projId) const { if (from == to) { @@ -285,7 +292,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); + ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, projId); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; @@ -298,7 +305,17 @@ namespace MWPhysics result.mHitPos = Misc::Convert::toOsg(resultCallback.m_hitPointWorld); result.mHitNormal = Misc::Convert::toOsg(resultCallback.m_hitNormalWorld); if (PtrHolder* ptrHolder = static_cast(resultCallback.m_collisionObject->getUserPointer())) + { result.mHitObject = ptrHolder->getPtr(); + + if (group == CollisionType_Projectile) + { + Projectile* projectile = static_cast(ptrHolder); + result.mProjectileId = projectile->getProjectileId(); + } + else + result.mProjectileId = -1; + } } return result; } @@ -502,6 +519,19 @@ namespace MWPhysics } } + void PhysicsSystem::removeProjectile(const int projectileId) + { + ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); + if (foundProjectile != mProjectiles.end()) + { + delete foundProjectile->second; + mProjectiles.erase(foundProjectile); + + if (projectileId == mProjectileId) + mProjectileId--; + } + } + void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) { ObjectMap::iterator found = mObjects.find(old); @@ -572,6 +602,17 @@ namespace MWPhysics } } + void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) + { + ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); + if (foundProjectile != mProjectiles.end()) + { + foundProjectile->second->setPosition(position); + mCollisionWorld->updateSingleAabb(foundProjectile->second->getCollisionObject()); + return; + } + } + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); @@ -632,6 +673,15 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } + int PhysicsSystem::addProjectile (const osg::Vec3f& position) + { + mProjectileId++; + Projectile* projectile = new Projectile(mProjectileId, position, mCollisionWorld); + mProjectiles.insert(std::make_pair(mProjectileId, projectile)); + + return mProjectileId; + } + bool PhysicsSystem::toggleCollisionMode() { ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4137b1e09..4b2aa0384 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -56,6 +56,7 @@ namespace MWPhysics class Object; class Actor; class PhysicsTaskScheduler; + class Projectile; using ActorMap = std::map>; @@ -127,6 +128,10 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); + int addProjectile(const osg::Vec3f& position); + void updateProjectile(const int projectileId, const osg::Vec3f &position); + void removeProjectile(const int projectileId); + void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); Actor* getActor(const MWWorld::Ptr& ptr); @@ -141,7 +146,6 @@ namespace MWPhysics 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); void removeHeightField (int x, int y); @@ -169,10 +173,19 @@ namespace MWPhysics /// \note Only Actor targets are supported at the moment. float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override; + struct RayResult + { + bool mHit; + osg::Vec3f mHitPos; + osg::Vec3f mHitNormal; + MWWorld::Ptr mHitObject; + int mProjectileId; + }; + /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override; @@ -269,6 +282,9 @@ namespace MWPhysics ActorMap mActors; + using ProjectileMap = std::map; + ProjectileMap mProjectiles; + using HeightFieldMap = std::map, HeightField *>; HeightFieldMap mHeightFields; @@ -279,6 +295,8 @@ namespace MWPhysics float mTimeAccum; + int mProjectileId; + float mWaterHeight; bool mWaterEnabled; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp new file mode 100644 index 000000000..f129b8f78 --- /dev/null +++ b/apps/openmw/mwphysics/projectile.cpp @@ -0,0 +1,64 @@ +#include "projectile.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "../mwworld/class.hpp" + +#include "collisiontype.hpp" + +namespace MWPhysics +{ +Projectile::Projectile(int projectileId, const osg::Vec3f& position, btCollisionWorld* world) + : mCollisionWorld(world) +{ + mProjectileId = projectileId; + + mShape.reset(new btSphereShape(1.f)); + mConvexShape = static_cast(mShape.get()); + + mCollisionObject.reset(new btCollisionObject); + mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); + mCollisionObject->setActivationState(DISABLE_DEACTIVATION); + mCollisionObject->setCollisionShape(mShape.get()); + mCollisionObject->setUserPointer(this); + + setPosition(position); + + const int collisionMask = CollisionType_World | CollisionType_HeightMap | + CollisionType_Actor | CollisionType_Door | CollisionType_Water; + mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); +} + +Projectile::~Projectile() +{ + if (mCollisionObject.get()) + mCollisionWorld->removeCollisionObject(mCollisionObject.get()); +} + +void Projectile::updateCollisionObjectPosition() +{ + btTransform tr = mCollisionObject->getWorldTransform(); + // osg::Vec3f scaledTranslation = mRotation * mMeshTranslation; + // osg::Vec3f newPosition = scaledTranslation + mPosition; + tr.setOrigin(Misc::Convert::toBullet(mPosition)); + mCollisionObject->setWorldTransform(tr); +} + +void Projectile::setPosition(const osg::Vec3f &position) +{ + mPosition = position; + updateCollisionObjectPosition(); +} + +osg::Vec3f Projectile::getPosition() const +{ + return mPosition; +} + +} diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp new file mode 100644 index 000000000..180802555 --- /dev/null +++ b/apps/openmw/mwphysics/projectile.hpp @@ -0,0 +1,68 @@ +#ifndef OPENMW_MWPHYSICS_PROJECTILE_H +#define OPENMW_MWPHYSICS_PROJECTILE_H + +#include + +#include "ptrholder.hpp" + +#include +#include +#include + +class btCollisionWorld; +class btCollisionShape; +class btCollisionObject; +class btConvexShape; + +namespace Resource +{ + class BulletShape; +} + +namespace MWPhysics +{ + class Projectile : public PtrHolder + { + public: + Projectile(const int projectileId, const osg::Vec3f& position, btCollisionWorld* world); + ~Projectile(); + + btConvexShape* getConvexShape() const { return mConvexShape; } + + void updateCollisionObjectPosition(); + + void setPosition(const osg::Vec3f& position); + + osg::Vec3f getPosition() const; + + btCollisionObject* getCollisionObject() const + { + return mCollisionObject.get(); + } + + int getProjectileId() const + { + return mProjectileId; + } + + private: + + std::unique_ptr mShape; + btConvexShape* mConvexShape; + + std::unique_ptr mCollisionObject; + + osg::Vec3f mPosition; + + btCollisionWorld* mCollisionWorld; + + Projectile(const Projectile&); + Projectile& operator=(const Projectile&); + + int mProjectileId; + }; + +} + + +#endif diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 58082f4db..4d2fccdb7 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -5,6 +5,9 @@ #include #include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + #include "collisiontype.hpp" #include "actor.hpp" #include "closestnotmeconvexresultcallback.hpp" diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 39cdc3e72..3b61e0522 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -302,6 +302,7 @@ namespace MWWorld MWWorld::Ptr ptr = ref.getPtr(); osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); + createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -312,7 +313,8 @@ namespace MWWorld if (sound) state.mSounds.push_back(sound); } - + + state.mProjectileId = mPhysics->addProjectile(pos); mMagicBolts.push_back(state); } @@ -325,7 +327,6 @@ namespace MWWorld state.mIdArrow = projectile.getCellRef().getRefId(); state.mCasterHandle = actor; state.mAttackStrength = attackStrength; - int type = projectile.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown; @@ -336,6 +337,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); + state.mProjectileId = mPhysics->addProjectile(pos); mProjectiles.push_back(state); } @@ -416,6 +418,8 @@ namespace MWWorld it->mNode->setPosition(newPos); + mPhysics->updateProjectile(it->mProjectileId, newPos); + update(*it, duration); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. @@ -425,7 +429,7 @@ namespace MWWorld // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); bool hit = false; if (result.mHit) @@ -433,7 +437,7 @@ namespace MWWorld hit = true; if (result.mHitObject.isEmpty()) { - // terrain + // terrain or projectile } else { @@ -456,11 +460,7 @@ namespace MWWorld MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, ESM::RT_Target, it->mSpellId, it->mSourceName); - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) - sndMgr->stopSound(it->mSounds.at(soundIter)); - - mParent->removeChild(it->mNode); + cleanupMagicBolt(*it); it = mMagicBolts.erase(it); continue; @@ -491,6 +491,8 @@ namespace MWWorld it->mNode->setPosition(newPos); + mPhysics->updateProjectile(it->mProjectileId, newPos); + update(*it, duration); MWWorld::Ptr caster = it->getCaster(); @@ -502,15 +504,14 @@ namespace MWWorld // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); if (result.mHit || underwater) { - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); - // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); MWWorld::Ptr bow = projectileRef.getPtr(); if (!caster.isEmpty() && it->mIdArrow != it->mBowId) { @@ -529,8 +530,9 @@ namespace MWWorld if (underwater) mRendering->emitWaterRipple(newPos); - mParent->removeChild(it->mNode); + cleanupProjectile(*it); it = mProjectiles.erase(it); + continue; } @@ -538,14 +540,105 @@ namespace MWWorld } } + void ProjectileManager::manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) + { + for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) + { + if (it->mProjectileId == projectileId) + { + MWWorld::Ptr caster = it->getCaster(); + if (caster == target) + return; + + if (!isValidTarget(caster, target)) + return; + + if (caster.isEmpty()) + caster = target; + + // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); + MWWorld::Ptr bow = projectileRef.getPtr(); + if (!caster.isEmpty() && it->mIdArrow != it->mBowId) + { + MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); + MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) + bow = *invIt; + } + + it->mHitPosition = pos; + cleanupProjectile(*it); + MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, it->mAttackStrength); + mProjectiles.erase(it); + return; + } + } + for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) + { + if (it->mProjectileId == projectileId) + { + MWWorld::Ptr caster = it->getCaster(); + if (caster == target) + return; + + if (!isValidTarget(caster, target)) + return; + + it->mHitPosition = pos; + cleanupMagicBolt(*it); + + MWMechanics::CastSpell cast(caster, target); + cast.mHitPosition = pos; + cast.mId = it->mSpellId; + cast.mSourceName = it->mSourceName; + cast.mStack = false; + cast.inflict(target, caster, it->mEffects, ESM::RT_Target, false, true); + + MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, target, ESM::RT_Target, it->mSpellId, it->mSourceName); + mMagicBolts.erase(it); + + return; + } + } + } + + bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + { + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + std::vector targetActors; + if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) + { + caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); + if (!targetActors.empty()) + { + bool validTarget = false; + for (MWWorld::Ptr& targetActor : targetActors) + { + if (targetActor == target) + { + validTarget = true; + break; + } + } + + return validTarget; + } + } + + return true; + } + void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state) { mParent->removeChild(state.mNode); + mPhysics->removeProjectile(state.mProjectileId); } void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state) { mParent->removeChild(state.mNode); + mPhysics->removeProjectile(state.mProjectileId); for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++) { MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter)); @@ -626,9 +719,10 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); - int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; + + state.mProjectileId = mPhysics->addProjectile(osg::Vec3f(esm.mPosition)); } catch(...) { @@ -672,6 +766,7 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); + state.mProjectileId = mPhysics->addProjectile(osg::Vec3f(esm.mPosition)); } catch(...) { diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index c7b1056b7..58e0ecbf8 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -56,6 +56,8 @@ namespace MWWorld void update(float dt); + void manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos); + /// Removes all current projectiles. Should be called when switching to a new worldspace. void clear(); @@ -76,6 +78,9 @@ namespace MWWorld std::shared_ptr mEffectAnimationTime; int mActorId; + int mProjectileId; + + osg::Vec3f mHitPosition; // TODO: this will break when the game is saved and reloaded, since there is currently // no way to write identifiers for non-actors to a savegame. @@ -125,6 +130,8 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); + bool isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); + void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); void update (State& state, float duration); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 49e6a729a..931791fbc 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -843,6 +843,11 @@ namespace MWWorld } } + void World::manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) + { + mProjectileManager->manualHit(projectileId, target, pos); + } + void World::disable (const Ptr& reference) { if (!reference.getRefData().isEnabled()) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1fc5ebc20..a913108ad 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -584,6 +584,8 @@ namespace MWWorld RestPermitted canRest() const override; ///< check if the player is allowed to rest + void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos); + void rest(double hours) override; void rechargeItems(double duration, bool activeOnly) override; From 66fe3b0d38469f280a1f6ec4747b5ed8d889d9bd Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Oct 2020 20:27:07 +0200 Subject: [PATCH 21/26] Modify projectile collision to work with async physics --- apps/openmw/mwbase/world.hpp | 2 - .../closestnotmeconvexresultcallback.cpp | 5 +- apps/openmw/mwphysics/mtphysics.cpp | 6 + apps/openmw/mwphysics/physicssystem.cpp | 38 +-- apps/openmw/mwphysics/physicssystem.hpp | 15 +- apps/openmw/mwphysics/projectile.cpp | 58 ++-- apps/openmw/mwphysics/projectile.hpp | 59 +++- apps/openmw/mwphysics/raycasting.hpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 257 +++++++++--------- apps/openmw/mwworld/projectilemanager.hpp | 4 +- apps/openmw/mwworld/worldimp.cpp | 6 +- apps/openmw/mwworld/worldimp.hpp | 2 - 12 files changed, 245 insertions(+), 209 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c4b3771af..d4f1d2f8a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -610,8 +610,6 @@ namespace MWBase virtual bool isPlayerInJail() const = 0; - virtual void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) = 0; - virtual void rest(double hours) = 0; virtual void rechargeItems(double duration, bool activeOnly) = 0; diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 3b97a3a34..b709811f4 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -26,12 +26,13 @@ namespace MWPhysics if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) { Projectile* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); - int projectileId = projectileHolder->getProjectileId(); + if (!projectileHolder->isActive()) + return btScalar(1); PtrHolder* targetHolder = static_cast(mMe->getUserPointer()); const MWWorld::Ptr target = targetHolder->getPtr(); osg::Vec3f pos = Misc::Convert::makeOsgVec3f(convexResult.m_hitPointLocal); - MWBase::Environment::get().getWorld()->manualProjectileHit(projectileId, target, pos); + projectileHolder->hit(target, pos); return btScalar(1); } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 50cfd808f..a78a30788 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -17,6 +17,7 @@ #include "mtphysics.hpp" #include "object.hpp" #include "physicssystem.hpp" +#include "projectile.hpp" namespace { @@ -455,6 +456,11 @@ namespace MWPhysics object->commitPositionChange(); mCollisionWorld->updateSingleAabb(object->getCollisionObject()); } + else if (const auto projectile = std::dynamic_pointer_cast(p)) + { + projectile->commitPositionChange(); + mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); + } }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8933c9d02..78010fb0e 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -115,11 +115,7 @@ namespace MWPhysics mObjects.clear(); mActors.clear(); - - for (ProjectileMap::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) - { - delete it->second; - } + mProjectiles.clear(); } void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) @@ -255,7 +251,7 @@ namespace MWPhysics return 0.f; } - PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group, int projId) const + RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group, int projId) const { if (from == to) { @@ -305,17 +301,7 @@ namespace MWPhysics result.mHitPos = Misc::Convert::toOsg(resultCallback.m_hitPointWorld); result.mHitNormal = Misc::Convert::toOsg(resultCallback.m_hitNormalWorld); if (PtrHolder* ptrHolder = static_cast(resultCallback.m_collisionObject->getUserPointer())) - { result.mHitObject = ptrHolder->getPtr(); - - if (group == CollisionType_Projectile) - { - Projectile* projectile = static_cast(ptrHolder); - result.mProjectileId = projectile->getProjectileId(); - } - else - result.mProjectileId = -1; - } } return result; } @@ -523,13 +509,7 @@ namespace MWPhysics { ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); if (foundProjectile != mProjectiles.end()) - { - delete foundProjectile->second; mProjectiles.erase(foundProjectile); - - if (projectileId == mProjectileId) - mProjectileId--; - } } void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) @@ -583,6 +563,14 @@ namespace MWPhysics return nullptr; } + Projectile* PhysicsSystem::getProjectile(int projectileId) const + { + ProjectileMap::const_iterator found = mProjectiles.find(projectileId); + if (found != mProjectiles.end()) + return found->second.get(); + return nullptr; + } + void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); @@ -608,7 +596,7 @@ namespace MWPhysics if (foundProjectile != mProjectiles.end()) { foundProjectile->second->setPosition(position); - mCollisionWorld->updateSingleAabb(foundProjectile->second->getCollisionObject()); + mTaskScheduler->updateSingleAabb(foundProjectile->second); return; } } @@ -676,8 +664,8 @@ namespace MWPhysics int PhysicsSystem::addProjectile (const osg::Vec3f& position) { mProjectileId++; - Projectile* projectile = new Projectile(mProjectileId, position, mCollisionWorld); - mProjectiles.insert(std::make_pair(mProjectileId, projectile)); + auto projectile = std::make_shared(mProjectileId, position, mTaskScheduler.get()); + mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4b2aa0384..6721a2d91 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -139,6 +139,8 @@ namespace MWPhysics const Object* getObject(const MWWorld::ConstPtr& ptr) const; + Projectile* getProjectile(int projectileId) const; + // Object or Actor void remove (const MWWorld::Ptr& ptr); @@ -173,15 +175,6 @@ namespace MWPhysics /// \note Only Actor targets are supported at the moment. float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override; - struct RayResult - { - bool mHit; - osg::Vec3f mHitPos; - osg::Vec3f mHitNormal; - MWWorld::Ptr mHitObject; - int mProjectileId; - }; - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), @@ -282,7 +275,7 @@ namespace MWPhysics ActorMap mActors; - using ProjectileMap = std::map; + using ProjectileMap = std::map>; ProjectileMap mProjectiles; using HeightFieldMap = std::map, HeightField *>; @@ -295,7 +288,7 @@ namespace MWPhysics float mTimeAccum; - int mProjectileId; + unsigned int mProjectileId; float mWaterHeight; bool mWaterEnabled; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index f129b8f78..8abf238cf 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -1,24 +1,28 @@ -#include "projectile.hpp" +#include #include #include -#include -#include +#include + #include #include +#include +#include #include "../mwworld/class.hpp" #include "collisiontype.hpp" +#include "mtphysics.hpp" +#include "projectile.hpp" namespace MWPhysics { -Projectile::Projectile(int projectileId, const osg::Vec3f& position, btCollisionWorld* world) - : mCollisionWorld(world) +Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler) + : mActive(true) + , mTaskScheduler(scheduler) + , mProjectileId(projectileId) { - mProjectileId = projectileId; - mShape.reset(new btSphereShape(1.f)); mConvexShape = static_cast(mShape.get()); @@ -32,33 +36,47 @@ Projectile::Projectile(int projectileId, const osg::Vec3f& position, btCollision const int collisionMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor | CollisionType_Door | CollisionType_Water; - mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); + mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); + + commitPositionChange(); } Projectile::~Projectile() { - if (mCollisionObject.get()) - mCollisionWorld->removeCollisionObject(mCollisionObject.get()); + if (mCollisionObject) + mTaskScheduler->removeCollisionObject(mCollisionObject.get()); } -void Projectile::updateCollisionObjectPosition() +void Projectile::commitPositionChange() { - btTransform tr = mCollisionObject->getWorldTransform(); - // osg::Vec3f scaledTranslation = mRotation * mMeshTranslation; - // osg::Vec3f newPosition = scaledTranslation + mPosition; - tr.setOrigin(Misc::Convert::toBullet(mPosition)); - mCollisionObject->setWorldTransform(tr); + std::unique_lock lock(mPositionMutex); + if (mTransformUpdatePending) + { + mCollisionObject->setWorldTransform(mLocalTransform); + mTransformUpdatePending = false; + } } void Projectile::setPosition(const osg::Vec3f &position) { - mPosition = position; - updateCollisionObjectPosition(); + std::unique_lock lock(mPositionMutex); + mLocalTransform.setOrigin(Misc::Convert::toBullet(position)); + mTransformUpdatePending = true; } -osg::Vec3f Projectile::getPosition() const +void Projectile::hit(MWWorld::Ptr target, osg::Vec3f pos) { - return mPosition; + if (!mActive.load(std::memory_order_acquire)) + return; + std::unique_lock lock(mPositionMutex); + mHitTarget = target; + mHitPosition = pos; + mActive.store(false, std::memory_order_release); } +void Projectile::activate() +{ + assert(!mActive); + mActive.store(true, std::memory_order_release); +} } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 180802555..7ad0a3124 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -1,18 +1,23 @@ #ifndef OPENMW_MWPHYSICS_PROJECTILE_H #define OPENMW_MWPHYSICS_PROJECTILE_H +#include #include +#include + +#include #include "ptrholder.hpp" -#include -#include -#include - -class btCollisionWorld; -class btCollisionShape; class btCollisionObject; +class btCollisionShape; class btConvexShape; +class btVector3; + +namespace osg +{ + class Vec3f; +} namespace Resource { @@ -21,20 +26,21 @@ namespace Resource namespace MWPhysics { - class Projectile : public PtrHolder + class PhysicsTaskScheduler; + class PhysicsSystem; + + class Projectile final : public PtrHolder { public: - Projectile(const int projectileId, const osg::Vec3f& position, btCollisionWorld* world); - ~Projectile(); + Projectile(const int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler); + ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } - void updateCollisionObjectPosition(); + void commitPositionChange(); void setPosition(const osg::Vec3f& position); - osg::Vec3f getPosition() const; - btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); @@ -45,16 +51,43 @@ namespace MWPhysics return mProjectileId; } + bool isActive() const + { + return mActive.load(std::memory_order_acquire); + } + + MWWorld::Ptr getTarget() const + { + assert(!mActive); + return mHitTarget; + } + + osg::Vec3f getHitPos() const + { + assert(!mActive); + return Misc::Convert::toOsg(mHitPosition); + } + + void hit(MWWorld::Ptr target, osg::Vec3f pos); + void activate(); + private: std::unique_ptr mShape; btConvexShape* mConvexShape; std::unique_ptr mCollisionObject; + btTransform mLocalTransform; + bool mTransformUpdatePending; + std::atomic mActive; + MWWorld::Ptr mHitTarget; + osg::Vec3f mHitPosition; + + mutable std::mutex mPositionMutex; osg::Vec3f mPosition; - btCollisionWorld* mCollisionWorld; + PhysicsTaskScheduler *mTaskScheduler; Projectile(const Projectile&); Projectile& operator=(const Projectile&); diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 7afbe9321..8ca6965d8 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -29,7 +29,7 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), std::vector targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3b61e0522..626c57ac3 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -43,6 +44,7 @@ #include "../mwsound/sound.hpp" #include "../mwphysics/physicssystem.hpp" +#include "../mwphysics/projectile.hpp" namespace { @@ -284,7 +286,7 @@ namespace MWWorld else state.mActorId = -1; - std::string texture = ""; + std::string texture; state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); @@ -315,6 +317,7 @@ namespace MWWorld } state.mProjectileId = mPhysics->addProjectile(pos); + state.mToDelete = false; mMagicBolts.push_back(state); } @@ -338,6 +341,7 @@ namespace MWWorld SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); state.mProjectileId = mPhysics->addProjectile(pos); + state.mToDelete = false; mProjectiles.push_back(state); } @@ -362,65 +366,58 @@ namespace MWWorld return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold; }; - for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end();) + for (auto& projectileState : mProjectiles) { - if (isCleanable(*it)) - { - cleanupProjectile(*it); - it = mProjectiles.erase(it); - } - else - ++it; + if (isCleanable(projectileState)) + cleanupProjectile(projectileState); } - for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) + for (auto& magicBoltState : mMagicBolts) { - if (isCleanable(*it)) - { - cleanupMagicBolt(*it); - it = mMagicBolts.erase(it); - } - else - ++it; + if (isCleanable(magicBoltState)) + cleanupMagicBolt(magicBoltState); } } } void ProjectileManager::moveMagicBolts(float duration) { - for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) + for (auto& magicBoltState : mMagicBolts) { + if (magicBoltState.mToDelete) + continue; + + const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); + if (!projectile->isActive()) + continue; // If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. - MWWorld::Ptr caster = it->getCaster(); + MWWorld::Ptr caster = magicBoltState.getCaster(); if (!caster.isEmpty() && caster.getClass().isActor()) { if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) { - cleanupMagicBolt(*it); - it = mMagicBolts.erase(it); + cleanupMagicBolt(magicBoltState); continue; } } - osg::Quat orient = it->mNode->getAttitude(); + osg::Quat orient = magicBoltState.mNode->getAttitude(); static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() .find("fTargetSpellMaxSpeed")->mValue.getFloat(); - float speed = fTargetSpellMaxSpeed * it->mSpeed; + float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); - osg::Vec3f pos(it->mNode->getPosition()); + osg::Vec3f pos(magicBoltState.mNode->getPosition()); osg::Vec3f newPos = pos + direction * duration * speed; - for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) - { - it->mSounds.at(soundIter)->setPosition(newPos); - } + for (const auto& sound : magicBoltState.mSounds) + sound->setPosition(newPos); - it->mNode->setPosition(newPos); + magicBoltState.mNode->setPosition(newPos); - mPhysics->updateProjectile(it->mProjectileId, newPos); + mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos); - update(*it, duration); + update(magicBoltState, duration); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; @@ -429,7 +426,7 @@ namespace MWWorld // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); + const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId); bool hit = false; if (result.mHit) @@ -443,10 +440,10 @@ namespace MWWorld { MWMechanics::CastSpell cast(caster, result.mHitObject); cast.mHitPosition = pos; - cast.mId = it->mSpellId; - cast.mSourceName = it->mSourceName; + cast.mId = magicBoltState.mSpellId; + cast.mSourceName = magicBoltState.mSourceName; cast.mStack = false; - cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true); + cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); } } @@ -457,45 +454,46 @@ namespace MWWorld if (hit) { - MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, - ESM::RT_Target, it->mSpellId, it->mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject, + ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); - cleanupMagicBolt(*it); - - it = mMagicBolts.erase(it); - continue; + cleanupMagicBolt(magicBoltState); } - else - ++it; } } void ProjectileManager::moveProjectiles(float duration) { - for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end();) + for (auto& projectileState : mProjectiles) { + if (projectileState.mToDelete) + continue; + + const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); + if (!projectile->isActive()) + continue; // gravity constant - must be way lower than the gravity affecting actors, since we're not // simulating aerodynamics at all - it->mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; + projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - osg::Vec3f pos(it->mNode->getPosition()); - osg::Vec3f newPos = pos + it->mVelocity * duration; + osg::Vec3f pos(projectileState.mNode->getPosition()); + osg::Vec3f newPos = pos + projectileState.mVelocity * duration; // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. - if (!it->mThrown) + if (!projectileState.mThrown) { osg::Quat orient; - orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); - it->mNode->setAttitude(orient); + orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity); + projectileState.mNode->setAttitude(orient); } - it->mNode->setPosition(newPos); + projectileState.mNode->setPosition(newPos); - mPhysics->updateProjectile(it->mProjectileId, newPos); + mPhysics->updateProjectile(projectileState.mProjectileId, newPos); - update(*it, duration); + update(projectileState, duration); - MWWorld::Ptr caster = it->getCaster(); + MWWorld::Ptr caster = projectileState.getCaster(); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; @@ -504,103 +502,107 @@ namespace MWWorld // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); + const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, projectileState.mProjectileId); bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); if (result.mHit || underwater) { // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty() && it->mIdArrow != it->mBowId) + if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) { MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) bow = *invIt; } if (caster.isEmpty()) caster = result.mHitObject; - MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, it->mAttackStrength); + MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, projectileState.mAttackStrength); mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); if (underwater) mRendering->emitWaterRipple(newPos); - cleanupProjectile(*it); - it = mProjectiles.erase(it); - - continue; + cleanupProjectile(projectileState); } - - ++it; } } - void ProjectileManager::manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) + void ProjectileManager::processHits() { - for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) + for (auto& projectileState : mProjectiles) { - if (it->mProjectileId == projectileId) + if (projectileState.mToDelete) + continue; + + auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); + if (projectile->isActive()) + continue; + const auto target = projectile->getTarget(); + const auto pos = projectile->getHitPos(); + MWWorld::Ptr caster = projectileState.getCaster(); + if (caster == target || !isValidTarget(caster, target)) { - MWWorld::Ptr caster = it->getCaster(); - if (caster == target) - return; - - if (!isValidTarget(caster, target)) - return; - - if (caster.isEmpty()) - caster = target; - - // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); - MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty() && it->mIdArrow != it->mBowId) - { - MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); - MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) - bow = *invIt; - } - - it->mHitPosition = pos; - cleanupProjectile(*it); - MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, it->mAttackStrength); - mProjectiles.erase(it); - return; + projectile->activate(); + continue; } + + if (caster.isEmpty()) + caster = target; + + // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow); + MWWorld::Ptr bow = projectileRef.getPtr(); + if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId) + { + MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); + MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) + bow = *invIt; + } + + projectileState.mHitPosition = pos; + cleanupProjectile(projectileState); + MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); } - for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) + for (auto& magicBoltState : mMagicBolts) { - if (it->mProjectileId == projectileId) + if (magicBoltState.mToDelete) + continue; + + auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); + if (projectile->isActive()) + continue; + const auto target = projectile->getTarget(); + const auto pos = projectile->getHitPos(); + MWWorld::Ptr caster = magicBoltState.getCaster(); + if (caster == target || !isValidTarget(caster, target)) { - MWWorld::Ptr caster = it->getCaster(); - if (caster == target) - return; - - if (!isValidTarget(caster, target)) - return; - - it->mHitPosition = pos; - cleanupMagicBolt(*it); - - MWMechanics::CastSpell cast(caster, target); - cast.mHitPosition = pos; - cast.mId = it->mSpellId; - cast.mSourceName = it->mSourceName; - cast.mStack = false; - cast.inflict(target, caster, it->mEffects, ESM::RT_Target, false, true); - - MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, target, ESM::RT_Target, it->mSpellId, it->mSourceName); - mMagicBolts.erase(it); - - return; + projectile->activate(); + continue; } + + magicBoltState.mHitPosition = pos; + cleanupMagicBolt(magicBoltState); + + MWMechanics::CastSpell cast(caster, target); + cast.mHitPosition = pos; + cast.mId = magicBoltState.mSpellId; + cast.mSourceName = magicBoltState.mSourceName; + cast.mStack = false; + cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); + + MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); } + mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), + mProjectiles.end()); + mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }), + mMagicBolts.end()); } bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) @@ -633,12 +635,14 @@ namespace MWWorld { mParent->removeChild(state.mNode); mPhysics->removeProjectile(state.mProjectileId); + state.mToDelete = true; } void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state) { mParent->removeChild(state.mNode); mPhysics->removeProjectile(state.mProjectileId); + state.mToDelete = true; for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++) { MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter)); @@ -647,15 +651,12 @@ namespace MWWorld void ProjectileManager::clear() { - for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) - { - cleanupProjectile(*it); - } + for (auto& mProjectile : mProjectiles) + cleanupProjectile(mProjectile); mProjectiles.clear(); - for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) - { - cleanupMagicBolt(*it); - } + + for (auto& mMagicBolt : mMagicBolts) + cleanupMagicBolt(mMagicBolt); mMagicBolts.clear(); } @@ -712,6 +713,7 @@ namespace MWWorld state.mVelocity = esm.mVelocity; state.mIdArrow = esm.mId; state.mAttackStrength = esm.mAttackStrength; + state.mToDelete = false; std::string model; try @@ -734,7 +736,7 @@ namespace MWWorld mProjectiles.push_back(state); return true; } - else if (type == ESM::REC_MPRJ) + if (type == ESM::REC_MPRJ) { ESM::MagicBoltState esm; esm.load(reader); @@ -743,7 +745,8 @@ namespace MWWorld state.mIdMagic.push_back(esm.mId); state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; - std::string texture = ""; + state.mToDelete = false; + std::string texture; try { diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 58e0ecbf8..d589e985e 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -56,7 +56,7 @@ namespace MWWorld void update(float dt); - void manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos); + void processHits(); /// Removes all current projectiles. Should be called when switching to a new worldspace. void clear(); @@ -93,6 +93,8 @@ namespace MWWorld // MW-id of an arrow projectile std::string mIdArrow; + + bool mToDelete; }; struct MagicBoltState : public State diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 931791fbc..9a2dd1181 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -843,11 +843,6 @@ namespace MWWorld } } - void World::manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) - { - mProjectileManager->manualHit(projectileId, target, pos); - } - void World::disable (const Ptr& reference) { if (!reference.getRefData().isEnabled()) @@ -1498,6 +1493,7 @@ namespace MWWorld mProjectileManager->update(duration); const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); + mProjectileManager->processHits(); mDiscardMovements = false; for(const auto& [actor, position]: results) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index a913108ad..1fc5ebc20 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -584,8 +584,6 @@ namespace MWWorld RestPermitted canRest() const override; ///< check if the player is allowed to rest - void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos); - void rest(double hours) override; void rechargeItems(double duration, bool activeOnly) override; From 7e85235220dfefca9f44f75098c271709e70e001 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 31 Oct 2020 14:01:14 +0100 Subject: [PATCH 22/26] Projectile to projectile collision --- .../closestnotmeconvexresultcallback.cpp | 4 +-- .../closestnotmerayresultcallback.cpp | 28 +++++++++++++------ .../closestnotmerayresultcallback.hpp | 6 ++-- apps/openmw/mwphysics/physicssystem.cpp | 4 +-- apps/openmw/mwphysics/projectile.cpp | 12 ++++++-- apps/openmw/mwphysics/projectile.hpp | 10 ++++--- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index b709811f4..bbffe16a8 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -30,9 +30,7 @@ namespace MWPhysics return btScalar(1); PtrHolder* targetHolder = static_cast(mMe->getUserPointer()); const MWWorld::Ptr target = targetHolder->getPtr(); - - osg::Vec3f pos = Misc::Convert::makeOsgVec3f(convexResult.m_hitPointLocal); - projectileHolder->hit(target, pos); + projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); return btScalar(1); } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 43e1d5c62..422ca78bd 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -1,19 +1,22 @@ #include "closestnotmerayresultcallback.hpp" #include +#include #include #include "../mwworld/class.hpp" +#include "actor.hpp" +#include "collisiontype.hpp" #include "projectile.hpp" #include "ptrholder.hpp" namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to, int projId) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me), mTargets(targets), mProjectileId(projId) + , mMe(me), mTargets(std::move(targets)), mProjectile(proj) { } @@ -25,20 +28,27 @@ namespace MWPhysics { if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) { - PtrHolder* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); + auto* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) return 1.f; } } - if (mProjectileId >= 0) + btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); + if (mProjectile) { - PtrHolder* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); - Projectile* projectile = dynamic_cast(holder); - if (projectile && projectile->getProjectileId() == mProjectileId) - return 1.f; + auto* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); + if (auto* target = dynamic_cast(holder)) + { + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + } + else if (auto* target = dynamic_cast(holder)) + { + target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + } } - return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); + return rayResult.m_hitFraction; } } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index d27d64c53..b86427165 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -9,16 +9,18 @@ class btCollisionObject; namespace MWPhysics { + class Projectile; + class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to, int projId=-1); + ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: const btCollisionObject* mMe; const std::vector mTargets; - const int mProjectileId; + Projectile* mProjectile; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 78010fb0e..7da2a6964 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -288,7 +288,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, projId); + ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId)); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; @@ -664,7 +664,7 @@ namespace MWPhysics int PhysicsSystem::addProjectile (const osg::Vec3f& position) { mProjectileId++; - auto projectile = std::make_shared(mProjectileId, position, mTaskScheduler.get()); + auto projectile = std::make_shared(mProjectileId, position, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 8abf238cf..cbc5b1981 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -18,8 +18,9 @@ namespace MWPhysics { -Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler) +Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) : mActive(true) + , mPhysics(physicssystem) , mTaskScheduler(scheduler) , mProjectileId(projectileId) { @@ -35,7 +36,7 @@ Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTask setPosition(position); const int collisionMask = CollisionType_World | CollisionType_HeightMap | - CollisionType_Actor | CollisionType_Door | CollisionType_Water; + CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile; mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); commitPositionChange(); @@ -44,7 +45,11 @@ Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTask Projectile::~Projectile() { if (mCollisionObject) + { + if (!mActive) + mPhysics->reportCollision(mHitPosition, mHitNormal); mTaskScheduler->removeCollisionObject(mCollisionObject.get()); + } } void Projectile::commitPositionChange() @@ -64,13 +69,14 @@ void Projectile::setPosition(const osg::Vec3f &position) mTransformUpdatePending = true; } -void Projectile::hit(MWWorld::Ptr target, osg::Vec3f pos) +void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) { if (!mActive.load(std::memory_order_acquire)) return; std::unique_lock lock(mPositionMutex); mHitTarget = target; mHitPosition = pos; + mHitNormal = normal; mActive.store(false, std::memory_order_release); } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 7ad0a3124..6e8aba60b 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include "components/misc/convert.hpp" #include "ptrholder.hpp" @@ -32,7 +32,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler); + Projectile(const int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -68,7 +68,7 @@ namespace MWPhysics return Misc::Convert::toOsg(mHitPosition); } - void hit(MWWorld::Ptr target, osg::Vec3f pos); + void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); void activate(); private: @@ -81,12 +81,14 @@ namespace MWPhysics bool mTransformUpdatePending; std::atomic mActive; MWWorld::Ptr mHitTarget; - osg::Vec3f mHitPosition; + btVector3 mHitPosition; + btVector3 mHitNormal; mutable std::mutex mPositionMutex; osg::Vec3f mPosition; + PhysicsSystem *mPhysics; PhysicsTaskScheduler *mTaskScheduler; Projectile(const Projectile&); From 4fbe1ed12cbe2e2adaa4b95eefee42a24af8f5a4 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 21 Nov 2020 16:26:45 +0100 Subject: [PATCH 23/26] Ignore caster collision shape. Sometimes the magic bolt get launched inside too near its caster. --- .../mwphysics/closestnotmeconvexresultcallback.cpp | 12 ++++++++---- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/projectile.cpp | 3 ++- apps/openmw/mwphysics/projectile.hpp | 5 ++++- apps/openmw/mwworld/projectilemanager.cpp | 14 ++++++++------ 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index bbffe16a8..8f7242276 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -25,13 +25,17 @@ namespace MWPhysics if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) { - Projectile* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); + auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); if (!projectileHolder->isActive()) return btScalar(1); - PtrHolder* targetHolder = static_cast(mMe->getUserPointer()); + auto* targetHolder = static_cast(mMe->getUserPointer()); const MWWorld::Ptr target = targetHolder->getPtr(); - projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); - return btScalar(1); + // do nothing if we hit the caster. Sometimes the launching origin is inside of caster collision shape + if (projectileHolder->getCaster() != target) + { + projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); + return btScalar(1); + } } btVector3 hitNormalWorld; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 7da2a6964..324b1db2d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -661,10 +661,10 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const osg::Vec3f& position) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position) { mProjectileId++; - auto projectile = std::make_shared(mProjectileId, position, mTaskScheduler.get(), this); + auto projectile = std::make_shared(mProjectileId, caster, position, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6721a2d91..b33d829a4 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -128,7 +128,7 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const osg::Vec3f& position); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position); void updateProjectile(const int projectileId, const osg::Vec3f &position); void removeProjectile(const int projectileId); diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index cbc5b1981..5f5bc778b 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -18,8 +18,9 @@ namespace MWPhysics { -Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) +Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) : mActive(true) + , mCaster(caster) , mPhysics(physicssystem) , mTaskScheduler(scheduler) , mProjectileId(projectileId) diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 6e8aba60b..ed0fdce15 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -32,7 +32,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); + Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -62,6 +62,8 @@ namespace MWPhysics return mHitTarget; } + MWWorld::Ptr getCaster() const { return mCaster; } + osg::Vec3f getHitPos() const { assert(!mActive); @@ -80,6 +82,7 @@ namespace MWPhysics btTransform mLocalTransform; bool mTransformUpdatePending; std::atomic mActive; + MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; btVector3 mHitPosition; btVector3 mHitNormal; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 626c57ac3..dba4289a2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -316,7 +316,7 @@ namespace MWWorld state.mSounds.push_back(sound); } - state.mProjectileId = mPhysics->addProjectile(pos); + state.mProjectileId = mPhysics->addProjectile(caster, pos); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -340,7 +340,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(pos); + state.mProjectileId = mPhysics->addProjectile(actor, pos); state.mToDelete = false; mProjectiles.push_back(state); } @@ -546,7 +546,8 @@ namespace MWWorld const auto target = projectile->getTarget(); const auto pos = projectile->getHitPos(); MWWorld::Ptr caster = projectileState.getCaster(); - if (caster == target || !isValidTarget(caster, target)) + assert(target != caster); + if (!isValidTarget(caster, target)) { projectile->activate(); continue; @@ -581,7 +582,8 @@ namespace MWWorld const auto target = projectile->getTarget(); const auto pos = projectile->getHitPos(); MWWorld::Ptr caster = magicBoltState.getCaster(); - if (caster == target || !isValidTarget(caster, target)) + assert(target != caster); + if (!isValidTarget(caster, target)) { projectile->activate(); continue; @@ -724,7 +726,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(osg::Vec3f(esm.mPosition)); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); } catch(...) { @@ -769,7 +771,7 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); - state.mProjectileId = mPhysics->addProjectile(osg::Vec3f(esm.mPosition)); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition)); } catch(...) { From 39ac0cbb4ab37a373e4c67b8c2c18be74e45cde8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 8 Dec 2020 17:47:25 +0100 Subject: [PATCH 24/26] Don't crash the game when placing a non-actor --- apps/openmw/mwworld/worldimp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9a2dd1181..d00fabb66 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1345,7 +1345,11 @@ namespace MWWorld moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); if (force) // force physics to use the new position - mPhysics->getActor(ptr)->resetPosition(); + { + auto actor = mPhysics->getActor(ptr); + if(actor) + actor->resetPosition(); + } } void World::fixPosition() From 7a023e33ef5c4d222c1f68e65cedf06bb5d0895b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 8 Dec 2020 20:13:42 +0100 Subject: [PATCH 25/26] Revert pointless shader version bump. --- files/shaders/terrain_vertex.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 71046a683..bf337cf54 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,4 +1,4 @@ -#version 130 +#version 120 varying vec2 uv; varying float euclideanDepth; From 1bebe51c29cc7e5b9440bd0f12712202a3c9dcc9 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 8 Dec 2020 21:37:27 +0100 Subject: [PATCH 26/26] Make use of shared shadow maps in the brute force method --- components/misc/stereo.cpp | 145 ++++++++++++++++++++++--------------- components/misc/stereo.hpp | 7 ++ files/settings-default.cfg | 3 + 3 files changed, 96 insertions(+), 59 deletions(-) diff --git a/components/misc/stereo.cpp b/components/misc/stereo.cpp index b57c71b01..5816c4d9e 100644 --- a/components/misc/stereo.cpp +++ b/components/misc/stereo.cpp @@ -233,11 +233,19 @@ namespace Misc , mTechnique(technique) , mGeometryShaderMask(geometryShaderMask) , mNoShaderMask(noShaderMask) + , mMasterConfig(new SharedShadowMapConfig) + , mSlaveConfig(new SharedShadowMapConfig) + , mSharedShadowMaps(Settings::Manager::getBool("shared shadow maps", "Stereo")) { if (technique == Technique::None) // Do nothing return; + mMasterConfig->_id = "STEREO"; + mMasterConfig->_master = true; + mSlaveConfig->_id = "STEREO"; + mSlaveConfig->_master = false; + SceneUtil::FindByNameVisitor findScene("Scene Root"); mRoot->accept(findScene); mScene = findScene.mFoundNode; @@ -292,6 +300,12 @@ namespace Misc mRightCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mRightCamera->setCullMask(mMainCamera->getCullMask()); + if (mSharedShadowMaps) + { + mLeftCamera->setUserData(mMasterConfig); + mRightCamera->setUserData(mSlaveConfig); + } + // Slave cameras must have their viewports defined immediately auto width = mMainCamera->getViewport()->width(); auto height = mMainCamera->getViewport()->height(); @@ -366,65 +380,66 @@ namespace Misc auto width = mMainCamera->getViewport()->width(); auto height = mMainCamera->getViewport()->height(); + // To correctly cull when drawing stereo using the geometry shader, the main camera must + // draw a fake view+perspective that includes the full frustums of both the left and right eyes. + // This frustum will be computed as a perspective frustum from a position P slightly behind the eyes L and R + // where it creates the minimum frustum encompassing both eyes' frustums. + // NOTE: I make an assumption that the eyes lie in a horizontal plane relative to the base view, + // and lie mirrored around the Y axis (straight ahead). + // Re-think this if that turns out to be a bad assumption. + View frustumView; + + // Compute Frustum angles. A simple min/max. + /* Example values for reference: + Left: + angleLeft -0.767549932 float + angleRight 0.620896876 float + angleDown -0.837898076 float + angleUp 0.726982594 float + + Right: + angleLeft -0.620896876 float + angleRight 0.767549932 float + angleDown -0.837898076 float + angleUp 0.726982594 float + */ + frustumView.fov.angleLeft = std::min(left.fov.angleLeft, right.fov.angleLeft); + frustumView.fov.angleRight = std::max(left.fov.angleRight, right.fov.angleRight); + frustumView.fov.angleDown = std::min(left.fov.angleDown, right.fov.angleDown); + frustumView.fov.angleUp = std::max(left.fov.angleUp, right.fov.angleUp); + + // Check that the case works for this approach + auto maxAngle = std::max(frustumView.fov.angleRight - frustumView.fov.angleLeft, frustumView.fov.angleUp - frustumView.fov.angleDown); + if (maxAngle > osg::PI) + { + Log(Debug::Error) << "Total FOV exceeds 180 degrees. Case cannot be culled in single-pass VR. Disabling culling to cope. Consider switching to dual-pass VR."; + mMainCamera->setCullingActive(false); + return; + // TODO: An explicit frustum projection could cope, so implement that later. Guarantee you there will be VR headsets with total horizontal fov > 180 in the future. Maybe already. + } + + // Use the law of sines on the triangle spanning PLR to determine P + double angleLeft = std::abs(frustumView.fov.angleLeft); + double angleRight = std::abs(frustumView.fov.angleRight); + double lengthRL = (rightEye - leftEye).length(); + double ratioRL = lengthRL / std::sin(osg::PI - angleLeft - angleRight); + double lengthLP = ratioRL * std::sin(angleRight); + + osg::Vec3d directionLP = osg::Vec3(std::cos(-angleLeft), std::sin(-angleLeft), 0); + osg::Vec3d LP = directionLP * lengthLP; + frustumView.pose.position = leftEye + LP; + //frustumView.pose.position.x() += 1000; + + // Base view position is 0.0, by definition. + // The length of the vector P is therefore the required offset to near/far. + auto nearFarOffset = frustumView.pose.position.length(); + + // Generate the frustum matrices + auto frustumViewMatrix = viewMatrix * frustumView.pose.viewMatrix(true); + auto frustumProjectionMatrix = frustumView.fov.perspectiveMatrix(near + nearFarOffset, far + nearFarOffset); + if (mTechnique == Technique::GeometryShader_IndexedViewports) { - // To correctly cull when drawing stereo using the geometry shader, the main camera must - // draw a fake view+perspective that includes the full frustums of both the left and right eyes. - // This frustum will be computed as a perspective frustum from a position P slightly behind the eyes L and R - // where it creates the minimum frustum encompassing both eyes' frustums. - // NOTE: I make an assumption that the eyes lie in a horizontal plane relative to the base view, - // and lie mirrored around the Y axis (straight ahead). - // Re-think this if that turns out to be a bad assumption. - View frustumView; - - // Compute Frustum angles. A simple min/max. - /* Example values for reference: - Left: - angleLeft -0.767549932 float - angleRight 0.620896876 float - angleDown -0.837898076 float - angleUp 0.726982594 float - - Right: - angleLeft -0.620896876 float - angleRight 0.767549932 float - angleDown -0.837898076 float - angleUp 0.726982594 float - */ - frustumView.fov.angleLeft = std::min(left.fov.angleLeft, right.fov.angleLeft); - frustumView.fov.angleRight = std::max(left.fov.angleRight, right.fov.angleRight); - frustumView.fov.angleDown = std::min(left.fov.angleDown, right.fov.angleDown); - frustumView.fov.angleUp = std::max(left.fov.angleUp, right.fov.angleUp); - - // Check that the case works for this approach - auto maxAngle = std::max(frustumView.fov.angleRight - frustumView.fov.angleLeft, frustumView.fov.angleUp - frustumView.fov.angleDown); - if (maxAngle > osg::PI) - { - Log(Debug::Error) << "Total FOV exceeds 180 degrees. Case cannot be culled in single-pass VR. Disabling culling to cope. Consider switching to dual-pass VR."; - mMainCamera->setCullingActive(false); - return; - // TODO: An explicit frustum projection could cope, so implement that later. Guarantee you there will be VR headsets with total fov > 180 in the future. Maybe already. - } - - // Use the law of sines on the triangle spanning PLR to determine P - double angleLeft = std::abs(frustumView.fov.angleLeft); - double angleRight = std::abs(frustumView.fov.angleRight); - double lengthRL = (rightEye - leftEye).length(); - double ratioRL = lengthRL / std::sin(osg::PI - angleLeft - angleRight); - double lengthLP = ratioRL * std::sin(angleRight); - - osg::Vec3d directionLP = osg::Vec3(std::cos(-angleLeft), std::sin(-angleLeft), 0); - osg::Vec3d LP = directionLP * lengthLP; - frustumView.pose.position = leftEye + LP; - //frustumView.pose.position.x() += 1000; - - // Base view position is 0.0, by definition. - // The length of the vector P is therefore the required offset to near/far. - auto nearFarOffset = frustumView.pose.position.length(); - - // Generate the frustum matrices - auto frustumViewMatrix = viewMatrix * frustumView.pose.viewMatrix(true); - auto frustumProjectionMatrix = frustumView.fov.perspectiveMatrix(near + nearFarOffset, far + nearFarOffset); // Update camera with frustum matrices mMainCamera->setViewMatrix(frustumViewMatrix); @@ -439,6 +454,18 @@ namespace Misc mLeftCamera->setViewport(0, 0, width / 2, height); mRightCamera->setViewport(width / 2, 0, width / 2, height); + + if (mMasterConfig->_projection == nullptr) + mMasterConfig->_projection = new osg::RefMatrix; + if (mMasterConfig->_modelView == nullptr) + mMasterConfig->_modelView = new osg::RefMatrix; + + if (mSharedShadowMaps) + { + mMasterConfig->_referenceFrame = mMainCamera->getReferenceFrame(); + mMasterConfig->_modelView->set(frustumViewMatrix); + mMasterConfig->_projection->set(projectionMatrix); + } } } @@ -462,9 +489,9 @@ namespace Misc stereoViewProjectionsUniform->setElement(1, frustumViewMatrixInverse * mRightCamera->getViewMatrix() * mRightCamera->getProjectionMatrix()); } - void StereoView::setUpdateViewCallback(std::shared_ptr cb) + void StereoView::setUpdateViewCallback(std::shared_ptr cb_) { - this->cb = cb; + cb = cb_; } void disableStereoForCamera(osg::Camera* camera) diff --git a/components/misc/stereo.hpp b/components/misc/stereo.hpp index c721f6a1c..e8e2d5886 100644 --- a/components/misc/stereo.hpp +++ b/components/misc/stereo.hpp @@ -8,6 +8,8 @@ #include +#include + // Some cursed headers like to define these #if defined(near) || defined(far) #undef near @@ -122,6 +124,11 @@ namespace Misc osg::ref_ptr mLeftCamera{ new osg::Camera }; osg::ref_ptr mRightCamera{ new osg::Camera }; + using SharedShadowMapConfig = SceneUtil::MWShadowTechnique::SharedShadowMapConfig; + osg::ref_ptr mMasterConfig; + osg::ref_ptr mSlaveConfig; + bool mSharedShadowMaps; + // Camera viewports bool flipViewOrder{ true }; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 5e28f0a73..9e7d39a0f 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -964,3 +964,6 @@ stereo enabled = false # BruteForce: Generates stereo using two cameras and two cull/render passes. Choose this if your game is GPU-bound. # GeometryShader: Generates stereo in a single pass using automatically generated geometry shaders. May break custom shaders. Choose this if your game is CPU-bound. stereo method = GeometryShader + +# May accelerate the BruteForce method when shadows are enabled +shared shadow maps = true \ No newline at end of file