diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 9e169b2563..e00e3446a6 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -6,10 +6,12 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -21,121 +23,287 @@ namespace MWRender { - class InstancingVisitor : public osg::NodeVisitor + namespace { - public: - InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mInstances(instances) - , mChunkPosition(chunkPosition) + using value_type = osgUtil::CullVisitor::value_type; + + // From OSG's CullVisitor.cpp + inline value_type distance(const osg::Vec3& coord, const osg::Matrix& matrix) { + return -((value_type)coord[0] * (value_type)matrix(0, 2) + (value_type)coord[1] * (value_type)matrix(1, 2) + + (value_type)coord[2] * (value_type)matrix(2, 2) + matrix(3, 2)); } - void apply(osg::Geometry& geom) override + inline osg::Matrix computeInstanceMatrix( + const Groundcover::GroundcoverEntry& entry, const osg::Vec3& chunkPosition) { - for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) - { - geom.getPrimitiveSet(i)->setNumInstances(mInstances.size()); - } - - osg::ref_ptr transforms = new osg::Vec4Array(mInstances.size()); - osg::BoundingBox box; - float radius = geom.getBoundingBox().radius(); - for (unsigned int i = 0; i < transforms->getNumElements(); i++) - { - osg::Vec3f pos(mInstances[i].mPos.asVec3()); - osg::Vec3f relativePos = pos - mChunkPosition; - (*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale); - - // Use an additional margin due to groundcover animation - float instanceRadius = radius * mInstances[i].mScale * 1.1f; - osg::BoundingSphere instanceBounds(relativePos, instanceRadius); - box.expandBy(instanceBounds); - } - - geom.setInitialBound(box); - - osg::ref_ptr rotations = new osg::Vec3Array(mInstances.size()); - for (unsigned int i = 0; i < rotations->getNumElements(); i++) - { - (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); - } - - // Display lists do not support instancing in OSG 3.4 - geom.setUseDisplayList(false); - geom.setUseVertexBufferObjects(true); - - geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); - geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); + return osg::Matrix::scale(entry.mScale, entry.mScale, entry.mScale) + * osg::Matrix(Misc::Convert::makeOsgQuat(entry.mPos)) + * osg::Matrix::translate(entry.mPos.asVec3() - chunkPosition); } - private: - std::vector mInstances; - osg::Vec3f mChunkPosition; - }; - - class DensityCalculator - { - public: - DensityCalculator(float density) - : mDensity(density) + class InstancedComputeNearFarCullCallback : public osg::DrawableCullCallback { - } + public: + InstancedComputeNearFarCullCallback(const std::vector& instances, + const osg::Vec3& chunkPosition, const osg::BoundingBox& instanceBounds) + : mInstanceMatrices() + , mInstanceBounds(instanceBounds) + { + mInstanceMatrices.reserve(instances.size()); + for (const auto& instance : instances) + mInstanceMatrices.emplace_back(computeInstanceMatrix(instance, chunkPosition)); + } - bool isInstanceEnabled() + bool cull(osg::NodeVisitor* nv, osg::Drawable* drawable, osg::RenderInfo* renderInfo) const override + { + osgUtil::CullVisitor& cullVisitor = *nv->asCullVisitor(); + osg::CullSettings::ComputeNearFarMode cnfMode = cullVisitor.getComputeNearFarMode(); + const osg::BoundingBox& boundingBox = drawable->getBoundingBox(); + osg::RefMatrix& matrix = *cullVisitor.getModelViewMatrix(); + + if (cnfMode != osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES + && cnfMode != osg::CullSettings::COMPUTE_NEAR_USING_PRIMITIVES) + return false; + + if (drawable->isCullingActive() && cullVisitor.isCulled(boundingBox)) + return true; + + osg::Vec3 lookVector = cullVisitor.getLookVectorLocal(); + unsigned int bbCornerFar + = (lookVector.x() >= 0 ? 1 : 0) | (lookVector.y() >= 0 ? 2 : 0) | (lookVector.z() >= 0 ? 4 : 0); + unsigned int bbCornerNear = (~bbCornerFar) & 7; + value_type dNear = distance(boundingBox.corner(bbCornerNear), matrix); + value_type dFar = distance(boundingBox.corner(bbCornerFar), matrix); + + if (dNear > dFar) + std::swap(dNear, dFar); + + if (dFar < 0) + return true; + + value_type computedZNear = cullVisitor.getCalculatedNearPlane(); + value_type computedZFar = cullVisitor.getCalculatedFarPlane(); + + if (dNear < computedZNear || dFar > computedZFar) + { + osg::Polytope frustum; + osg::Polytope::ClippingMask resultMask + = cullVisitor.getCurrentCullingSet().getFrustum().getResultMask(); + if (resultMask) + { + // Other objects are likely cheaper and should let us skip all but a few groundcover instances + cullVisitor.computeNearPlane(); + + if (dNear < computedZNear) + { + dNear = computedZNear; + for (const auto& instanceMatrix : mInstanceMatrices) + { + osg::Matrix fullMatrix = instanceMatrix * matrix; + osg::Vec3 instanceLookVector(-fullMatrix(0, 2), -fullMatrix(1, 2), -fullMatrix(2, 2)); + unsigned int instanceBbCornerFar = (instanceLookVector.x() >= 0 ? 1 : 0) + | (instanceLookVector.y() >= 0 ? 2 : 0) | (instanceLookVector.z() >= 0 ? 4 : 0); + unsigned int instanceBbCornerNear = (~instanceBbCornerFar) & 7; + value_type instanceDNear + = distance(mInstanceBounds.corner(instanceBbCornerNear), fullMatrix); + value_type instanceDFar + = distance(mInstanceBounds.corner(instanceBbCornerFar), fullMatrix); + + if (instanceDNear > instanceDFar) + std::swap(instanceDNear, instanceDFar); + + if (instanceDFar < 0 || instanceDNear > dNear) + continue; + + frustum.setAndTransformProvidingInverse( + cullVisitor.getProjectionCullingStack().back().getFrustum(), fullMatrix); + osg::Polytope::PlaneList planes; + osg::Polytope::ClippingMask selectorMask = 0x1; + for (const auto& plane : frustum.getPlaneList()) + { + if (resultMask & selectorMask) + planes.push_back(plane); + selectorMask <<= 1; + } + + value_type newNear + = cullVisitor.computeNearestPointInFrustum(fullMatrix, planes, *drawable); + dNear = std::min(dNear, newNear); + } + if (dNear < computedZNear) + cullVisitor.setCalculatedNearPlane(dNear); + } + + if (cnfMode == osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES && dFar > computedZFar) + { + dFar = computedZFar; + for (const auto& instanceMatrix : mInstanceMatrices) + { + osg::Matrix fullMatrix = instanceMatrix * matrix; + osg::Vec3 instanceLookVector(-fullMatrix(0, 2), -fullMatrix(1, 2), -fullMatrix(2, 2)); + unsigned int instanceBbCornerFar = (instanceLookVector.x() >= 0 ? 1 : 0) + | (instanceLookVector.y() >= 0 ? 2 : 0) | (instanceLookVector.z() >= 0 ? 4 : 0); + unsigned int instanceBbCornerNear = (~instanceBbCornerFar) & 7; + value_type instanceDNear + = distance(mInstanceBounds.corner(instanceBbCornerNear), fullMatrix); + value_type instanceDFar + = distance(mInstanceBounds.corner(instanceBbCornerFar), fullMatrix); + + if (instanceDNear > instanceDFar) + std::swap(instanceDNear, instanceDFar); + + if (instanceDFar < 0 || instanceDFar < dFar) + continue; + + frustum.setAndTransformProvidingInverse( + cullVisitor.getProjectionCullingStack().back().getFrustum(), fullMatrix); + osg::Polytope::PlaneList planes; + osg::Polytope::ClippingMask selectorMask = 0x1; + for (const auto& plane : frustum.getPlaneList()) + { + if (resultMask & selectorMask) + planes.push_back(plane); + selectorMask <<= 1; + } + + value_type newFar = cullVisitor.computeFurthestPointInFrustum( + instanceMatrix * matrix, planes, *drawable); + dFar = std::max(dFar, newFar); + } + if (dFar > computedZFar) + cullVisitor.setCalculatedFarPlane(dFar); + } + } + } + + return false; + } + + private: + std::vector mInstanceMatrices; + osg::BoundingBox mInstanceBounds; + }; + + class InstancingVisitor : public osg::NodeVisitor { - if (mDensity >= 1.f) + public: + InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mInstances(instances) + , mChunkPosition(chunkPosition) + { + } + + void apply(osg::Geometry& geom) override + { + for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) + { + geom.getPrimitiveSet(i)->setNumInstances(mInstances.size()); + } + + osg::ref_ptr transforms = new osg::Vec4Array(mInstances.size()); + osg::BoundingBox box; + osg::BoundingBox originalBox = geom.getBoundingBox(); + float radius = originalBox.radius(); + for (unsigned int i = 0; i < transforms->getNumElements(); i++) + { + osg::Vec3f pos(mInstances[i].mPos.asVec3()); + osg::Vec3f relativePos = pos - mChunkPosition; + (*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale); + + // Use an additional margin due to groundcover animation + float instanceRadius = radius * mInstances[i].mScale * 1.1f; + osg::BoundingSphere instanceBounds(relativePos, instanceRadius); + box.expandBy(instanceBounds); + } + + geom.setInitialBound(box); + + osg::ref_ptr rotations = new osg::Vec3Array(mInstances.size()); + for (unsigned int i = 0; i < rotations->getNumElements(); i++) + { + (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); + } + + // Display lists do not support instancing in OSG 3.4 + geom.setUseDisplayList(false); + geom.setUseVertexBufferObjects(true); + + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); + geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); + + geom.addCullCallback(new InstancedComputeNearFarCullCallback(mInstances, mChunkPosition, originalBox)); + } + + private: + std::vector mInstances; + osg::Vec3f mChunkPosition; + }; + + class DensityCalculator + { + public: + DensityCalculator(float density) + : mDensity(density) + { + } + + bool isInstanceEnabled() + { + if (mDensity >= 1.f) + return true; + + mCurrentGroundcover += mDensity; + if (mCurrentGroundcover < 1.f) + return false; + + mCurrentGroundcover -= 1.f; + + return true; + } + void reset() { mCurrentGroundcover = 0.f; } + + private: + float mCurrentGroundcover = 0.f; + float mDensity = 0.f; + }; + + class ViewDistanceCallback : public SceneUtil::NodeCallback + { + public: + ViewDistanceCallback(float dist, const osg::BoundingBox& box) + : mViewDistance(dist) + , mBox(box) + { + } + void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (Terrain::distance(mBox, nv->getEyePoint()) <= mViewDistance) + traverse(node, nv); + } + + private: + float mViewDistance; + osg::BoundingBox mBox; + }; + + inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) + { + osg::Vec2f size = maxBound - minBound; + if (size.x() >= 1 && size.y() >= 1) return true; - mCurrentGroundcover += mDensity; - if (mCurrentGroundcover < 1.f) + osg::Vec3f pos = ref.mPos.asVec3(); + osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) + || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) + || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) return false; - mCurrentGroundcover -= 1.f; - return true; } - void reset() { mCurrentGroundcover = 0.f; } - - private: - float mCurrentGroundcover = 0.f; - float mDensity = 0.f; - }; - - class ViewDistanceCallback : public SceneUtil::NodeCallback - { - public: - ViewDistanceCallback(float dist, const osg::BoundingBox& box) - : mViewDistance(dist) - , mBox(box) - { - } - void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - if (Terrain::distance(mBox, nv->getEyePoint()) <= mViewDistance) - traverse(node, nv); - } - - private: - float mViewDistance; - osg::BoundingBox mBox; - }; - - inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) - { - osg::Vec2f size = maxBound - minBound; - if (size.x() >= 1 && size.y() >= 1) - return true; - - osg::Vec3f pos = ref.mPos.asVec3(); - osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; - if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) - || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) - || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) - || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) - return false; - - return true; } osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod,