34#include <gtest/gtest.h>
47#define PV(d) (parse_svg_path(d))
48#define PTH(d) (std::move(PV(d)[0]))
49#define REV(d) ((PV(d)[0]).reversed())
54 unsigned reversal_count = 0, merge_count = 0, detachment_count = 0;
55 void onReverse() { reversal_count++; }
56 void onMergeWith(TestLabel
const &) { merge_count++; }
57 void onDetach() { detachment_count++; }
65 std::vector<TestLabel>
result;
74class PlanarGraphTest :
public ::testing::Test
79TEST(PlanarGraphTest, EdgeInsertion)
81 double const precision = 1e-3;
83 graph.insertEdge(PTH(
"M 0, 0 L 1, 0"));
84 graph.insertEdge(PTH(
"M 0, 1 L 1, 1"));
85 graph.insertEdge(PTH(
"M 1, 0 L 1, 1.0009"));
87 auto vertices = graph.getVertices();
90 EXPECT_EQ(vertices.size(), 4);
91 EXPECT_EQ(graph.numEdges(), 3);
94 EXPECT_EQ(vertices.front().point(),
Point(0, 0));
95 auto after = std::next(vertices.begin());
96 EXPECT_EQ(after->point(),
Point(0, 1));
98 EXPECT_EQ(after->point(),
Point(1, 0));
99 EXPECT_TRUE(
are_near(vertices.back().point(),
Point(1, 1), precision));
101 EXPECT_FALSE(graph.isRegularized());
105TEST(PlanarGraphTest, InsertDetached)
108 auto detached = graph.
insertDetached(PTH(
"M 0,0 A 1,1 0,0,1 2,0 V -2 H 0 Z"));
111 EXPECT_EQ(
edges.size(), 1);
112 EXPECT_TRUE(
edges.at(detached).detached);
113 EXPECT_TRUE(
edges.at(detached).inserted_as_detached);
116 EXPECT_EQ(graph.
numEdges(
false), 0);
121TEST(PlanarGraphTest, ClosedPathArea)
125 auto square_positive = PTH(
"M 0,0 H 1 V 1 H 0 Z");
129 auto triangle_negative = PTH(
"M 0,0 V 1 L 1,1 Z");
134TEST(PlanarGraphTest, Deviation)
136 auto vertical_up = PTH(
"M 0,0 V 1");
137 auto arc_right1 = PTH(
"M 0,0 A 1,1 0,1,0 2,0");
138 auto arc_left1 = PTH(
"M 0,0 A 1,1 0,1,1 -2,0");
139 auto arc_right2 = PTH(
"M 0,0 A 2,2 0,1,0, 4,0");
140 auto arc_left2 = PTH(
"M 0,0 A 2,2 0,1,1 -4,0");
142 auto bezier_right = PTH(
"M 0,0 C 0,50 1,20 2,10");
162TEST(PlanarGraphTest, BasicAzimuthalSort)
167 bool const clockwise =
true;
168 unsigned const num_rays = 9;
169 unsigned edges[num_rays];
187 ASSERT_TRUE(incidence);
190 EXPECT_DOUBLE_EQ(std::abs(incidence->azimuth), M_PI);
193 for (
unsigned i = 0; i < num_rays; i++) {
194 EXPECT_EQ(incidence->index,
edges[i]);
200TEST(PlanarGraphTest, PathRetrieval)
204 Path const path = PTH(
"M 0,0 L 1,1 C 2,2 4,2 5,1");
211 auto [start_point, start_incidence] = graph.
getIncidence(edge, TestGraph::Incidence::START);
212 ASSERT_TRUE(start_point);
213 ASSERT_TRUE(start_incidence);
217 auto [end_point, end_incidence] = graph.
getIncidence(edge, TestGraph::Incidence::END);
218 ASSERT_TRUE(end_point);
219 ASSERT_TRUE(end_incidence);
225TEST(PlanarGraphTest, LabelRetrieval)
230 label.reversal_count = 420;
231 label.merge_count = 69;
232 label.detachment_count = 111;
237 EXPECT_EQ(retrieved.reversal_count, 420);
238 EXPECT_EQ(retrieved.merge_count, 69);
239 EXPECT_EQ(retrieved.detachment_count, 111);
243TEST(PlanarGraphTest, MergeDuplicate)
245 char const *
const d =
"M 2, 3 H 0 C 1,4 1,5 0,6 H 10 L 8, 0";
246 char const *
const near_d =
"M 2.0009,3 H 0 C 1,4 1,5 0,6 H 10.0009 L 8, 0.0005";
259 ASSERT_EQ(remaining.size(), 1);
261 EXPECT_EQ(remaining[0].merge_count, 1);
262 EXPECT_EQ(remaining[0].reversal_count, 0);
263 EXPECT_EQ(remaining[0].detachment_count, 0);
268 fuzzy.insertEdge(PTH(near_d));
271 EXPECT_TRUE(fuzzy.isRegularized());
274 ASSERT_EQ(fuzmaining.size(), 1);
276 EXPECT_EQ(fuzmaining[0].merge_count, 1);
277 EXPECT_EQ(fuzmaining[0].reversal_count, 0);
278 EXPECT_EQ(fuzmaining[0].detachment_count, 0);
289 ASSERT_EQ(left.size(), 1);
291 EXPECT_EQ(left[0].merge_count, 1);
292 EXPECT_TRUE(left[0].reversal_count == 0 || left[0].reversal_count == 1);
293 EXPECT_EQ(left[0].detachment_count, 0);
297TEST(PlanarGraphTest, MergePartial)
300 auto longer = graph.
insertEdge(PTH(
"M 0, 0 L 10, 10"));
301 auto shorter = graph.
insertEdge(PTH(
"M 0, 0 L 6, 6"));
311 ASSERT_EQ(labels.size(), 2);
313 EXPECT_EQ(labels[longer].merge_count, 0);
314 EXPECT_EQ(labels[longer].reversal_count, 0);
315 EXPECT_EQ(labels[longer].detachment_count, 0);
317 EXPECT_EQ(labels[shorter].merge_count, 1);
318 EXPECT_EQ(labels[shorter].reversal_count, 0);
319 EXPECT_EQ(labels[shorter].detachment_count, 0);
323 longer = graphopp.
insertEdge(PTH(
"M 0, 0 L 10, 0"));
324 shorter = graphopp.
insertEdge(PTH(
"M 10, 0 L 5, 0"));
334 ASSERT_EQ(labels.size(), 2);
336 EXPECT_EQ(labels[longer].merge_count, 0);
337 EXPECT_EQ(labels[longer].reversal_count, 0);
338 EXPECT_EQ(labels[longer].detachment_count, 0);
340 EXPECT_EQ(labels[shorter].merge_count, 1);
341 EXPECT_EQ(labels[shorter].reversal_count, 0);
342 EXPECT_EQ(labels[shorter].detachment_count, 0);
349 auto left = graph.
insertEdge(PTH(
"M 1 0 V 1 L 0, 2"));
350 auto right = graph.
insertEdge(PTH(
"M 1,0 V 1 L 2, 2"));
357 EXPECT_EQ(
edges.size(), 3);
366 auto loop = graph.
insertEdge(PTH(
"M 1,0 A 1,1, 0,0,1 0,1 L 2,2 V 1 H 1 V 0"));
368 auto before = graph.
insertEdge(PTH(
"M 1,0 H 10"));
369 auto after = graph.
insertEdge(PTH(
"M 1,0 H -10"));
376 auto [start_vertex, start_incidence] = graph.
getIncidence(loop, TestGraph::Incidence::START);
377 auto [end_vertex, end_incidence] = graph.
getIncidence(loop, TestGraph::Incidence::END);
379 EXPECT_EQ(start_vertex, end_vertex);
380 ASSERT_NE(start_vertex,
nullptr);
383 EXPECT_EQ(start_vertex->cyclicNextIncidence(end_incidence), start_incidence);
384 EXPECT_EQ(start_vertex->cyclicPrevIncidence(start_incidence), end_incidence);
385 auto [b, before_incidence] = graph.
getIncidence(before, TestGraph::Incidence::START);
386 EXPECT_EQ(start_vertex->cyclicNextIncidence(before_incidence), end_incidence);
387 auto [a, after_incidence] = graph.
getIncidence(after, TestGraph::Incidence::START);
388 EXPECT_EQ(start_vertex->cyclicPrevIncidence(after_incidence), start_incidence);
392TEST(PlanarGraphTest, ReglueLasso)
396 auto original_lasso = graph.
insertEdge(PTH(
"M 0,0 V 1 C 0,2 1,3 1,4 "
397 "A 1,1 0,1,1 -1,4 C -1,3 0,2 0,1 V 0"));
402 EXPECT_EQ(graph.
numEdges(
false), 2);
407 auto from_origin = std::find_if(
edges.begin(),
edges.end(), [](
auto const &edge) ->
bool {
408 return !edge.detached && (edge.start->point() == Point(0, 0) ||
409 edge.end->point() == Point(0, 0));
411 ASSERT_NE(from_origin,
edges.end());
412 ASSERT_EQ(from_origin->label.merge_count, 1);
416TEST(PlanarGraphTest, RemoveCollapsed)
420 auto collapsed = graph.
insertEdge(PTH(
"M 0,0 L 1,1 L 0,0"));
423 ASSERT_EQ(graph.
numEdges(
false), 0);
428 auto nearly = fuzzy.
insertEdge(PTH(
"M 0,0 H 2 V 0.001 L 1,0 H 0"));
431 ASSERT_EQ(fuzzy.
numEdges(
false), 0);
436TEST(PlanarGraphTest, RemoveWisp)
442 graph.
insertEdge(PTH(
"M 0 0 C -1 0 -1 0 0 0"));
446 EXPECT_EQ(graph.
numEdges(
false), 1);
Cartesian point / 2D vector and related operations.
Sequence of contiguous curves, aka spline.
Path reversed() const
Obtain a reversed version of the current path.
Planar graph - a graph geometrically embedded in the plane.
void regularize(double angle_precision=0x1p-50, bool remove_collapsed_loops=true)
Merge overlapping edges or their portions, adding vertices if necessary.
Path getIncomingPath(Incidence const *incidence) const
Get the incident path, always oriented towards the vertex.
unsigned insertDetached(Path &&path, EdgeLabel &&edge=EdgeLabel())
Move-insert a new labeled edge but do not connect it to the graph.
Edge const & getEdge(size_t index) const
size_t numVertices() const
bool isRegularized() const
Check if the graph has been regularized.
static bool deviatesLeft(Path const &first, Path const &second)
Determine whether the first path deviates to the left of the second.
Path getOutgoingPath(Incidence const *incidence) const
Get the incident path, always oriented away from the vertex.
static double closedPathArea(Path const &path)
Return the signed area enclosed by a closed path.
Incidence const & nextIncidence(VertexIterator const &vertex, IncConstIt const &incidence, bool clockwise=false) const
Go clockwise or counterclockwise around the vertex and find the next incidence.
std::pair< Vertex *, Incidence * > getIncidence(unsigned edge_index, Incidence::Sign sign) const
Find the incidence at the specified endpoint of the edge.
std::vector< Edge > const & getEdges() const
size_t numEdges(bool include_detached=true) const
unsigned insertEdge(Path &&path, EdgeLabel &&edge=EdgeLabel())
Move-insert a new labeled edge into the planar graph.
Two-dimensional point that doubles as a vector.
Various utility functions.
TEST(AffineTest, Equality)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
PathVector - a sequence of subpaths.
static std::vector< TestLabel > extract_labels(TestGraph const &graph)
PlanarGraph< TestLabel > TestGraph
Edges edges(Path const &p, Crossings const &cr, unsigned ix)
bool detached
Whether the edge is detached from the graph.
EdgeLabel label
The user-supplied label of the edge.
Represents the joint between an edge and a vertex.
parse SVG path specifications
Path sink which writes an SVG-compatible command string.