123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- // 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 <algorithm>
- #include <array>
- #include <map>
- #include <set>
- #include <vector>
- // 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 <typename Real>
- 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<std::array<Real, 2>> const& positions,
- std::vector<std::array<int, 2>> 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<OrderedEdge> 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<int, 2> e0 = edges[key.V[0]];
- std::array<int, 2> e1 = edges[key.V[1]];
- std::array<Real, 2> const& p0 = positions[e0[0]];
- std::array<Real, 2> const& p1 = positions[e0[1]];
- std::array<Real, 2> const& q0 = positions[e1[0]];
- std::array<Real, 2> 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<int,2>.
- return V < edge.V;
- }
- std::array<int, 2> V;
- };
- inline std::vector<std::vector<int>> const& GetDuplicatedPositions() const
- {
- return mDuplicatedPositions;
- }
- inline std::vector<std::vector<int>> const& GetDuplicatedEdges() const
- {
- return mDuplicatedEdges;
- }
- inline std::vector<int> const& GetDegenerateEdges() const
- {
- return mDegenerateEdges;
- }
- inline std::vector<int> const& GetEdgesWithInvalidVertices() const
- {
- return mEdgesWithInvalidVertices;
- }
- inline std::vector<typename IsPlanarGraph<Real>::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<std::array<Real, 2>> const& positions,
- std::vector<std::array<int, 2>> const& edges)
- {
- int const numPositions = static_cast<int>(positions.size());
- int const numEdges = static_cast<int>(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::array<Real, 2>, std::vector<int>> uniquePositions;
- for (int i = 0; i < numPositions; ++i)
- {
- std::array<Real, 2> p = positions[i];
- auto iter = uniquePositions.find(p);
- if (iter == uniquePositions.end())
- {
- std::vector<int> 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<OrderedEdge, std::vector<int>> 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<int> 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<int, 2> 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<std::array<Real, 2>> const& positions,
- std::vector<std::array<int, 2>> const& edges,
- std::set<OrderedEdge>& overlappingRectangles) const
- {
- // Compute axis-aligned bounding rectangles for the edges.
- int const numEdges = static_cast<int>(edges.size());
- std::vector<std::array<Real, 2>> emin(numEdges);
- std::vector<std::array<Real, 2>> emax(numEdges);
- for (int i = 0; i < numEdges; ++i)
- {
- std::array<int, 2> e = edges[i];
- std::array<Real, 2> const& p0 = positions[e[0]];
- std::array<Real, 2> 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<Endpoint> xEndpoints(numEndpoints);
- std::vector<Endpoint> 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<int> 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<Real, 2> const& r0min = emin[activeIndex];
- std::array<Real, 2> const& r0max = emax[activeIndex];
- std::array<Real, 2> const& r1min = emin[index];
- std::array<Real, 2> 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<Real, 2> const& p0, std::array<Real, 2> const& p1,
- std::array<Real, 2> const& q0, std::array<Real, 2> 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<Real, 2> 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<Real, 2> 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<std::vector<int>> mDuplicatedPositions;
- std::vector<std::vector<int>> mDuplicatedEdges;
- std::vector<int> mDegenerateEdges;
- std::vector<int> mEdgesWithInvalidVertices;
- std::vector<OrderedEdge> mInvalidIntersections;
- Real mZero, mOne;
- };
- }
|