// David Eberly, Geometric Tools, Redmond WA 98052 // Copyright (c) 1998-2020 // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt // Version: 4.0.2019.08.13 #pragma once #include #include #include #include #include // Test whether an undirected graph is planar. The input positions must be // unique and the input edges must be unique, so the number of positions is // at least 2 and the number of edges is at least one. The elements of the // edges array must be indices in {0,..,positions.size()-1}. // // A sort-and-sweep algorithm is used to determine edge-edge intersections. // If none of the intersections occur at edge-interior points, the graph is // planar. See Game Physics (2nd edition), Section 6.2.2: Culling with // Axis-Aligned Bounding Boxes for such an algorithm. The operator() // returns 'true' when the graph is planar. If it returns 'false', the // 'invalidIntersections' set contains pairs of edges that intersect at an // edge-interior point (that by definition is not a graph vertex). Each set // element is a pair of indices into the 'edges' array; the indices are // ordered so that element[0] < element[1]. The Real type must be chosen // to guarantee exact computation of edge-edge intersections. namespace WwiseGTE { template class IsPlanarGraph { public: IsPlanarGraph() : mZero(0), mOne(1) { } enum { IPG_IS_PLANAR_GRAPH = 0, IPG_INVALID_INPUT_SIZES = 1, IPG_DUPLICATED_POSITIONS = 2, IPG_DUPLICATED_EDGES = 4, IPG_DEGENERATE_EDGES = 8, IPG_EDGES_WITH_INVALID_VERTICES = 16, IPG_INVALID_INTERSECTIONS = 32 }; // The function returns a combination of the IPG_* flags listed in the // previous enumeration. A combined value of 0 indicates the input // forms a planar graph. If the combined value is not zero, you may // examine the flags for the failure conditions and use the Get* // member accessors to obtain specific information about the failure. // If the positions.size() < 2 or edges.size() == 0, the // IPG_INVALID_INPUT_SIZES flag is set. int operator()(std::vector> const& positions, std::vector> const& edges) { mDuplicatedPositions.clear(); mDuplicatedEdges.clear(); mDegenerateEdges.clear(); mEdgesWithInvalidVertices.clear(); mInvalidIntersections.clear(); int flags = IsValidTopology(positions, edges); if (flags == IPG_INVALID_INPUT_SIZES) { return flags; } std::set overlappingRectangles; ComputeOverlappingRectangles(positions, edges, overlappingRectangles); for (auto key : overlappingRectangles) { // Get the endpoints of the line segments for the edges whose // bounding rectangles overlapped. Determine whether the line // segments intersect. If they do, determine how they // intersect. std::array e0 = edges[key.V[0]]; std::array e1 = edges[key.V[1]]; std::array const& p0 = positions[e0[0]]; std::array const& p1 = positions[e0[1]]; std::array const& q0 = positions[e1[0]]; std::array const& q1 = positions[e1[1]]; if (InvalidSegmentIntersection(p0, p1, q0, q1)) { mInvalidIntersections.push_back(key); } } if (mInvalidIntersections.size() > 0) { flags |= IPG_INVALID_INTERSECTIONS; } return flags; } // A pair of indices (v0,v1) into the positions array is stored as // (min(v0,v1), max(v0,v1)). This supports sorted containers of // edges. struct OrderedEdge { OrderedEdge(int v0 = -1, int v1 = -1) { if (v0 < v1) { // v0 is minimum V[0] = v0; V[1] = v1; } else { // v1 is minimum V[0] = v1; V[1] = v0; } } bool operator<(OrderedEdge const& edge) const { // Lexicographical ordering used by std::array. return V < edge.V; } std::array V; }; inline std::vector> const& GetDuplicatedPositions() const { return mDuplicatedPositions; } inline std::vector> const& GetDuplicatedEdges() const { return mDuplicatedEdges; } inline std::vector const& GetDegenerateEdges() const { return mDegenerateEdges; } inline std::vector const& GetEdgesWithInvalidVertices() const { return mEdgesWithInvalidVertices; } inline std::vector::OrderedEdge> const& GetInvalidIntersections() const { return mInvalidIntersections; } private: class Endpoint { public: Real value; // endpoint value int type; // '0' if interval min, '1' if interval max. int index; // index of interval containing this endpoint // Comparison operator for sorting. bool operator<(Endpoint const& endpoint) const { if (value < endpoint.value) { return true; } if (value > endpoint.value) { return false; } return type < endpoint.type; } }; int IsValidTopology(std::vector> const& positions, std::vector> const& edges) { int const numPositions = static_cast(positions.size()); int const numEdges = static_cast(edges.size()); if (numPositions < 2 || numEdges == 0) { // The graph must have at least one edge. return IPG_INVALID_INPUT_SIZES; } // The positions must be unique. int flags = IPG_IS_PLANAR_GRAPH; std::map, std::vector> uniquePositions; for (int i = 0; i < numPositions; ++i) { std::array p = positions[i]; auto iter = uniquePositions.find(p); if (iter == uniquePositions.end()) { std::vector indices; indices.push_back(i); uniquePositions.insert(std::make_pair(p, indices)); } else { iter->second.push_back(i); } } if (uniquePositions.size() < positions.size()) { // At least two positions are duplicated. for (auto const& element : uniquePositions) { if (element.second.size() > 1) { mDuplicatedPositions.push_back(element.second); } } flags |= IPG_DUPLICATED_POSITIONS; } // The edges must be unique. std::map> uniqueEdges; for (int i = 0; i < numEdges; ++i) { OrderedEdge key(edges[i][0], edges[i][1]); auto iter = uniqueEdges.find(key); if (iter == uniqueEdges.end()) { std::vector indices; indices.push_back(i); uniqueEdges.insert(std::make_pair(key, indices)); } else { iter->second.push_back(i); } } if (uniqueEdges.size() < edges.size()) { // At least two edges are duplicated, possibly even a pair of // edges (v0,v1) and (v1,v0) which is not allowed because the // graph is undirected. for (auto const& element : uniqueEdges) { if (element.second.size() > 1) { mDuplicatedEdges.push_back(element.second); } } flags |= IPG_DUPLICATED_EDGES; } // The edges are represented as pairs of indices into the // 'positions' array. The indices for a single edge must be // different (no edges allowed from a vertex to itself) and all // indices must be valid. At the same time, keep track of unique // edges. for (int i = 0; i < numEdges; ++i) { std::array e = edges[i]; if (e[0] == e[1]) { // The edge is degenerate, originating and terminating at // the same vertex. mDegenerateEdges.push_back(i); flags |= IPG_DEGENERATE_EDGES; } if (e[0] < 0 || e[0] >= numPositions || e[1] < 0 || e[1] >= numPositions) { // The edge has an index that references a nonexistent // vertex. mEdgesWithInvalidVertices.push_back(i); flags |= IPG_EDGES_WITH_INVALID_VERTICES; } } return flags; } void ComputeOverlappingRectangles(std::vector> const& positions, std::vector> const& edges, std::set& overlappingRectangles) const { // Compute axis-aligned bounding rectangles for the edges. int const numEdges = static_cast(edges.size()); std::vector> emin(numEdges); std::vector> emax(numEdges); for (int i = 0; i < numEdges; ++i) { std::array e = edges[i]; std::array const& p0 = positions[e[0]]; std::array const& p1 = positions[e[1]]; for (int j = 0; j < 2; ++j) { if (p0[j] < p1[j]) { emin[i][j] = p0[j]; emax[i][j] = p1[j]; } else { emin[i][j] = p1[j]; emax[i][j] = p0[j]; } } } // Get the rectangle endpoints. int const numEndpoints = 2 * numEdges; std::vector xEndpoints(numEndpoints); std::vector yEndpoints(numEndpoints); for (int i = 0, j = 0; i < numEdges; ++i) { xEndpoints[j].type = 0; xEndpoints[j].value = emin[i][0]; xEndpoints[j].index = i; yEndpoints[j].type = 0; yEndpoints[j].value = emin[i][1]; yEndpoints[j].index = i; ++j; xEndpoints[j].type = 1; xEndpoints[j].value = emax[i][0]; xEndpoints[j].index = i; yEndpoints[j].type = 1; yEndpoints[j].value = emax[i][1]; yEndpoints[j].index = i; ++j; } // Sort the rectangle endpoints. std::sort(xEndpoints.begin(), xEndpoints.end()); std::sort(yEndpoints.begin(), yEndpoints.end()); // Sweep through the endpoints to determine overlapping // x-intervals. Use an active set of rectangles to reduce the // complexity of the algorithm. std::set active; for (int i = 0; i < numEndpoints; ++i) { Endpoint const& endpoint = xEndpoints[i]; int index = endpoint.index; if (endpoint.type == 0) // an interval 'begin' value { // In the 1D problem, the current interval overlaps with // all the active intervals. In 2D this we also need to // check for y-overlap. for (auto activeIndex : active) { // Rectangles activeIndex and index overlap in the // x-dimension. Test for overlap in the y-dimension. std::array const& r0min = emin[activeIndex]; std::array const& r0max = emax[activeIndex]; std::array const& r1min = emin[index]; std::array const& r1max = emax[index]; if (r0max[1] >= r1min[1] && r0min[1] <= r1max[1]) { if (activeIndex < index) { overlappingRectangles.insert(OrderedEdge(activeIndex, index)); } else { overlappingRectangles.insert(OrderedEdge(index, activeIndex)); } } } active.insert(index); } else // an interval 'end' value { active.erase(index); } } } bool InvalidSegmentIntersection( std::array const& p0, std::array const& p1, std::array const& q0, std::array const& q1) const { // We must solve the two linear equations // p0 + t0 * (p1 - p0) = q0 + t1 * (q1 - q0) // for the unknown variables t0 and t1. These may be written as // t0 * (p1 - p0) - t1 * (q1 - q0) = q0 - p0 // If denom = Dot(p1 - p0, Perp(q1 - q0)) is not zero, then // t0 = Dot(q0 - p0, Perp(q1 - q0)) / denom = numer0 / denom // t1 = Dot(q0 - p0, Perp(p1 - p0)) / denom = numer1 / denom // For an invalid intersection, we need (t0,t1) with: // ((0 < t0 < 1) and (0 <= t1 <= 1)) or ((0 <= t0 <= 1) and // (0 < t1 < 1). std::array p1mp0, q1mq0, q0mp0; for (int j = 0; j < 2; ++j) { p1mp0[j] = p1[j] - p0[j]; q1mq0[j] = q1[j] - q0[j]; q0mp0[j] = q0[j] - p0[j]; } Real denom = p1mp0[0] * q1mq0[1] - p1mp0[1] * q1mq0[0]; Real numer0 = q0mp0[0] * q1mq0[1] - q0mp0[1] * q1mq0[0]; Real numer1 = q0mp0[0] * p1mp0[1] - q0mp0[1] * p1mp0[0]; if (denom != mZero) { // The lines of the segments are not parallel. if (denom > mZero) { if (mZero <= numer0 && numer0 <= denom && mZero <= numer1 && numer1 <= denom) { // The segments intersect. return (numer0 != mZero && numer0 != denom) || (numer1 != mZero && numer1 != denom); } else { return false; } } else // denom < mZero { if (mZero >= numer0 && numer0 >= denom && mZero >= numer1 && numer1 >= denom) { // The segments intersect. return (numer0 != mZero && numer0 != denom) || (numer1 != mZero && numer1 != denom); } else { return false; } } } else { // The lines of the segments are parallel. if (numer0 != mZero || numer1 != mZero) { // The lines of the segments are separated. return false; } else { // The segments lie on the same line. Compute the // parameter intervals for the segments in terms of the // t0-parameter and determine their overlap (if any). std::array q1mp0; for (int j = 0; j < 2; ++j) { q1mp0[j] = q1[j] - p0[j]; } Real sqrLenP1mP0 = p1mp0[0] * p1mp0[0] + p1mp0[1] * p1mp0[1]; Real value0 = q0mp0[0] * p1mp0[0] + q0mp0[1] * p1mp0[1]; Real value1 = q1mp0[0] * p1mp0[0] + q1mp0[1] * p1mp0[1]; if ((value0 >= sqrLenP1mP0 && value1 >= sqrLenP1mP0) || (value0 <= mZero && value1 <= mZero)) { // If the segments intersect, they must do so at // endpoints of the segments. return false; } else { // The segments overlap in a positive-length interval. return true; } } } } std::vector> mDuplicatedPositions; std::vector> mDuplicatedEdges; std::vector mDegenerateEdges; std::vector mEdgesWithInvalidVertices; std::vector mInvalidIntersections; Real mZero, mOne; }; }