diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 378881d8dc..0bb8a26c98 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -1787,6 +1787,168 @@ struct ConvexHull _edges.push_back( Edge(frustum.corners[3],frustum.corners[7]) ); } + struct ConvexHull2D + { + // Implementation based on https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain#C++ + typedef osg::Vec3d Point; + + static double cross(const Point &O, const Point &A, const Point &B) + { + return (A.x() - O.x())*(B.y() - O.y()) - (A.y() - O.y())*(B.x() - O.x()); + } + + // Calculates the 2D convex hull and returns it as a vector containing the points in CCW order with the first and last point being the same. + static std::vector convexHull(std::set &P) + { + size_t n = P.size(), k = 0; + if (n <= 3) + return std::vector(P.cbegin(), P.cend()); + + std::vector H(2 * n); + + // Points are already sorted in a std::set + + // Build lower hull + for (auto pItr = P.cbegin(); pItr != P.cend(); ++pItr) + { + while (k >= 2 && cross(H[k - 2], H[k - 1], *pItr) <= 0) + k--; + H[k++] = *pItr; + } + + // Build upper hull + size_t t = k + 1; + for (auto pItr = std::next(P.crbegin()); pItr != P.crend(); ++pItr) + { + while (k >= t && cross(H[k - 2], H[k - 1], *pItr) <= 0) + k--; + H[k++] = *pItr; + } + + H.resize(k - 1); + return H; + } + }; + + bool shouldBeDeleted(osg::Vec3d vertex, std::set &extremeVertices) + { + // A vertex should be deleted if there is no route -Z-wards to an extreme vertex + // equivalent to all -Z-wards vertices being deletable. + if (extremeVertices.find(vertex) != extremeVertices.end()) + return false; + for (Edge edge : _edges) + { + osg::Vec3d otherEnd; + if (edge.first == vertex) + otherEnd = edge.second; + else if (edge.second == vertex) + otherEnd = edge.first; + else + continue; + + if (otherEnd.z() >= vertex.z()) + continue; + + if (!shouldBeDeleted(otherEnd, extremeVertices)) + return false; + } + + return true; + } + + void extendTowardsNegativeZ() + { + typedef std::set VertexSet; + + // Collect the set of vertices + VertexSet vertices; + for (Edge edge : _edges) + { + vertices.insert(edge.first); + vertices.insert(edge.second); + } + + if (vertices.size() == 0) + return; + + // Get the vertices contributing to the 2D convex hull + Vertices extremeVertices = ConvexHull2D::convexHull(vertices); + VertexSet extremeVerticesSet(extremeVertices.cbegin(), extremeVertices.cend()); + + // Add their extrusions to the final edge collection + Edges finalEdges; + // Add edges towards -Z + for (auto vertex : extremeVertices) + finalEdges.push_back(Edge(vertex, osg::Vec3d(vertex.x(), vertex.y(), -DBL_MAX))); + // Add edge loop to 'seal' the hull + for (auto itr = extremeVertices.cbegin(); itr != extremeVertices.cend() - 1; ++itr) + finalEdges.push_back(Edge(osg::Vec3d(itr->x(), itr->y(), -DBL_MAX), osg::Vec3d((itr + 1)->x(), (itr + 1)->y(), -DBL_MAX))); + // The convex hull algorithm we are using places a point at both ends of the vector, so we don't need to add the last edge separately. + // finalEdges.push_back(Edge(osg::Vec3d(extremeVertices.front().x(), extremeVertices.front().y(), -DBL_MAX), osg::Vec3d(extremeVertices.back().x(), extremeVertices.back().y(), -DBL_MAX))); + + // Collect the first layer of unneeded vertices and remove the edges connecting them to the rest of the mesh + VertexSet deletedVertices; + for (auto edgeItr = _edges.begin(); edgeItr != _edges.end(); /* nothing */ ) + { + if (extremeVerticesSet.find(edgeItr->first) != extremeVerticesSet.end()) + { + if (extremeVerticesSet.find(edgeItr->second) == extremeVerticesSet.end()) + { + if (edgeItr->first.z() >= edgeItr->second.z()) + { + // If we can travel along edges towards -Z and reach an extreme vertex, the current edge must be kept + if (shouldBeDeleted(edgeItr->second, extremeVerticesSet)) + { + deletedVertices.insert(edgeItr->second); + edgeItr = _edges.erase(edgeItr); + continue; + } + } + } + } + else if (extremeVerticesSet.find(edgeItr->second) != extremeVerticesSet.end()) + { + if (edgeItr->second.z() >= edgeItr->first.z()) + { + if (shouldBeDeleted(edgeItr->first, extremeVerticesSet)) + { + deletedVertices.insert(edgeItr->first); + edgeItr = _edges.erase(edgeItr); + continue; + } + } + } + ++edgeItr; + } + + // Remove all edges connected to removed vertices + bool modifiedSomething = true; + while (modifiedSomething) + { + modifiedSomething = false; + for (auto edgeItr = _edges.begin(); edgeItr != _edges.end(); /* nothing */) + { + if (deletedVertices.find(edgeItr->first) != deletedVertices.end()) + { + deletedVertices.insert(edgeItr->second); + edgeItr = _edges.erase(edgeItr); + modifiedSomething = true; + continue; + } + else if (deletedVertices.find(edgeItr->second) != deletedVertices.end()) + { + deletedVertices.insert(edgeItr->first); + edgeItr = _edges.erase(edgeItr); + modifiedSomething = true; + continue; + } + ++edgeItr; + } + } + + _edges.splice(_edges.end(), finalEdges); + } + void transform(const osg::Matrixd& m) { for(Edges::iterator itr = _edges.begin(); @@ -2186,6 +2348,8 @@ bool MWShadowTechnique::cropShadowCameraToMainFrustum(Frustum& frustum, osg::Cam convexHull.transform(light_vp); + convexHull.extendTowardsNegativeZ(); + double xMin = -1.0, xMax = 1.0; double yMin = -1.0, yMax = 1.0; double zMin = -1.0, zMax = 1.0; @@ -2269,6 +2433,8 @@ bool MWShadowTechnique::adjustPerspectiveShadowMapCameraSettings(osgUtil::Render convexHull.transform(light_vp); + convexHull.extendTowardsNegativeZ(); + #if 0 convexHull.output(osg::notify(osg::NOTICE));