summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt56
-rw-r--r--tests/WontSnapToSomeCurveSegments.svg129
-rw-r--r--tests/affine-test.cpp429
-rw-r--r--tests/angle-test.cpp209
-rw-r--r--tests/bezier-sbasis-transforms.py72
-rw-r--r--tests/bezier-test.cpp680
-rw-r--r--tests/bezier-utils-test.cpp333
-rw-r--r--tests/choose-test.cpp79
-rw-r--r--tests/circle-test.cpp141
-rw-r--r--tests/convex-hull-test.cpp335
-rw-r--r--tests/coord-test.cpp90
-rw-r--r--tests/dependent-project/.gitignore2
-rw-r--r--tests/dependent-project/CMakeLists.txt27
-rw-r--r--tests/dependent-project/main.cpp12
-rw-r--r--tests/dependent-project/my_lib.cpp6
-rw-r--r--tests/dependent-project/my_lib.h4
-rw-r--r--tests/ellipse-test.cpp410
-rw-r--r--tests/elliptical-arc-test.cpp275
-rw-r--r--tests/implicitization-test.cpp130
-rw-r--r--tests/intersection-graph-test.cpp266
-rw-r--r--tests/interval-test.cpp54
-rw-r--r--tests/linalg-test.cpp502
-rw-r--r--tests/line-test.cpp185
-rw-r--r--tests/mersennetwister.h427
-rw-r--r--tests/nl-vector-test.cpp333
-rw-r--r--tests/parallelogram-test.cpp161
-rw-r--r--tests/parser-test.py94
-rw-r--r--tests/path-test.cpp991
-rw-r--r--tests/pick.h172
-rw-r--r--tests/planar-graph-test.cpp457
-rw-r--r--tests/point-test.cpp119
-rw-r--r--tests/polybez-cases.svg168
-rw-r--r--tests/polynomial-test.cpp126
-rw-r--r--tests/rect-test.cpp368
-rw-r--r--tests/root-find-test.cpp156
-rw-r--r--tests/rtree-performance-test.cpp361
-rw-r--r--tests/rtree-test.cpp158
-rw-r--r--tests/sbasis-test.cpp268
-rw-r--r--tests/sbasis-text-test.cpp225
-rw-r--r--tests/self-intersections-test.cpp219
-rw-r--r--tests/test_pwsb.py67
-rw-r--r--tests/test_py2geom.py75
-rw-r--r--tests/testing.h186
-rw-r--r--tests/timing-test.cpp270
-rw-r--r--tests/utest.h134
45 files changed, 9961 insertions, 0 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..95e10d4
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,56 @@
+
+find_package(GTest REQUIRED MODULE)
+# Use this variable for tests which provide their own main().
+SET(2GEOM_TESTS_SRC
+#bezier-utils-test
+#lin_alg_test
+sbasis-text-test
+root-find-test
+implicitization-test
+#timing-test
+#rtree-performance-test
+)
+
+# Use this variable for GTest tests which should have a default main().
+SET(2GEOM_GTESTS_SRC
+affine-test
+angle-test
+bezier-test
+choose-test
+circle-test
+convex-hull-test
+coord-test
+ellipse-test
+elliptical-arc-test
+intersection-graph-test
+interval-test
+line-test
+nl-vector-test
+parallelogram-test
+path-test
+planar-graph-test
+point-test
+polynomial-test
+rect-test
+sbasis-test
+self-intersections-test
+)
+
+foreach(source ${2GEOM_GTESTS_SRC})
+ add_executable(${source} ${source}.cpp)
+ target_include_directories(${source} PRIVATE ${GSL_INCLUDE_DIRS} ${GTK3_INCLUDE_DIRS})
+ target_link_libraries(${source} 2geom GTest::Main ${GSL_LIBRARIES} ${GTK3_LIBRARIES})
+ add_test(NAME ${source} COMMAND ${source})
+endforeach()
+
+foreach(source ${2GEOM_TESTS_SRC})
+ add_executable(${source} ${source}.cpp)
+ target_include_directories(${source} PRIVATE ${GSL_INCLUDE_DIRS} ${GTK3_INCLUDE_DIRS})
+ target_link_libraries(${source} 2geom GTest::GTest ${GSL_LIBRARIES} ${GTK3_LIBRARIES})
+ add_test(NAME ${source} COMMAND ${source})
+endforeach(source)
+
+if(WIN32 AND 2GEOM_BUILD_SHARED)
+ add_custom_target(copy ALL COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/src/2geom/lib2geom.dll ${CMAKE_BINARY_DIR}/src/tests/lib2geom.dll)
+ add_dependencies(copy 2geom)
+endif()
diff --git a/tests/WontSnapToSomeCurveSegments.svg b/tests/WontSnapToSomeCurveSegments.svg
new file mode 100644
index 0000000..b7e8dfb
--- /dev/null
+++ b/tests/WontSnapToSomeCurveSegments.svg
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="800"
+ height="400"
+ id="svg3975"
+ version="1.1"
+ inkscape:version="0.48+devel r10639 custom"
+ sodipodi:docname="825840-geom-bbox-nan-stroke-width-2.svg">
+ <defs
+ id="defs3977">
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lend"
+ style="overflow:visible">
+ <path
+ id="path3915"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Mend"
+ style="overflow:visible">
+ <path
+ id="path3921"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(-0.6,-0.6)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.76013979"
+ inkscape:cx="406.66823"
+ inkscape:cy="300.66798"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1047"
+ inkscape:window-height="815"
+ inkscape:window-x="736"
+ inkscape:window-y="62"
+ inkscape:window-maximized="0"
+ borderlayer="false"
+ inkscape:showpageshadow="false"
+ inkscape:snap-center="false"
+ inkscape:snap-text-baseline="false"
+ inkscape:object-nodes="false"
+ inkscape:snap-midpoints="false"
+ inkscape:object-paths="true"
+ inkscape:snap-global="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-intersection-paths="false"
+ inkscape:snap-others="false"
+ inkscape:snap-bbox="false"
+ inkscape:bbox-paths="false"
+ inkscape:bbox-nodes="false"
+ inkscape:snap-bbox-edge-midpoints="false"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:snap-smooth-nodes="false"
+ inkscape:snap-object-midpoints="false"
+ inkscape:snap-page="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4495"
+ empspacing="2"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true"
+ dotted="false"
+ color="#009862"
+ opacity="0.40392157"
+ empcolor="#ff0000"
+ empopacity="0.1254902"
+ spacingx="50px"
+ spacingy="50px" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3980">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 262.6037,35.824151 c 0,0 -92.64892,-187.405851 30,-149.999981 104.06976,31.739531 170,109.9999815 170,109.9999815 l -10,-59.9999905 c 0,0 40,79.99999 -40,79.99999 -80,0 -70,-129.999981 -70,-129.999981 l 50,0 C 435.13571,-131.5667 652.76275,126.44872 505.74322,108.05672 358.73876,89.666591 292.6037,-14.175849 292.6037,15.824151 c 0,30 -30,20 -30,20 z"
+ id="path3132"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csccsccssc" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="illustrate" />
+</svg>
diff --git a/tests/affine-test.cpp b/tests/affine-test.cpp
new file mode 100644
index 0000000..2ddeb1d
--- /dev/null
+++ b/tests/affine-test.cpp
@@ -0,0 +1,429 @@
+/** @file
+ * @brief Unit tests for Affine
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2010 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <gtest/gtest.h>
+#include <2geom/affine.h>
+#include <2geom/transforms.h>
+
+namespace Geom {
+
+TEST(AffineTest, Equality) {
+ Affine e; // identity
+ Affine a(1, 2, 3, 4, 5, 6);
+ EXPECT_EQ(e, e);
+ EXPECT_EQ(e, Geom::identity());
+ EXPECT_EQ(e, Geom::Affine::identity());
+ EXPECT_NE(e, a);
+}
+
+TEST(AffineTest, Classification) {
+ {
+ Affine a; // identity
+ EXPECT_TRUE(a.isIdentity());
+ EXPECT_TRUE(a.isTranslation());
+ EXPECT_TRUE(a.isScale());
+ EXPECT_TRUE(a.isUniformScale());
+ EXPECT_TRUE(a.isRotation());
+ EXPECT_TRUE(a.isHShear());
+ EXPECT_TRUE(a.isVShear());
+ EXPECT_TRUE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_FALSE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_TRUE(a.preservesArea());
+ EXPECT_TRUE(a.preservesAngles());
+ EXPECT_TRUE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = Translate(10, 15); // pure translation
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_TRUE(a.isTranslation());
+ EXPECT_FALSE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_TRUE(a.isZoom());
+ EXPECT_TRUE(a.isNonzeroTranslation());
+ EXPECT_FALSE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_TRUE(a.preservesArea());
+ EXPECT_TRUE(a.preservesAngles());
+ EXPECT_TRUE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = Scale(-1.0, 1.0); // flip on the X axis
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_TRUE(a.isScale());
+ EXPECT_TRUE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_FALSE(a.isZoom()); // zoom must be non-flipping
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_TRUE(a.isNonzeroScale());
+ EXPECT_TRUE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_TRUE(a.preservesArea());
+ EXPECT_TRUE(a.preservesAngles());
+ EXPECT_TRUE(a.preservesDistances());
+ EXPECT_TRUE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = Scale(0.5, 0.5); // pure uniform scale
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_TRUE(a.isScale());
+ EXPECT_TRUE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_TRUE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_TRUE(a.isNonzeroScale());
+ EXPECT_TRUE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_FALSE(a.preservesArea());
+ EXPECT_TRUE(a.preservesAngles());
+ EXPECT_FALSE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = Scale(0.5, -0.5); // pure uniform flipping scale
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_TRUE(a.isScale());
+ EXPECT_TRUE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_FALSE(a.isZoom()); // zoom must be non-flipping
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_TRUE(a.isNonzeroScale());
+ EXPECT_TRUE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_FALSE(a.preservesArea());
+ EXPECT_TRUE(a.preservesAngles());
+ EXPECT_FALSE(a.preservesDistances());
+ EXPECT_TRUE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = Scale(0.5, 0.7); // pure non-uniform scale
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_TRUE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_FALSE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_TRUE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_FALSE(a.preservesArea());
+ EXPECT_FALSE(a.preservesAngles());
+ EXPECT_FALSE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = Scale(0.5, 2.0); // "squeeze" transform (non-uniform scale with det=1)
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_TRUE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_FALSE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_TRUE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_TRUE(a.preservesArea());
+ EXPECT_FALSE(a.preservesAngles());
+ EXPECT_FALSE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = Rotate(0.7); // pure rotation
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_FALSE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_TRUE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_FALSE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_FALSE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_TRUE(a.isNonzeroRotation());
+ EXPECT_TRUE(a.isNonzeroNonpureRotation());
+ EXPECT_EQ(a.rotationCenter(), Point(0.0,0.0));
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_TRUE(a.preservesArea());
+ EXPECT_TRUE(a.preservesAngles());
+ EXPECT_TRUE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Point rotation_center(1.23,4.56);
+ Affine a = Translate(-rotation_center) * Rotate(0.7) * Translate(rotation_center); // rotation around (1.23,4.56)
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_FALSE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_FALSE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_FALSE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_TRUE(a.isNonzeroNonpureRotation());
+ EXPECT_TRUE(are_near(a.rotationCenter(), rotation_center, 1e-7));
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_TRUE(a.preservesArea());
+ EXPECT_TRUE(a.preservesAngles());
+ EXPECT_TRUE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = HShear(0.5); // pure horizontal shear
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_FALSE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_TRUE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_FALSE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_FALSE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_TRUE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_TRUE(a.preservesArea());
+ EXPECT_FALSE(a.preservesAngles());
+ EXPECT_FALSE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = VShear(0.5); // pure vertical shear
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_FALSE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_TRUE(a.isVShear());
+ EXPECT_FALSE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_FALSE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_TRUE(a.isNonzeroVShear());
+ EXPECT_TRUE(a.preservesArea());
+ EXPECT_FALSE(a.preservesAngles());
+ EXPECT_FALSE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+ }
+ {
+ Affine a = Zoom(3.0, Translate(10, 15)); // zoom
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_FALSE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_TRUE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_FALSE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_FALSE(a.preservesArea());
+ EXPECT_TRUE(a.preservesAngles());
+ EXPECT_FALSE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_FALSE(a.isSingular());
+
+ EXPECT_TRUE(a.withoutTranslation().isUniformScale());
+ EXPECT_TRUE(a.withoutTranslation().isNonzeroUniformScale());
+ }
+ {
+ Affine a(0, 0, 0, 0, 0, 0); // zero matrix (singular)
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_FALSE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_FALSE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_FALSE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_FALSE(a.preservesArea());
+ EXPECT_FALSE(a.preservesAngles());
+ EXPECT_FALSE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_TRUE(a.isSingular());
+ }
+ {
+ Affine a(0, 1, 0, 1, 10, 10); // another singular matrix
+ EXPECT_FALSE(a.isIdentity());
+ EXPECT_FALSE(a.isTranslation());
+ EXPECT_FALSE(a.isScale());
+ EXPECT_FALSE(a.isUniformScale());
+ EXPECT_FALSE(a.isRotation());
+ EXPECT_FALSE(a.isHShear());
+ EXPECT_FALSE(a.isVShear());
+ EXPECT_FALSE(a.isZoom());
+ EXPECT_FALSE(a.isNonzeroTranslation());
+ EXPECT_FALSE(a.isNonzeroScale());
+ EXPECT_FALSE(a.isNonzeroUniformScale());
+ EXPECT_FALSE(a.isNonzeroRotation());
+ EXPECT_FALSE(a.isNonzeroNonpureRotation());
+ EXPECT_FALSE(a.isNonzeroHShear());
+ EXPECT_FALSE(a.isNonzeroVShear());
+ EXPECT_FALSE(a.preservesArea());
+ EXPECT_FALSE(a.preservesAngles());
+ EXPECT_FALSE(a.preservesDistances());
+ EXPECT_FALSE(a.flips());
+ EXPECT_TRUE(a.isSingular());
+ }
+}
+
+TEST(AffineTest, Inversion) {
+ Affine i(1, 2, 1, -2, 10, 15); // invertible
+ Affine n(1, 2, 1, 2, 15, 30); // non-invertible
+ Affine e; // identity
+ EXPECT_EQ(i * i.inverse(), e);
+ EXPECT_EQ(i.inverse().inverse(), i);
+ EXPECT_EQ(n.inverse(), e);
+ EXPECT_EQ(e.inverse(), e);
+}
+
+TEST(AffineTest, CoordinateAccess) {
+ Affine a(0, 1, 2, 3, 4, 5);
+ for (int i=0; i<6; ++i) {
+ EXPECT_EQ(a[i], i);
+ }
+ for (int i=0; i<6; ++i) {
+ a[i] = 5*i;
+ }
+ for (int i=0; i<6; ++i) {
+ EXPECT_EQ(a[i], 5*i);
+ }
+}
+
+TEST(AffineTest, Nearness) {
+ Affine a1(1, 0, 1, 2, 1e-8, 1e-8);
+ Affine a2(1+1e-8, 0, 1, 2-1e-8, -1e-8, -1e-8);
+ EXPECT_TRUE(are_near(a1, a2, 1e-7));
+ EXPECT_FALSE(are_near(a1, a2, 1e-9));
+}
+
+TEST(AffineTest, Multiplication) {
+ // test whether noncommutative multiplications work correctly
+ Affine a1 = Scale(0.1), a2 = Translate(10, 10), a3 = Scale(10.0);
+ Affine t1 = Translate(1, 1), t100 = Translate(100, 100);
+ EXPECT_EQ(a1 * a2 * a3, t100);
+ EXPECT_EQ(a3 * a2 * a1, t1);
+}
+
+} // end namespace Geom
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/angle-test.cpp b/tests/angle-test.cpp
new file mode 100644
index 0000000..687be65
--- /dev/null
+++ b/tests/angle-test.cpp
@@ -0,0 +1,209 @@
+/** @file
+ * @brief Unit tests for Angle and AngleInterval.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <2geom/angle.h>
+#include <glib.h>
+#include "testing.h"
+
+using namespace Geom;
+
+TEST(AngleIntervalTest, InnerAngleConstrutor) {
+ std::vector<AngleInterval> ivs;
+
+ ivs.emplace_back(0, M_PI, true);
+ ivs.emplace_back(0, M_PI, false);
+ ivs.emplace_back(M_PI, 0, true);
+ ivs.emplace_back(M_PI, 0, false);
+ ivs.emplace_back(Angle(0), Angle(0), Angle(M_PI));
+
+ for (auto & iv : ivs) {
+ AngleInterval inner(iv.angleAt(0), iv.angleAt(0.5), iv.angleAt(1));
+ EXPECT_EQ(inner, iv);
+ }
+}
+
+TEST(AngleIntervalTest, Containment) {
+ AngleInterval a(0, M_PI, true);
+ AngleInterval b(0, M_PI, false);
+ AngleInterval c(M_PI, 0, true);
+ AngleInterval d(M_PI, 0, false);
+ AngleInterval e = AngleInterval::create_full(M_PI, true);
+
+ EXPECT_TRUE(a.contains(1.));
+ EXPECT_FALSE(a.contains(5.));
+ EXPECT_EQ(a.extent(), M_PI);
+
+ EXPECT_FALSE(b.contains(1.));
+ EXPECT_TRUE(b.contains(5.));
+ EXPECT_EQ(b.extent(), M_PI);
+
+ EXPECT_FALSE(c.contains(1.));
+ EXPECT_TRUE(c.contains(5.));
+ EXPECT_EQ(c.extent(), M_PI);
+
+ EXPECT_TRUE(d.contains(1.));
+ EXPECT_FALSE(d.contains(5.));
+ EXPECT_EQ(d.extent(), M_PI);
+
+ EXPECT_TRUE(e.contains(1.));
+ EXPECT_TRUE(e.contains(5.));
+ EXPECT_EQ(e.extent(), 2*M_PI);
+}
+
+TEST(AngleIntervalTest, TimeAtAngle) {
+ Coord pi32 = (3./2.)*M_PI;
+ AngleInterval a(M_PI, pi32, true);
+ AngleInterval b(pi32, M_PI, true);
+ AngleInterval c(M_PI, 0, false);
+ AngleInterval d(M_PI/2, M_PI, false);
+ AngleInterval e = AngleInterval::create_full(M_PI, true);
+ AngleInterval f = AngleInterval::create_full(M_PI, false);
+ Interval unit(0, 1);
+
+ EXPECT_EQ(a.timeAtAngle(M_PI), 0);
+ EXPECT_EQ(a.timeAtAngle(pi32), 1);
+ EXPECT_EQ(a.extent(), M_PI/2);
+ for (Coord t = -1; t <= 2; t += 0.125) {
+ Coord angle = lerp(t, M_PI, pi32);
+ Coord ti = a.timeAtAngle(angle);
+ EXPECT_EQ(unit.contains(ti), a.contains(angle));
+ EXPECT_FLOAT_EQ(ti, t);
+ }
+
+ EXPECT_EQ(b.timeAtAngle(pi32), 0);
+ EXPECT_EQ(b.timeAtAngle(M_PI), 1);
+ EXPECT_EQ(b.extent(), pi32);
+ EXPECT_FLOAT_EQ(b.timeAtAngle(M_PI/4), 0.5);
+ EXPECT_FLOAT_EQ(b.timeAtAngle(0), 1./3.);
+ EXPECT_FLOAT_EQ(b.timeAtAngle((11./8)*M_PI), -1./12);
+ for (Coord t = -0.125; t <= 1.125; t += 0.0625) {
+ Coord angle = lerp(t, pi32, 3*M_PI);
+ Coord ti = b.timeAtAngle(angle);
+ EXPECT_EQ(unit.contains(ti), b.contains(angle));
+ EXPECT_FLOAT_EQ(ti, t);
+ }
+
+ EXPECT_EQ(c.timeAtAngle(M_PI), 0);
+ EXPECT_EQ(c.timeAtAngle(0), 1);
+ EXPECT_EQ(c.extent(), M_PI);
+ EXPECT_FLOAT_EQ(c.timeAtAngle(M_PI/2), 0.5);
+ for (Coord t = -0.25; t <= 1.25; t += 0.125) {
+ Coord angle = lerp(t, M_PI, 0);
+ Coord ti = c.timeAtAngle(angle);
+ EXPECT_EQ(unit.contains(ti), c.contains(angle));
+ EXPECT_FLOAT_EQ(ti, t);
+ }
+
+ EXPECT_EQ(d.timeAtAngle(M_PI/2), 0);
+ EXPECT_EQ(d.timeAtAngle(M_PI), 1);
+ EXPECT_EQ(d.extent(), pi32);
+ EXPECT_FLOAT_EQ(d.timeAtAngle(-M_PI/4), 0.5);
+ for (Coord t = -0.125; t <= 1.125; t += 0.0625) {
+ Coord angle = lerp(t, M_PI/2, -M_PI);
+ Coord ti = d.timeAtAngle(angle);
+ EXPECT_EQ(unit.contains(ti), d.contains(angle));
+ EXPECT_FLOAT_EQ(ti, t);
+ }
+
+ EXPECT_EQ(e.timeAtAngle(M_PI), 0);
+ EXPECT_EQ(e.extent(), 2*M_PI);
+ EXPECT_FLOAT_EQ(e.timeAtAngle(0), 0.5);
+ for (Coord t = 0; t < 1; t += 0.125) {
+ Coord angle = lerp(t, M_PI, 3*M_PI);
+ Coord ti = e.timeAtAngle(angle);
+ EXPECT_EQ(unit.contains(ti), true);
+ EXPECT_EQ(e.contains(angle), true);
+ EXPECT_FLOAT_EQ(ti, t);
+ }
+
+ EXPECT_EQ(f.timeAtAngle(M_PI), 0);
+ EXPECT_EQ(f.extent(), 2*M_PI);
+ EXPECT_FLOAT_EQ(e.timeAtAngle(0), 0.5);
+ for (Coord t = 0; t < 1; t += 0.125) {
+ Coord angle = lerp(t, M_PI, -M_PI);
+ Coord ti = f.timeAtAngle(angle);
+ EXPECT_EQ(unit.contains(ti), true);
+ EXPECT_EQ(f.contains(angle), true);
+ EXPECT_FLOAT_EQ(ti, t);
+ }
+}
+
+TEST(AngleIntervalTest, AngleAt) {
+ Coord pi32 = (3./2.)*M_PI;
+ AngleInterval a(M_PI, pi32, true);
+ AngleInterval c(M_PI, 0, false);
+ AngleInterval f1 = AngleInterval::create_full(0, true);
+ AngleInterval f2 = AngleInterval::create_full(M_PI, false);
+
+ EXPECT_EQ(a.angleAt(0), M_PI);
+ EXPECT_EQ(a.angleAt(1), pi32);
+ EXPECT_EQ(a.extent(), M_PI/2);
+ for (Coord t = -1; t <= 2; t += 0.125) {
+ EXPECT_FLOAT_EQ(a.angleAt(t), Angle(lerp(t, M_PI, pi32)));
+ }
+
+ EXPECT_EQ(c.angleAt(0), M_PI);
+ EXPECT_EQ(c.angleAt(1), 0.);
+ EXPECT_EQ(c.extent(), M_PI);
+ for (Coord t = -0.25; t <= 1.25; t += 0.0625) {
+ EXPECT_FLOAT_EQ(c.angleAt(t), Angle(lerp(t, M_PI, 0)));
+ }
+
+ EXPECT_EQ(f1.angleAt(0), 0.);
+ EXPECT_EQ(f1.angleAt(1), 0.);
+ for (Coord t = 0; t < 1; t += 0.125) {
+ EXPECT_FLOAT_EQ(f1.angleAt(t), Angle(lerp(t, 0, 2*M_PI)));
+ }
+ EXPECT_EQ(f2.angleAt(0), M_PI);
+ EXPECT_EQ(f2.angleAt(1), M_PI);
+ for (Coord t = 0; t < 1; t += 0.125) {
+ EXPECT_FLOAT_EQ(f2.angleAt(t), Angle(lerp(t, M_PI, -M_PI)));
+ }
+}
+
+TEST(AngleIntervalTest, Extent) {
+ Coord pi32 = (3./2.)*M_PI;
+ AngleInterval a(M_PI, pi32, true);
+ AngleInterval b(pi32, M_PI, true);
+ AngleInterval c(M_PI, 0, false);
+ AngleInterval d(M_PI/2, M_PI, false);
+
+ EXPECT_EQ(a.extent(), M_PI/2);
+ EXPECT_EQ(a.sweepAngle(), M_PI/2);
+ EXPECT_EQ(b.extent(), pi32);
+ EXPECT_EQ(b.sweepAngle(), pi32);
+ EXPECT_EQ(c.extent(), M_PI);
+ EXPECT_EQ(c.sweepAngle(), -M_PI);
+ EXPECT_EQ(d.extent(), pi32);
+ EXPECT_EQ(d.sweepAngle(), -pi32);
+}
diff --git a/tests/bezier-sbasis-transforms.py b/tests/bezier-sbasis-transforms.py
new file mode 100644
index 0000000..1dc850f
--- /dev/null
+++ b/tests/bezier-sbasis-transforms.py
@@ -0,0 +1,72 @@
+#!/usr/bin/python
+
+from Numeric import *
+from LinearAlgebra import *
+
+pascals_triangle = []
+rows_done = 0
+
+def choose(n, k):
+ r = 1
+ for i in range(1,k+1):
+ r *= n-k+i
+ r /= i
+ return r
+
+# http://www.research.att.com/~njas/sequences/A109954
+def T(n, k):
+ return ((-1)**(n+k))*choose(n+k+2, 2*k+2)
+
+def inver(q):
+ result = zeros((q+2,q+2))
+ q2 = q/2+1
+ for i in range(q2):
+ for j in range(i+1):
+ val = T(i,j)
+ result[q/2-j][q/2-i] = val
+ result[q/2+j+2][q/2+i+2] = val
+ result[q/2+j+2][q/2-i-1] = -val
+ if q/2+i+3 < q+2:
+ result[q/2-j][q/2+i+3] = -val
+
+ for i in range(q+2):
+ result[q2][i] = [1,-1][(i-q2)%2]
+ return result
+
+def simple(q):
+ result = zeros((q+2,q+2))
+ for i in range(q/2+1):
+ for j in range(q+1):
+ result[j][i] = choose(q-2*i, j-i)
+ result[j+1][q-i+1] = choose(q-2*i, j-i)
+ result[q/2+1][q/2+1] = 1
+ return result
+
+print "The aim of the game is to work out the correct indexing to make the two matrices match :)"
+
+s = simple(4)
+si = floor(inverse(s)+0.5)
+print si.astype(Int)
+print inver(4)
+exit(0)
+print "<html><head><title></title></head><body>"
+
+def arrayhtml(a):
+ s = "<table>"
+ r,c = a.shape
+ for i in range(r):
+ s += "<tr>";
+ for j in range(c):
+ s += "<td>%g</td>" % a[i,j]
+ s += "</tr>"
+ s += "</table>"
+ return s
+
+for i in [21]:#range(1,13,2):
+ s = simple(i)
+ print "<h1>T<sup>-1</sup> = </h1>"
+ print arrayhtml(s)
+ print "<h1>T = </h1>"
+ print arrayhtml(floor(inverse(s)+0.5))
+
+print "</body></html>"
diff --git a/tests/bezier-test.cpp b/tests/bezier-test.cpp
new file mode 100644
index 0000000..0799393
--- /dev/null
+++ b/tests/bezier-test.cpp
@@ -0,0 +1,680 @@
+/** @file
+ * @brief Unit tests for Affine.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@njhurst.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ *
+ * Copyright 2010 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include "testing.h"
+#include <iostream>
+
+#include <2geom/bezier.h>
+#include <2geom/polynomial.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/bezier-curve.h>
+#include <vector>
+#include <iterator>
+#include <glib.h>
+
+using std::vector, std::min, std::max;
+using namespace Geom;
+
+Poly lin_poly(double a, double b) { // ax + b
+ Poly p;
+ p.push_back(b);
+ p.push_back(a);
+ return p;
+}
+
+bool are_equal(Bezier A, Bezier B) {
+ int maxSize = max(A.size(), B.size());
+ double t = 0., dt = 1./maxSize;
+
+ for(int i = 0; i <= maxSize; i++) {
+ EXPECT_FLOAT_EQ(A.valueAt(t), B.valueAt(t));// return false;
+ t += dt;
+ }
+ return true;
+}
+
+class BezierTest : public ::testing::Test {
+protected:
+
+ BezierTest()
+ : zero(fragments[0])
+ , unit(fragments[1])
+ , hump(fragments[2])
+ , wiggle(fragments[3])
+ {
+ zero = Bezier(0.0,0.0);
+ unit = Bezier(0.0,1.0);
+ hump = Bezier(0,1,0);
+ wiggle = Bezier(0,1,-2,3);
+ }
+
+ Bezier fragments[4];
+ Bezier &zero, &unit, &hump, &wiggle;
+};
+
+TEST_F(BezierTest, Basics) {
+
+ //std::cout << unit <<std::endl;
+ //std::cout << hump <<std::endl;
+
+ EXPECT_TRUE(Bezier(0,0,0,0).isZero());
+ EXPECT_TRUE(Bezier(0,1,2,3).isFinite());
+
+ EXPECT_EQ(3u, Bezier(0,2,4,5).order());
+
+ ///cout << " Bezier::Bezier(const Bezier& b);\n";
+ //cout << Bezier(wiggle) << " == " << wiggle << endl;
+
+ //cout << "explicit Bezier(unsigned ord);\n";
+ //cout << Bezier(10) << endl;
+
+ //cout << "Bezier(Coord c0, Coord c1);\n";
+ //cout << Bezier(0.0,1.0) << endl;
+
+ //cout << "Bezier(Coord c0, Coord c1, Coord c2);\n";
+ //cout << Bezier(0,1, 2) << endl;
+
+ //cout << "Bezier(Coord c0, Coord c1, Coord c2, Coord c3);\n";
+ //cout << Bezier(0,1,2,3) << endl;
+
+ //cout << "unsigned degree();\n";
+ EXPECT_EQ(2u, hump.degree());
+
+ //cout << "unsigned size();\n";
+ EXPECT_EQ(3u, hump.size());
+}
+
+TEST_F(BezierTest, ValueAt) {
+ EXPECT_EQ(0.0, wiggle.at0());
+ EXPECT_EQ(3.0, wiggle.at1());
+
+ EXPECT_EQ(0.0, wiggle.valueAt(0.5));
+
+ EXPECT_EQ(0.0, wiggle(0.5));
+
+ //cout << "SBasis toSBasis();\n";
+ //cout << unit.toSBasis() << endl;
+ //cout << hump.toSBasis() << endl;
+ //cout << wiggle.toSBasis() << endl;
+}
+
+TEST_F(BezierTest, Casteljau) {
+ unsigned N = wiggle.order() + 1;
+ std::vector<Coord> left(N), right(N);
+ std::vector<Coord> left2(N), right2(N);
+ double eps = 1e-15;
+
+ for (unsigned i = 0; i < 10000; ++i) {
+ double t = g_random_double_range(0, 1);
+ double vok = bernstein_value_at(t, &wiggle[0], wiggle.order());
+ double v = casteljau_subdivision<double>(t, &wiggle[0], &left[0], &right[0], wiggle.order());
+ EXPECT_near(v, vok, eps);
+ EXPECT_EQ(left[0], wiggle.at0());
+ EXPECT_EQ(left[wiggle.order()], right[0]);
+ EXPECT_EQ(right[wiggle.order()], wiggle.at1());
+
+ double vl = casteljau_subdivision<double>(t, &wiggle[0], &left2[0], NULL, wiggle.order());
+ double vr = casteljau_subdivision<double>(t, &wiggle[0], NULL, &right2[0], wiggle.order());
+ EXPECT_EQ(vl, vok);
+ EXPECT_near(vr, vok, eps);
+ EXPECT_vector_near(left2, left, eps);
+ EXPECT_vector_equal(right2, right);
+
+ double vnone = casteljau_subdivision<double>(t, &wiggle[0], NULL, NULL, wiggle.order());
+ EXPECT_near(vnone, vok, 1e-12);
+ }
+}
+
+TEST_F(BezierTest, Portion) {
+ constexpr Coord eps{1e-12};
+
+ for (unsigned i = 0; i < 10000; ++i) {
+ double from = g_random_double_range(0, 1);
+ double to = g_random_double_range(0, 1);
+ for (auto & input : fragments) {
+ Bezier result = portion(input, from, to);
+
+ // the endpoints must correspond exactly
+ EXPECT_near(result.at0(), input.valueAt(from), eps);
+ EXPECT_near(result.at1(), input.valueAt(to), eps);
+ }
+ }
+}
+
+TEST_F(BezierTest, Subdivide) {
+ std::vector<std::pair<Bezier, double> > errors;
+ for (unsigned i = 0; i < 10000; ++i) {
+ double t = g_random_double_range(0, 1e-6);
+ for (auto & input : fragments) {
+ std::pair<Bezier, Bezier> result = input.subdivide(t);
+
+ // the endpoints must correspond exactly
+ // moreover, the subdivision point must be exactly equal to valueAt(t)
+ EXPECT_DOUBLE_EQ(result.first.at0(), input.at0());
+ EXPECT_DOUBLE_EQ(result.first.at1(), result.second.at0());
+ EXPECT_DOUBLE_EQ(result.second.at0(), input.valueAt(t));
+ EXPECT_DOUBLE_EQ(result.second.at1(), input.at1());
+
+ // ditto for valueAt
+ EXPECT_DOUBLE_EQ(result.first.valueAt(0), input.valueAt(0));
+ EXPECT_DOUBLE_EQ(result.first.valueAt(1), result.second.valueAt(0));
+ EXPECT_DOUBLE_EQ(result.second.valueAt(0), input.valueAt(t));
+ EXPECT_DOUBLE_EQ(result.second.valueAt(1), input.valueAt(1));
+
+ if (result.first.at1() != result.second.at0()) {
+ errors.emplace_back(input, t);
+ }
+ }
+ }
+ if (!errors.empty()) {
+ std::cout << "Found " << errors.size() << " subdivision errors" << std::endl;
+ for (unsigned i = 0; i < errors.size(); ++i) {
+ std::cout << "Error #" << i << ":\n"
+ << errors[i].first << "\n"
+ << "t: " << format_coord_nice(errors[i].second) << std::endl;
+ }
+ }
+}
+
+TEST_F(BezierTest, Mutation) {
+//Coord &operator[](unsigned ix);
+//Coord const &operator[](unsigned ix);
+//void setCoeff(unsigned ix double val);
+ //cout << "bigun\n";
+ Bezier bigun(Bezier::Order(30));
+ bigun.setCoeff(5,10.0);
+ for(unsigned i = 0; i < bigun.size(); i++) {
+ EXPECT_EQ((i == 5) ? 10 : 0, bigun[i]);
+ }
+
+ bigun[5] = -3;
+ for(unsigned i = 0; i < bigun.size(); i++) {
+ EXPECT_EQ((i == 5) ? -3 : 0, bigun[i]);
+ }
+}
+
+TEST_F(BezierTest, MultiDerivative) {
+ vector<double> vnd = wiggle.valueAndDerivatives(0.5, 5);
+ expect_array((const double[]){0,0,12,72,0,0}, vnd);
+}
+
+TEST_F(BezierTest, DegreeElevation) {
+ EXPECT_TRUE(are_equal(wiggle, wiggle));
+ Bezier Q = wiggle;
+ Bezier P = Q.elevate_degree();
+ EXPECT_EQ(P.size(), Q.size()+1);
+ //EXPECT_EQ(0, P.forward_difference(1)[0]);
+ EXPECT_TRUE(are_equal(Q, P));
+ Q = wiggle;
+ P = Q.elevate_to_degree(10);
+ EXPECT_EQ(10u, P.order());
+ EXPECT_TRUE(are_equal(Q, P));
+ //EXPECT_EQ(0, P.forward_difference(10)[0]);
+ /*Q = wiggle.elevate_degree();
+ P = Q.reduce_degree();
+ EXPECT_EQ(P.size()+1, Q.size());
+ EXPECT_TRUE(are_equal(Q, P));*/
+}
+//std::pair<Bezier, Bezier > subdivide(Coord t);
+
+// Constructs a linear Bezier with root at t
+Bezier linear_root(double t) {
+ return Bezier(0-t, 1-t);
+}
+
+// Constructs a Bezier with roots at the locations in x
+Bezier array_roots(vector<double> x) {
+ Bezier b(1);
+ for(double i : x) {
+ b = multiply(b, linear_root(i));
+ }
+ return b;
+}
+
+TEST_F(BezierTest, Deflate) {
+ Bezier b = array_roots(vector_from_array((const double[]){0,0.25,0.5}));
+ EXPECT_FLOAT_EQ(0, b.at0());
+ b = b.deflate();
+ EXPECT_FLOAT_EQ(0, b.valueAt(0.25));
+ b = b.subdivide(0.25).second;
+ EXPECT_FLOAT_EQ(0, b.at0());
+ b = b.deflate();
+ const double rootposition = (0.5-0.25) / (1-0.25);
+ constexpr Coord eps{1e-12};
+ EXPECT_near(0.0, b.valueAt(rootposition), eps);
+ b = b.subdivide(rootposition).second;
+ EXPECT_near(0.0, b.at0(), eps);
+}
+
+TEST_F(BezierTest, Roots) {
+ expect_array((const double[]){0, 0.5, 0.5}, wiggle.roots());
+
+ /*Bezier bigun(Bezier::Order(30));
+ for(unsigned i = 0; i < bigun.size(); i++) {
+ bigun.setCoeff(i,rand()-0.5);
+ }
+ cout << bigun.roots() << endl;*/
+
+ // The results of our rootfinding are at the moment fairly inaccurate.
+ double eps = 5e-4;
+
+ vector<vector<double> > tests;
+ tests.push_back(vector_from_array((const double[]){0}));
+ tests.push_back(vector_from_array((const double[]){1}));
+ tests.push_back(vector_from_array((const double[]){0, 0}));
+ tests.push_back(vector_from_array((const double[]){0.5}));
+ tests.push_back(vector_from_array((const double[]){0.5, 0.5}));
+ tests.push_back(vector_from_array((const double[]){0.1, 0.1}));
+ tests.push_back(vector_from_array((const double[]){0.1, 0.1, 0.1}));
+ tests.push_back(vector_from_array((const double[]){0.25,0.75}));
+ tests.push_back(vector_from_array((const double[]){0.5,0.5}));
+ tests.push_back(vector_from_array((const double[]){0, 0.2, 0.6, 0.6, 1}));
+ tests.push_back(vector_from_array((const double[]){.1,.2,.3,.4,.5,.6}));
+ tests.push_back(vector_from_array((const double[]){0.25,0.25,0.25,0.75,0.75,0.75}));
+
+ for(auto & test : tests) {
+ Bezier b = array_roots(test);
+ //std::cout << tests[test_i] << ": " << b << std::endl;
+ //std::cout << b.roots() << std::endl;
+ EXPECT_vector_near(test, b.roots(), eps);
+ }
+}
+
+TEST_F(BezierTest, BoundsExact) {
+ OptInterval unit_bounds = bounds_exact(unit);
+ EXPECT_EQ(unit_bounds->min(), 0);
+ EXPECT_EQ(unit_bounds->max(), 1);
+
+ OptInterval hump_bounds = bounds_exact(hump);
+ EXPECT_EQ(hump_bounds->min(), 0);
+ EXPECT_FLOAT_EQ(hump_bounds->max(), hump.valueAt(0.5));
+
+ OptInterval wiggle_bounds = bounds_exact(wiggle);
+ EXPECT_EQ(wiggle_bounds->min(), 0);
+ EXPECT_EQ(wiggle_bounds->max(), 3);
+}
+
+TEST_F(BezierTest, Operators) {
+ // Test equality operators
+ EXPECT_EQ(zero, zero);
+ EXPECT_EQ(hump, hump);
+ EXPECT_EQ(wiggle, wiggle);
+ EXPECT_EQ(unit, unit);
+
+ EXPECT_NE(zero, hump);
+ EXPECT_NE(hump, zero);
+ EXPECT_NE(wiggle, hump);
+ EXPECT_NE(zero, wiggle);
+ EXPECT_NE(wiggle, unit);
+
+ // Recall that hump == Bezier(0,1,0);
+ EXPECT_EQ(hump + 3, Bezier(3, 4, 3));
+ EXPECT_EQ(hump - 3, Bezier(-3, -2, -3));
+ EXPECT_EQ(hump * 3, Bezier(0, 3, 0));
+ EXPECT_EQ(hump / 3, Bezier(0, 1.0/3.0, 0));
+ EXPECT_EQ(-hump, Bezier(0, -1, 0));
+
+ Bezier reverse_wiggle = reverse(wiggle);
+ EXPECT_EQ(reverse_wiggle.at0(), wiggle.at1());
+ EXPECT_EQ(reverse_wiggle.at1(), wiggle.at0());
+ EXPECT_TRUE(are_equal(reverse(reverse_wiggle), wiggle));
+
+ //cout << "Bezier portion(const Bezier & a, double from, double to);\n";
+ //cout << portion(Bezier(0.0,2.0), 0.5, 1) << endl;
+
+// std::vector<Point> bezier_points(const D2<Bezier > & a) {
+
+ /*cout << "Bezier derivative(const Bezier & a);\n";
+ std::cout << derivative(hump) <<std::endl;
+ std::cout << integral(hump) <<std::endl;*/
+
+ EXPECT_TRUE(are_equal(derivative(integral(wiggle)), wiggle));
+ //std::cout << derivative(integral(hump)) <<std::endl;
+ expect_array((const double []){0.5}, derivative(hump).roots());
+
+ EXPECT_TRUE(bounds_fast(hump)->contains(Interval(0,hump.valueAt(0.5))));
+
+ EXPECT_EQ(Interval(0,hump.valueAt(0.5)), *bounds_exact(hump));
+
+ Interval tight_local_bounds(min(hump.valueAt(0.3),hump.valueAt(0.6)),
+ hump.valueAt(0.5));
+ EXPECT_TRUE(bounds_local(hump, Interval(0.3, 0.6))->contains(tight_local_bounds));
+
+ Bezier Bs[] = {unit, hump, wiggle};
+ for(auto B : Bs) {
+ Bezier product = multiply(B, B);
+ for(int i = 0; i <= 16; i++) {
+ double t = i/16.0;
+ double b = B.valueAt(t);
+ EXPECT_near(b*b, product.valueAt(t), 1e-12);
+ }
+ }
+}
+
+struct XPt {
+ XPt(Coord x, Coord y, Coord ta, Coord tb)
+ : p(x, y), ta(ta), tb(tb)
+ {}
+ XPt() {}
+ Point p;
+ Coord ta, tb;
+};
+
+struct XTest {
+ D2<Bezier> a;
+ D2<Bezier> b;
+ std::vector<XPt> s;
+};
+
+struct CILess {
+ bool operator()(CurveIntersection const &a, CurveIntersection const &b) const {
+ if (a.first < b.first) return true;
+ if (a.first == b.first && a.second < b.second) return true;
+ return false;
+ }
+};
+
+TEST_F(BezierTest, Intersection) {
+ /* Intersection test cases taken from:
+ * Dieter Lasser (1988), Calculating the Self-Intersections of Bezier Curves
+ * https://archive.org/stream/calculatingselfi00lass
+ *
+ * The intersection points are not actually calculated to a high precision
+ * in the paper. The most relevant tests are whether the curves actually
+ * intersect at the returned time values (i.e. whether a(ta) = b(tb))
+ * and whether the number of intersections is correct.
+ */
+ typedef D2<Bezier> D2Bez;
+ std::vector<XTest> tests;
+
+ // Example 1
+ tests.emplace_back();
+ tests.back().a = D2Bez(Bezier(-3.3, -3.3, 0, 3.3, 3.3), Bezier(1.3, -0.7, 2.3, -0.7, 1.3));
+ tests.back().b = D2Bez(Bezier(-4.0, -4.0, 0, 4.0, 4.0), Bezier(-0.35, 3.0, -2.6, 3.0, -0.35));
+ tests.back().s.resize(4);
+ tests.back().s[0] = XPt(-3.12109, 0.76362, 0.09834, 0.20604);
+ tests.back().s[1] = XPt(-1.67341, 0.60298, 0.32366, 0.35662);
+ tests.back().s[2] = XPt(1.67341, 0.60298, 0.67634, 0.64338);
+ tests.back().s[3] = XPt(3.12109, 0.76362, 0.90166, 0.79396);
+
+ // Example 2
+ tests.emplace_back();
+ tests.back().a = D2Bez(Bezier(0, 0, 3, 3), Bezier(0, 14, -9, 5));
+ tests.back().b = D2Bez(Bezier(-1, 13, -10, 4), Bezier(4, 4, 1, 1));
+ tests.back().s.resize(9);
+ tests.back().s[0] = XPt(0.00809, 1.17249, 0.03029, 0.85430);
+ tests.back().s[1] = XPt(0.02596, 1.97778, 0.05471, 0.61825);
+ tests.back().s[2] = XPt(0.17250, 3.99191, 0.14570, 0.03029);
+ tests.back().s[3] = XPt(0.97778, 3.97404, 0.38175, 0.05471);
+ tests.back().s[4] = XPt(1.5, 2.5, 0.5, 0.5);
+ tests.back().s[5] = XPt(2.02221, 1.02596, 0.61825, 0.94529);
+ tests.back().s[6] = XPt(2.82750, 1.00809, 0.85430, 0.96971);
+ tests.back().s[7] = XPt(2.97404, 3.02221, 0.94529, 0.38175);
+ tests.back().s[8] = XPt(2.99191, 3.82750, 0.96971, 0.14570);
+
+ // Example 3
+ tests.emplace_back();
+ tests.back().a = D2Bez(Bezier(-5, -5, -3, 0, 3, 5, 5), Bezier(0, 3.555, -1, 4.17, -1, 3.555, 0));
+ tests.back().b = D2Bez(Bezier(-6, -6, -3, 0, 3, 6, 6), Bezier(3, -0.555, 4, -1.17, 4, -0.555, 3));
+ tests.back().s.resize(6);
+ tests.back().s[0] = XPt(-3.64353, 1.49822, 0.23120, 0.27305);
+ tests.back().s[1] = XPt(-2.92393, 1.50086, 0.29330, 0.32148);
+ tests.back().s[2] = XPt(-0.77325, 1.49989, 0.44827, 0.45409);
+ tests.back().s[3] = XPt(0.77325, 1.49989, 0.55173, 0.54591);
+ tests.back().s[4] = XPt(2.92393, 1.50086, 0.70670, 0.67852);
+ tests.back().s[5] = XPt(3.64353, 1.49822, 0.76880, 0.72695);
+
+ // Example 4
+ tests.emplace_back();
+ tests.back().a = D2Bez(Bezier(-4, -10, -2, -2, 2, 2, 10, 4), Bezier(0, 6, 6, 0, 0, 6, 6, 0));
+ tests.back().b = D2Bez(Bezier(-8, 0, 8), Bezier(1, 6, 1));
+ tests.back().s.resize(4);
+ tests.back().s[0] = XPt(-5.69310, 2.23393, 0.06613, 0.14418);
+ tests.back().s[1] = XPt(-2.68113, 3.21920, 0.35152, 0.33243);
+ tests.back().s[2] = XPt(2.68113, 3.21920, 0.64848, 0.66757);
+ tests.back().s[3] = XPt(5.69310, 2.23393, 0.93387, 0.85582);
+
+ //std::cout << std::setprecision(5);
+
+ for (unsigned i = 0; i < tests.size(); ++i) {
+ BezierCurve a(tests[i].a), b(tests[i].b);
+ std::vector<CurveIntersection> xs;
+ xs = a.intersect(b, 1e-8);
+ std::sort(xs.begin(), xs.end(), CILess());
+ //xs.erase(std::unique(xs.begin(), xs.end(), XEqual()), xs.end());
+
+ std::cout << "\n\n"
+ << "===============================\n"
+ << "=== Intersection Testcase " << i+1 << " ===\n"
+ << "===============================\n" << std::endl;
+
+ EXPECT_EQ(xs.size(), tests[i].s.size());
+ //if (xs.size() != tests[i].s.size()) continue;
+
+ for (unsigned j = 0; j < std::min(xs.size(), tests[i].s.size()); ++j) {
+ std::cout << xs[j].first << " = " << a.pointAt(xs[j].first) << " "
+ << xs[j].second << " = " << b.pointAt(xs[j].second) << "\n"
+ << tests[i].s[j].ta << " = " << tests[i].a.valueAt(tests[i].s[j].ta) << " "
+ << tests[i].s[j].tb << " = " << tests[i].b.valueAt(tests[i].s[j].tb) << std::endl;
+ }
+
+ EXPECT_intersections_valid(a, b, xs, 1e-6);
+ }
+
+ #if 0
+ // these contain second-order intersections
+ Coord a5x[] = {-1.5, -1.5, -10, -10, 0, 10, 10, 1.5, 1.5};
+ Coord a5y[] = {0, -8, -8, 9, 9, 9, -8, -8, 0};
+ Coord b5x[] = {-3, -12, 0, 12, 3};
+ Coord b5y[] = {-5, 8, 2.062507, 8, -5};
+ Coord p5x[] = {-3.60359, -5.44653, 0, 5.44653, 3.60359};
+ Coord p5y[] = {-4.10631, -0.76332, 4.14844, -0.76332, -4.10631};
+ Coord p5ta[] = {0.01787, 0.10171, 0.5, 0.89829, 0.98213};
+ Coord p5tb[] = {0.12443, 0.28110, 0.5, 0.71890, 0.87557};
+
+ Coord a6x[] = {5, 14, 10, -12, -12, -2};
+ Coord a6y[] = {1, 6, -6, -6, 2, 2};
+ Coord b6x[] = {0, 2, -10.5, -10.5, 3.5, 3, 8, 6};
+ Coord b6y[] = {0, -8, -8, 9, 9, -4.129807, -4.129807, 3};
+ Coord p6x[] = {6.29966, 5.87601, 0.04246, -4.67397, -3.57214};
+ Coord p6y[] = {1.63288, -0.86192, -2.38219, -2.17973, 1.91463};
+ Coord p6ta[] = {0.03184, 0.33990, 0.49353, 0.62148, 0.96618};
+ Coord p6tb[] = {0.96977, 0.85797, 0.05087, 0.28232, 0.46102};
+ #endif
+}
+
+/** Basic test for intersecting a quadratic Bézier with a line segment. */
+TEST_F(BezierTest, QuadraticIntersectLineSeg)
+{
+ double const EPS = 1e-12;
+ auto const bow = QuadraticBezier({0, 0}, {1, 1}, {2, 0});
+ auto const highhoriz = LineSegment(Point(0, 0), Point(2, 0));
+ auto const midhoriz = LineSegment(Point(0, 0.25), Point(2, 0.25));
+ auto const lowhoriz = LineSegment(Point(0, 0.5), Point(2, 0.5));
+ auto const noninters = LineSegment(Point(0, 0.5 + EPS), Point(2, 0.5 + EPS));
+ auto const noninters2 = LineSegment(Point(1, 0), Point(1, 0.5 - EPS));
+
+ auto const endpoint_intersections = bow.intersect(highhoriz, EPS);
+ EXPECT_EQ(endpoint_intersections.size(), 2);
+ EXPECT_intersections_valid(bow, highhoriz, endpoint_intersections, EPS);
+ for (auto const &ex : endpoint_intersections) {
+ EXPECT_DOUBLE_EQ(ex.point()[Y], 0.0);
+ }
+
+ auto const mid_intersections = bow.intersect(midhoriz, EPS);
+ EXPECT_EQ(mid_intersections.size(), 2);
+ EXPECT_intersections_valid(bow, midhoriz, mid_intersections, EPS);
+ for (auto const &mx : mid_intersections) {
+ EXPECT_DOUBLE_EQ(mx.point()[Y], 0.25);
+ }
+
+ auto const tangent_intersection = bow.intersect(lowhoriz, EPS);
+ EXPECT_EQ(tangent_intersection.size(), 1);
+ EXPECT_intersections_valid(bow, lowhoriz, tangent_intersection, EPS);
+ for (auto const &tx : tangent_intersection) {
+ EXPECT_DOUBLE_EQ(tx.point()[Y], 0.5);
+ }
+
+ auto no_intersections = bow.intersect(noninters, EPS);
+ EXPECT_TRUE(no_intersections.empty());
+
+ no_intersections = bow.intersect(noninters2, EPS);
+ EXPECT_TRUE(no_intersections.empty());
+}
+
+TEST_F(BezierTest, QuadraticIntersectLineRandom)
+{
+ g_random_set_seed(0xB747A380);
+ auto const diagonal = LineSegment(Point(0, 0), Point(1, 1));
+ double const EPS = 1e-12;
+
+ for (unsigned i = 0; i < 10'000; i++) {
+ auto q = QuadraticBezier({0, 1}, {g_random_double_range(0.0, 1.0), g_random_double_range(0.0, 1.0)}, {1, 0});
+ auto xings = q.intersect(diagonal, EPS);
+ ASSERT_EQ(xings.size(), 1);
+ auto pt = xings[0].point();
+ EXPECT_TRUE(are_near(pt[X], pt[Y], EPS));
+ EXPECT_intersections_valid(q, diagonal, xings, EPS);
+ }
+}
+
+/** Basic test for intersecting a cubic Bézier with a line segment. */
+TEST_F(BezierTest, CubicIntersectLine)
+{
+ double const EPS = 1e-12;
+ auto const wavelet = CubicBezier({0, 0}, {1, 2}, {0, -2}, {1, 0});
+
+ auto const unit_seg = LineSegment(Point(0, 0), Point(1, 0));
+ auto const expect3 = wavelet.intersect(unit_seg, EPS);
+ EXPECT_EQ(expect3.size(), 3);
+ EXPECT_intersections_valid(wavelet, unit_seg, expect3, EPS);
+
+ auto const half_seg = LineSegment(Point(0, 0), Point(0.5, 0));
+ auto const expect2 = wavelet.intersect(half_seg, EPS);
+ EXPECT_EQ(expect2.size(), 2);
+ EXPECT_intersections_valid(wavelet, half_seg, expect2, EPS);
+
+ auto const less_than_half = LineSegment(Point(0, 0), Point(0.5 - EPS, 0));
+ auto const expect1 = wavelet.intersect(less_than_half, EPS);
+ EXPECT_EQ(expect1.size(), 1);
+ EXPECT_intersections_valid(wavelet, less_than_half, expect1, EPS);
+
+ auto const dollar_stroke = LineSegment(Point(0, 0.5), Point(1, -0.5));
+ auto const dollar_xings = wavelet.intersect(dollar_stroke, EPS);
+ EXPECT_EQ(dollar_xings.size(), 3);
+ EXPECT_intersections_valid(wavelet, dollar_stroke, dollar_xings, EPS);
+}
+
+TEST_F(BezierTest, CubicIntersectLineRandom)
+{
+ g_random_set_seed(0xCAFECAFE);
+ auto const diagonal = LineSegment(Point(0, 0), Point(1, 1));
+ double const EPS = 1e-8;
+
+ for (unsigned i = 0; i < 10'000; i++) {
+ double a1 = g_random_double_range(0.0, 1.0);
+ double a2 = g_random_double_range(a1, 1.0);
+ double b1 = g_random_double_range(0.0, 1.0);
+ double b2 = g_random_double_range(0.0, b1);
+
+ auto c = CubicBezier({0, 1}, {a1, a2}, {b1, b2}, {1, 0});
+ auto xings = c.intersect(diagonal, EPS);
+ ASSERT_EQ(xings.size(), 1);
+ auto pt = xings[0].point();
+ EXPECT_TRUE(are_near(pt[X], pt[Y], EPS));
+ EXPECT_intersections_valid(c, diagonal, xings, EPS);
+ }
+}
+
+/** Regression test for issue https://gitlab.com/inkscape/lib2geom/-/issues/47 . */
+TEST_F(BezierTest, Balloon)
+{
+ auto const loop = CubicBezier({0, 0}, {4, -2}, {4, 2}, {0, 0});
+ auto const seghoriz = LineSegment(Point(-1, 0), Point(0, 0));
+
+ for (double EPS : {1e-6, 1e-9, 1e-12}) {
+ // We expect that 2 intersections are found: one at each end of the loop,
+ // both at the coordinates (0, 0).
+ auto xings_horiz = loop.intersect(seghoriz, EPS);
+ EXPECT_EQ(xings_horiz.size(), 2);
+ EXPECT_intersections_valid(loop, seghoriz, xings_horiz, EPS);
+ }
+}
+
+TEST_F(BezierTest, ExpandToTransformedTest)
+{
+ auto test_curve = [] (Curve const &c) {
+ constexpr int N = 50;
+ for (int i = 0; i < N; i++) {
+ auto angle = 2 * M_PI * i / N;
+ auto transform = Affine(Rotate(angle));
+
+ auto copy = std::unique_ptr<Curve>(c.duplicate());
+ *copy *= transform;
+ auto box1 = copy->boundsExact();
+
+ auto pt = c.initialPoint() * transform;
+ auto box2 = Rect(pt, pt);
+ c.expandToTransformed(box2, transform);
+
+ for (auto i : { X, Y }) {
+ EXPECT_DOUBLE_EQ(box1[i].min(), box2[i].min());
+ EXPECT_DOUBLE_EQ(box1[i].max(), box2[i].max());
+ }
+ }
+ };
+
+ test_curve(LineSegment(Point(-1, 0), Point(1, 2)));
+ test_curve(QuadraticBezier(Point(-1, 0), Point(1, 1), Point(3, 0)));
+ test_curve(CubicBezier(Point(-1, 0), Point(1, 1), Point(2, -2), Point(3, 0)));
+}
+
+TEST_F(BezierTest, ForwardDifferenceTest)
+{
+ auto b = Bezier(3, 4, 2, -5, 7);
+ EXPECT_EQ(b.forward_difference(1), Bezier(19, 34, 22, 5));
+ EXPECT_EQ(b.forward_difference(2), Bezier(-3, 2, 2));
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/bezier-utils-test.cpp b/tests/bezier-utils-test.cpp
new file mode 100644
index 0000000..6f95ccd
--- /dev/null
+++ b/tests/bezier-utils-test.cpp
@@ -0,0 +1,333 @@
+#include "utest.h"
+#include <glib.h>
+
+/* MenTaLguY disclaims all responsibility for this evil idea for testing
+ static functions. The main disadvantages are that we retain the
+ #define's and `using' directives of the included file. */
+#include "../bezier-utils.cpp"
+
+using Geom::Point;
+
+static bool range_approx_equal(double const a[], double const b[], unsigned len);
+
+/* (Returns false if NaN encountered.) */
+template<class T>
+static bool range_equal(T const a[], T const b[], unsigned len) {
+ for (unsigned i = 0; i < len; ++i) {
+ if ( a[i] != b[i] ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+inline bool point_approx_equal(Geom::Point const &a, Geom::Point const &b, double const eps)
+{
+ using Geom::X; using Geom::Y;
+ return ( Geom_DF_TEST_CLOSE(a[X], b[X], eps) &&
+ Geom_DF_TEST_CLOSE(a[Y], b[Y], eps) );
+}
+
+static inline double square(double const x) {
+ return x * x;
+}
+
+/** Determine whether the found control points are the same as previously found on some developer's
+ machine. Doesn't call utest__fail, just writes a message to stdout for diagnostic purposes:
+ the most important test is that the root-mean-square of errors in the estimation are low rather
+ than that the control points found are the same.
+**/
+static void compare_ctlpts(Point const est_b[], Point const exp_est_b[])
+{
+ unsigned diff_mask = 0;
+ for (unsigned i = 0; i < 4; ++i) {
+ for (unsigned d = 0; d < 2; ++d) {
+ if ( fabs( est_b[i][d] - exp_est_b[i][d] ) > 1.1e-5 ) {
+ diff_mask |= 1 << ( i * 2 + d );
+ }
+ }
+ }
+ if ( diff_mask != 0 ) {
+ printf("Warning: got different control points from previously-coded (diffs=0x%x).\n",
+ diff_mask);
+ printf(" Previous:");
+ for (unsigned i = 0; i < 4; ++i) {
+ printf(" (%g, %g)", exp_est_b[i][0], exp_est_b[i][1]); // localizing ok
+ }
+ putchar('\n');
+ printf(" Found: ");
+ for (unsigned i = 0; i < 4; ++i) {
+ printf(" (%g, %g)", est_b[i][0], est_b[i][1]); // localizing ok
+ }
+ putchar('\n');
+ }
+}
+
+static void compare_rms(Point const est_b[], double const t[], Point const d[], unsigned const n,
+ double const exp_rms_error)
+{
+ double sum_errsq = 0.0;
+ for (unsigned i = 0; i < n; ++i) {
+ Point const fit_pt = bezier_pt(3, est_b, t[i]);
+ Point const diff = fit_pt - d[i];
+ sum_errsq += dot(diff, diff);
+ }
+ double const rms_error = sqrt( sum_errsq / n );
+ UTEST_ASSERT( rms_error <= exp_rms_error + 1.1e-6 );
+ if ( rms_error < exp_rms_error - 1.1e-6 ) {
+ /* The fitter code appears to have improved [or the floating point calculations differ
+ on this machine from the machine where exp_rms_error was calculated]. */
+ printf("N.B. rms_error regression requirement can be decreased: have rms_error=%g.\n", rms_error); // localizing ok
+ }
+}
+
+int main(int argc, char *argv[]) {
+ utest_start("bezier-utils.cpp");
+
+ UTEST_TEST("copy_without_nans_or_adjacent_duplicates") {
+ Geom::Point const src[] = {
+ Point(2., 3.),
+ Point(2., 3.),
+ Point(0., 0.),
+ Point(2., 3.),
+ Point(2., 3.),
+ Point(1., 9.),
+ Point(1., 9.)
+ };
+ Point const exp_dest[] = {
+ Point(2., 3.),
+ Point(0., 0.),
+ Point(2., 3.),
+ Point(1., 9.)
+ };
+ g_assert( G_N_ELEMENTS(src) == 7 );
+ Point dest[7];
+ struct tst {
+ unsigned src_ix0;
+ unsigned src_len;
+ unsigned exp_dest_ix0;
+ unsigned exp_dest_len;
+ } const test_data[] = {
+ /* src start ix, src len, exp_dest start ix, exp dest len */
+ {0, 0, 0, 0},
+ {2, 1, 1, 1},
+ {0, 1, 0, 1},
+ {0, 2, 0, 1},
+ {0, 3, 0, 2},
+ {1, 3, 0, 3},
+ {0, 5, 0, 3},
+ {0, 6, 0, 4},
+ {0, 7, 0, 4}
+ };
+ for (unsigned i = 0 ; i < G_N_ELEMENTS(test_data) ; ++i) {
+ tst const &t = test_data[i];
+ UTEST_ASSERT( t.exp_dest_len
+ == copy_without_nans_or_adjacent_duplicates(src + t.src_ix0,
+ t.src_len,
+ dest) );
+ UTEST_ASSERT(range_equal(dest,
+ exp_dest + t.exp_dest_ix0,
+ t.exp_dest_len));
+ }
+ }
+
+ UTEST_TEST("bezier_pt(1)") {
+ Point const a[] = {Point(2.0, 4.0),
+ Point(1.0, 8.0)};
+ UTEST_ASSERT( bezier_pt(1, a, 0.0) == a[0] );
+ UTEST_ASSERT( bezier_pt(1, a, 1.0) == a[1] );
+ UTEST_ASSERT( bezier_pt(1, a, 0.5) == Point(1.5, 6.0) );
+ double const t[] = {0.5, 0.25, 0.3, 0.6};
+ for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
+ double const ti = t[i], si = 1.0 - ti;
+ UTEST_ASSERT( bezier_pt(1, a, ti) == si * a[0] + ti * a[1] );
+ }
+ }
+
+ UTEST_TEST("bezier_pt(2)") {
+ Point const b[] = {Point(1.0, 2.0),
+ Point(8.0, 4.0),
+ Point(3.0, 1.0)};
+ UTEST_ASSERT( bezier_pt(2, b, 0.0) == b[0] );
+ UTEST_ASSERT( bezier_pt(2, b, 1.0) == b[2] );
+ UTEST_ASSERT( bezier_pt(2, b, 0.5) == Point(5.0, 2.75) );
+ double const t[] = {0.5, 0.25, 0.3, 0.6};
+ for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
+ double const ti = t[i], si = 1.0 - ti;
+ Point const exp_pt( si*si * b[0] + 2*si*ti * b[1] + ti*ti * b[2] );
+ Point const pt(bezier_pt(2, b, ti));
+ UTEST_ASSERT(point_approx_equal(pt, exp_pt, 1e-11));
+ }
+ }
+
+ Point const c[] = {Point(1.0, 2.0),
+ Point(8.0, 4.0),
+ Point(3.0, 1.0),
+ Point(-2.0, -4.0)};
+ UTEST_TEST("bezier_pt(3)") {
+ UTEST_ASSERT( bezier_pt(3, c, 0.0) == c[0] );
+ UTEST_ASSERT( bezier_pt(3, c, 1.0) == c[3] );
+ UTEST_ASSERT( bezier_pt(3, c, 0.5) == Point(4.0, 13.0/8.0) );
+ double const t[] = {0.5, 0.25, 0.3, 0.6};
+ for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) {
+ double const ti = t[i], si = 1.0 - ti;
+ UTEST_ASSERT( LInfty( bezier_pt(3, c, ti)
+ - ( si*si*si * c[0] +
+ 3*si*si*ti * c[1] +
+ 3*si*ti*ti * c[2] +
+ ti*ti*ti * c[3] ) )
+ < 1e-4 );
+ }
+ }
+
+ struct Err_tst {
+ Point pt;
+ double u;
+ double err;
+ } const err_tst[] = {
+ {c[0], 0.0, 0.0},
+ {Point(4.0, 13.0/8.0), 0.5, 0.0},
+ {Point(4.0, 2.0), 0.5, 9.0/64.0},
+ {Point(3.0, 2.0), 0.5, 1.0 + 9.0/64.0},
+ {Point(6.0, 2.0), 0.5, 4.0 + 9.0/64.0},
+ {c[3], 1.0, 0.0},
+ };
+
+ UTEST_TEST("compute_max_error_ratio") {
+ Point d[G_N_ELEMENTS(err_tst)];
+ double u[G_N_ELEMENTS(err_tst)];
+ for (unsigned i = 0; i < G_N_ELEMENTS(err_tst); ++i) {
+ Err_tst const &t = err_tst[i];
+ d[i] = t.pt;
+ u[i] = t.u;
+ }
+ g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(d) );
+ unsigned max_ix = ~0u;
+ double const err_ratio = compute_max_error_ratio(d, u, G_N_ELEMENTS(d), c, 1.0, &max_ix);
+ UTEST_ASSERT( fabs( sqrt(err_tst[4].err) - err_ratio ) < 1e-12 );
+ UTEST_ASSERT( max_ix == 4 );
+ }
+
+ UTEST_TEST("chord_length_parameterize") {
+ /* n == 2 */
+ {
+ Point const d[] = {Point(2.9415, -5.8149),
+ Point(23.021, 4.9814)};
+ double u[G_N_ELEMENTS(d)];
+ double const exp_u[] = {0.0, 1.0};
+ g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(exp_u) );
+ chord_length_parameterize(d, u, G_N_ELEMENTS(d));
+ UTEST_ASSERT(range_equal(u, exp_u, G_N_ELEMENTS(exp_u)));
+ }
+
+ /* Straight line. */
+ {
+ double const exp_u[] = {0.0, 0.1829, 0.2105, 0.2105, 0.619, 0.815, 0.999, 1.0};
+ unsigned const n = G_N_ELEMENTS(exp_u);
+ Point d[n];
+ double u[n];
+ Point const a(-23.985, 4.915), b(4.9127, 5.203);
+ for (unsigned i = 0; i < n; ++i) {
+ double bi = exp_u[i], ai = 1.0 - bi;
+ d[i] = ai * a + bi * b;
+ }
+ chord_length_parameterize(d, u, n);
+ UTEST_ASSERT(range_approx_equal(u, exp_u, n));
+ }
+ }
+
+ /* Feed it some points that can be fit exactly with a single bezier segment, and see how
+ well it manages. */
+ Point const src_b[4] = {Point(5., -3.),
+ Point(8., 0.),
+ Point(4., 2.),
+ Point(3., 3.)};
+ double const t[] = {0.0, .001, .03, .05, .09, .13, .18, .25, .29, .33, .39, .44,
+ .51, .57, .62, .69, .75, .81, .91, .93, .97, .98, .999, 1.0};
+ unsigned const n = G_N_ELEMENTS(t);
+ Point d[n];
+ for (unsigned i = 0; i < n; ++i) {
+ d[i] = bezier_pt(3, src_b, t[i]);
+ }
+ Point const tHat1(unit_vector( src_b[1] - src_b[0] ));
+ Point const tHat2(unit_vector( src_b[2] - src_b[3] ));
+
+ UTEST_TEST("generate_bezier") {
+ Point est_b[4];
+ generate_bezier(est_b, d, t, n, tHat1, tHat2, 1.0);
+
+ compare_ctlpts(est_b, src_b);
+
+ /* We're being unfair here in using our t[] rather than best t[] for est_b: we
+ may over-estimate RMS of errors. */
+ compare_rms(est_b, t, d, n, 1e-8);
+ }
+
+ UTEST_TEST("sp_bezier_fit_cubic_full") {
+ Point est_b[4];
+ int splitpoints[2];
+ gint const succ = sp_bezier_fit_cubic_full(est_b, splitpoints, d, n, tHat1, tHat2, square(1.2), 1);
+ UTEST_ASSERT( succ == 1 );
+
+ Point const exp_est_b[4] = {
+ Point(5.000000, -3.000000),
+ Point(7.5753, -0.4247),
+ Point(4.77533, 1.22467),
+ Point(3, 3)
+ };
+ compare_ctlpts(est_b, exp_est_b);
+
+ /* We're being unfair here in using our t[] rather than best t[] for est_b: we
+ may over-estimate RMS of errors. */
+ compare_rms(est_b, t, d, n, .307911);
+ }
+
+ UTEST_TEST("sp_bezier_fit_cubic") {
+ Point est_b[4];
+ gint const succ = sp_bezier_fit_cubic(est_b, d, n, square(1.2));
+ UTEST_ASSERT( succ == 1 );
+
+ Point const exp_est_b[4] = {
+ Point(5.000000, -3.000000),
+ Point(7.57134, -0.423509),
+ Point(4.77929, 1.22426),
+ Point(3, 3)
+ };
+ compare_ctlpts(est_b, exp_est_b);
+
+#if 1 /* A change has been made to right_tangent. I believe that usually this change
+ will result in better fitting, but it won't do as well for this example where
+ we happen to be feeding a t=0.999 point to the fitter. */
+ printf("TODO: Update this test case for revised right_tangent implementation.\n");
+ /* In particular, have a test case to show whether the new implementation
+ really is likely to be better on average. */
+#else
+ /* We're being unfair here in using our t[] rather than best t[] for est_b: we
+ may over-estimate RMS of errors. */
+ compare_rms(est_b, t, d, n, .307983);
+#endif
+ }
+
+ return !utest_end();
+}
+
+/* (Returns false if NaN encountered.) */
+static bool range_approx_equal(double const a[], double const b[], unsigned const len) {
+ for (unsigned i = 0; i < len; ++i) {
+ if (!( fabs( a[i] - b[i] ) < 1e-4 )) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/choose-test.cpp b/tests/choose-test.cpp
new file mode 100644
index 0000000..c2b2b50
--- /dev/null
+++ b/tests/choose-test.cpp
@@ -0,0 +1,79 @@
+/** @file
+ * @brief Unit tests for the binomial coefficient function.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include "testing.h"
+#include <2geom/choose.h>
+#include <glib.h>
+
+using namespace Geom;
+
+TEST(ChooseTest, PascalsTriangle) {
+ // check whether the values match Pascal's triangle
+ for (unsigned i = 0; i < 500; ++i) {
+ int n = g_random_int_range(3, 100);
+ int k = g_random_int_range(1, n-1);
+
+ double a = choose<double>(n, k);
+ double b = choose<double>(n-1, k);
+ double c = choose<double>(n-1, k-1);
+
+ EXPECT_NEAR((b + c) / a, 1.0, 1e-14);
+ }
+}
+
+TEST(ChooseTest, Values) {
+ // test some well-known values
+ EXPECT_EQ(choose<double>(0, 0), 1);
+ EXPECT_EQ(choose<double>(1, 0), 1);
+ EXPECT_EQ(choose<double>(1, 1), 1);
+ EXPECT_EQ(choose<double>(127, 127), 1);
+ EXPECT_EQ(choose<double>(92, 0), 1);
+ EXPECT_EQ(choose<double>(2, 1), 2);
+
+ // number of possible flops in Texas Hold 'Em Poker
+ EXPECT_EQ(choose<double>(50, 3), 19600.);
+ EXPECT_EQ(choose<double>(50, 47), 19600.);
+ // number of possible hands in bridge
+ EXPECT_EQ(choose<double>(52, 13), 635013559600.);
+ EXPECT_EQ(choose<double>(52, 39), 635013559600.);
+ // number of possible Lotto results
+ EXPECT_EQ(choose<double>(49, 6), 13983816.);
+ EXPECT_EQ(choose<double>(49, 43), 13983816.);
+}
+
+TEST(ChooseTest, Unsigned) {
+ auto const BIG = std::numeric_limits<unsigned>::max() - 1;
+ EXPECT_EQ(choose<unsigned>(BIG, BIG - 1), BIG);
+ EXPECT_EQ(choose<unsigned>(BIG, BIG), 1);
+ EXPECT_EQ(choose<unsigned>(BIG, BIG + 1), 0);
+}
diff --git a/tests/circle-test.cpp b/tests/circle-test.cpp
new file mode 100644
index 0000000..5ff6493
--- /dev/null
+++ b/tests/circle-test.cpp
@@ -0,0 +1,141 @@
+/** @file
+ * @brief Unit tests for Circle and related functions.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include "testing.h"
+#include <2geom/circle.h>
+#include <2geom/line.h>
+
+using namespace Geom;
+
+TEST(CircleTest, Equality) {
+ Circle a(4, 5, 6);
+ Circle b(Point(4, 5), 6);
+ Circle c(4.00000001, 5, 6);
+
+ EXPECT_EQ(a, b);
+ EXPECT_NE(a, c);
+ EXPECT_NE(b, c);
+}
+
+TEST(CircleTest, Nearness) {
+ Circle a(4, 5, 6);
+ Circle b(4.000007, 5, 6);
+ Circle c(4, 5, 6.000007);
+ Circle d(4.000007, 5, 6.000007);
+ Circle e(4, 5, 7);
+
+ EXPECT_TRUE(are_near(a, b, 1e-5));
+ EXPECT_TRUE(are_near(a, c, 1e-5));
+ EXPECT_TRUE(are_near(c, d, 1e-5));
+ EXPECT_FALSE(are_near(a, d, 1e-5));
+ EXPECT_FALSE(are_near(a, e, 1e-2));
+ EXPECT_FALSE(are_near(b, e, 1e-2));
+ EXPECT_FALSE(are_near(c, e, 1e-2));
+}
+
+TEST(CircleTest, UnitCircleTransform) {
+ Circle c(17, 23, 22);
+
+ Point q = c.pointAt(M_PI/2);
+ Point p = Point(0, 1) * c.unitCircleTransform();
+ Point r = q * c.inverseUnitCircleTransform();
+
+ EXPECT_FLOAT_EQ(p[X], q[X]);
+ EXPECT_FLOAT_EQ(p[Y], q[Y]);
+ EXPECT_FLOAT_EQ(r[X], 0);
+ EXPECT_FLOAT_EQ(r[Y], 1);
+}
+
+TEST(CircleTest, Coefficients) {
+ Circle circ(5, 12, 87), circ2;
+
+ Coord a, b, c, d;
+ circ.coefficients(a, b, c, d);
+ circ2.setCoefficients(a, b, c, d);
+
+ EXPECT_TRUE(are_near(circ, circ2, 1e-15));
+
+ for (unsigned i = 0; i < 100; ++i) {
+ Coord t = -5 + 0.111 * i;
+ Point p = circ.pointAt(t);
+ Coord eqres = a * p[X]*p[X] + a*p[Y]*p[Y] + b*p[X] + c*p[Y] + d;
+ EXPECT_NEAR(eqres, 0, 1e-11);
+ }
+}
+
+TEST(CircleTest, CircleIntersection) {
+ Circle a(5, 5, 5), b(15, 5, 5), c(10, 10, 6), d(-5, 5, 2);
+ std::vector<ShapeIntersection> r1, r2, r3;
+
+ r1 = a.intersect(b);
+ ASSERT_EQ(r1.size(), 1u);
+ EXPECT_EQ(r1[0].point(), Point(10,5));
+ EXPECT_intersections_valid(a, b, r1, 1e-15);
+
+ r2 = a.intersect(c);
+ EXPECT_EQ(r2.size(), 2u);
+ EXPECT_intersections_valid(a, c, r2, 1e-15);
+
+ r3 = b.intersect(c);
+ EXPECT_EQ(r3.size(), 2u);
+ EXPECT_intersections_valid(b, c, r3, 4e-15);
+
+ EXPECT_TRUE(a.intersect(d).empty());
+ EXPECT_TRUE(b.intersect(d).empty());
+ EXPECT_TRUE(c.intersect(d).empty());
+}
+
+TEST(CircleTest, LineIntersection) {
+ Circle c(5, 5, 10);
+ Line l1(Point(-5, -20), Point(-5, 20));
+ Line l2(Point(0, 0), Point(10, 2.3));
+ Line l3(Point(20, -20), Point(0, -20));
+
+ EXPECT_TRUE(c.intersects(l1));
+ EXPECT_TRUE(c.intersects(l2));
+ EXPECT_FALSE(c.intersects(l3));
+
+ std::vector<ShapeIntersection> r1, r2, r3;
+
+ r1 = c.intersect(l1);
+ ASSERT_EQ(r1.size(), 1u);
+ EXPECT_EQ(r1[0].point(), Point(-5, 5));
+ EXPECT_intersections_valid(c, l1, r1, 1e-15);
+
+ r2 = c.intersect(l2);
+ EXPECT_EQ(r2.size(), 2u);
+ EXPECT_intersections_valid(c, l2, r2, 1e-14);
+
+ r3 = c.intersect(l3);
+ EXPECT_TRUE(r3.empty());
+}
diff --git a/tests/convex-hull-test.cpp b/tests/convex-hull-test.cpp
new file mode 100644
index 0000000..2f20f43
--- /dev/null
+++ b/tests/convex-hull-test.cpp
@@ -0,0 +1,335 @@
+/** @file
+ * @brief Unit tests for ConvexHull and related functions.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Nathan Hurst <njh@njhurst.com>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2011-2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include "testing.h"
+#include <iostream>
+
+#include <2geom/convex-hull.h>
+#include <vector>
+#include <iterator>
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846
+#endif
+
+using namespace std;
+using namespace Geom;
+
+void points_from_shape(std::vector<Point> &pts, std::string const &shape) {
+ pts.clear();
+ int x = 0, y = 0;
+ for (char c : shape) {
+ if (c == '\n') {
+ x = 0; ++y;
+ continue;
+ }
+ if (c == ' ') {
+ ++x;
+ continue;
+ }
+ pts.emplace_back(x, y);
+ ++x;
+ }
+}
+
+class ConvexHullTest : public ::testing::Test {
+protected:
+ ConvexHullTest()
+ : null(hulls[0])
+ , point(hulls[1])
+ , line(hulls[2])
+ , triangle(hulls[3])
+ , square(hulls[4])
+ , hexagon(hulls[5])
+ , antihexagon(hulls[6])
+ , gem(hulls[7])
+ , diamond(hulls[8])
+ {
+ null = ConvexHull();
+
+ std::vector<Point> pts;
+
+ pts.emplace_back(0,0);
+ point = ConvexHull(pts);
+ pts.emplace_back(1,0);
+ line = ConvexHull(pts);
+ pts.emplace_back(0,1);
+ triangle = ConvexHull(pts);
+ pts.emplace_back(1,1);
+ square = ConvexHull(pts);
+ pts.clear();
+
+ for(int i = 0; i < 6; i++) {
+ pts.emplace_back(cos(i*M_PI*2/6), sin(i*M_PI*2/6));
+ }
+ hexagon = ConvexHull(pts);
+ pts.clear();
+
+ for(int i = 0; i < 6; i++) {
+ pts.emplace_back(cos((1-i*2)*M_PI/6), sin((1-i*2)*M_PI/6));
+ }
+ antihexagon = ConvexHull(pts);
+ pts.clear();
+
+ gem_shape =
+ " ++++ \n"
+ "++++++ \n"
+ "++++++ \n"
+ "++++++ \n"
+ " ++++ \n";
+ points_from_shape(pts, gem_shape);
+ gem.swap(pts);
+
+ diamond_shape =
+ " + \n"
+ " +++++ \n"
+ " +++++ \n"
+ "+++++++ \n"
+ " +++++ \n"
+ " +++++ \n"
+ " + \n";
+ points_from_shape(pts, diamond_shape);
+ diamond.swap(pts);
+ }
+
+ ConvexHull hulls[9];
+ ConvexHull &null, &point, &line, &triangle, &square, &hexagon, &antihexagon, &gem, &diamond;
+ std::string gem_shape, diamond_shape;
+};
+
+void check_convex(ConvexHull &/*ch*/) {
+ // TODO
+}
+
+TEST_F(ConvexHullTest, SizeAndDegeneracy) {
+ EXPECT_EQ(0u, null.size());
+ EXPECT_TRUE(null.empty());
+ EXPECT_TRUE(null.isDegenerate());
+ EXPECT_FALSE(null.isSingular());
+ EXPECT_FALSE(null.isLinear());
+
+ EXPECT_EQ(1u, point.size());
+ EXPECT_FALSE(point.empty());
+ EXPECT_TRUE(point.isDegenerate());
+ EXPECT_TRUE(point.isSingular());
+ EXPECT_FALSE(point.isLinear());
+
+ EXPECT_EQ(2u, line.size());
+ EXPECT_FALSE(line.empty());
+ EXPECT_TRUE(line.isDegenerate());
+ EXPECT_FALSE(line.isSingular());
+ EXPECT_TRUE(line.isLinear());
+
+ EXPECT_EQ(3u, triangle.size());
+ EXPECT_FALSE(triangle.empty());
+ EXPECT_FALSE(triangle.isDegenerate());
+ EXPECT_FALSE(triangle.isSingular());
+ EXPECT_FALSE(triangle.isLinear());
+
+ EXPECT_EQ(4u, square.size());
+ EXPECT_FALSE(square.empty());
+ EXPECT_FALSE(square.isDegenerate());
+ EXPECT_FALSE(square.isSingular());
+ EXPECT_FALSE(square.isLinear());
+
+ EXPECT_EQ(6u, hexagon.size());
+ EXPECT_FALSE(hexagon.empty());
+ EXPECT_FALSE(hexagon.isDegenerate());
+ EXPECT_FALSE(hexagon.isSingular());
+ EXPECT_FALSE(hexagon.isLinear());
+
+ EXPECT_EQ(6u, antihexagon.size());
+ EXPECT_FALSE(antihexagon.empty());
+ EXPECT_FALSE(antihexagon.isDegenerate());
+ EXPECT_FALSE(antihexagon.isSingular());
+ EXPECT_FALSE(antihexagon.isLinear());
+
+ EXPECT_EQ(8u, gem.size());
+ EXPECT_FALSE(gem.empty());
+ EXPECT_FALSE(gem.isDegenerate());
+ EXPECT_FALSE(gem.isSingular());
+ EXPECT_FALSE(gem.isLinear());
+
+ EXPECT_EQ(8u, diamond.size());
+ EXPECT_FALSE(diamond.empty());
+ EXPECT_FALSE(diamond.isDegenerate());
+ EXPECT_FALSE(diamond.isSingular());
+ EXPECT_FALSE(diamond.isLinear());
+}
+
+
+TEST_F(ConvexHullTest, Area) {
+ EXPECT_EQ(0, null.area());
+ EXPECT_EQ(0, point.area());
+ EXPECT_EQ(0, line.area());
+ EXPECT_EQ(0.5, triangle.area());
+ EXPECT_EQ(1, square.area());
+ EXPECT_EQ(18, gem.area());
+ EXPECT_EQ(24, diamond.area());
+ EXPECT_FLOAT_EQ(6*(0.5*1*sin(M_PI/3)), hexagon.area());
+ EXPECT_FLOAT_EQ(6*(0.5*1*sin(M_PI/3)), antihexagon.area());
+}
+
+TEST_F(ConvexHullTest, Bounds) {
+ //Rect hexbounds(-1,sin(M_PI/3),1,-sin(M_PI/3));
+
+ EXPECT_EQ(OptRect(), null.bounds());
+ EXPECT_EQ(OptRect(0,0,0,0), point.bounds());
+ EXPECT_EQ(OptRect(0,0,1,0), line.bounds());
+ EXPECT_EQ(OptRect(0,0,1,1), triangle.bounds());
+ EXPECT_EQ(OptRect(0,0,1,1), square.bounds());
+ EXPECT_EQ(OptRect(0,0,5,4), gem.bounds());
+ EXPECT_EQ(OptRect(0,0,6,6), diamond.bounds());
+ //EXPECT_TRUE(hexbounds == hexagon.bounds());
+ //EXPECT_TRUE(hexbounds == antihexagon.bounds());
+}
+
+::testing::AssertionResult HullContainsPoint(ConvexHull const &h, Point const &p) {
+ if (h.contains(p)) {
+ return ::testing::AssertionSuccess();
+ } else {
+ return ::testing::AssertionFailure()
+ << "Convex hull:\n"
+ << h << "\ndoes not contain " << p;
+ }
+}
+
+TEST_F(ConvexHullTest, PointContainment) {
+ Point zero(0,0), half(0.5, 0.5), x(0.25, 0.25);
+ EXPECT_FALSE(HullContainsPoint(null, zero));
+ EXPECT_TRUE(HullContainsPoint(point, zero));
+ EXPECT_TRUE(HullContainsPoint(line, zero));
+ EXPECT_TRUE(HullContainsPoint(triangle, zero));
+ EXPECT_TRUE(HullContainsPoint(square, zero));
+ EXPECT_FALSE(HullContainsPoint(line, half));
+ EXPECT_TRUE(HullContainsPoint(triangle, x));
+ EXPECT_TRUE(HullContainsPoint(triangle, half));
+ EXPECT_TRUE(HullContainsPoint(square, half));
+ EXPECT_TRUE(HullContainsPoint(hexagon, zero));
+ EXPECT_TRUE(HullContainsPoint(antihexagon, zero));
+
+ std::vector<Point> pts;
+
+ points_from_shape(pts, gem_shape);
+ for (auto & pt : pts) {
+ EXPECT_TRUE(HullContainsPoint(gem, pt));
+ }
+
+ points_from_shape(pts, diamond_shape);
+ for (auto & pt : pts) {
+ EXPECT_TRUE(HullContainsPoint(diamond, pt));
+ }
+
+ /*EXPECT_FALSE(null.interiorContains(zero));
+ EXPECT_FALSE(point.interiorContains(zero));
+ EXPECT_FALSE(line.interiorContains(zero));
+ EXPECT_FALSE(triangle.interiorContains(zero));
+ EXPECT_FALSE(square.interiorContains(zero));
+ EXPECT_FALSE(line.interiorContains(half));
+ EXPECT_FALSE(triangle.interiorContains(Point(0,0.5)));
+ EXPECT_FALSE(triangle.interiorContains(half));
+ EXPECT_TRUE(square.interiorContains(half));*/
+}
+
+TEST_F(ConvexHullTest, ExtremePoints) {
+ Point zero(0,0);
+ EXPECT_EQ(0., point.top());
+ EXPECT_EQ(0., point.right());
+ EXPECT_EQ(0., point.bottom());
+ EXPECT_EQ(0., point.left());
+ EXPECT_EQ(zero, point.topPoint());
+ EXPECT_EQ(zero, point.rightPoint());
+ EXPECT_EQ(zero, point.bottomPoint());
+ EXPECT_EQ(zero, point.leftPoint());
+
+ // line from 0,0 to 1,0
+ EXPECT_EQ(0., line.top());
+ EXPECT_EQ(1., line.right());
+ EXPECT_EQ(0., line.bottom());
+ EXPECT_EQ(0., line.left());
+ EXPECT_EQ(Point(1,0), line.topPoint());
+ EXPECT_EQ(Point(1,0), line.rightPoint());
+ EXPECT_EQ(Point(0,0), line.bottomPoint());
+ EXPECT_EQ(Point(0,0), line.leftPoint());
+
+ // triangle 0,0 1,0 0,1
+ EXPECT_EQ(0., triangle.top());
+ EXPECT_EQ(1., triangle.right());
+ EXPECT_EQ(1., triangle.bottom());
+ EXPECT_EQ(0., triangle.left());
+ EXPECT_EQ(Point(1,0), triangle.topPoint());
+ EXPECT_EQ(Point(1,0), triangle.rightPoint());
+ EXPECT_EQ(Point(0,1), triangle.bottomPoint());
+ EXPECT_EQ(Point(0,0), triangle.leftPoint());
+
+ // square 0,0 to 1,1
+ EXPECT_EQ(0., square.top());
+ EXPECT_EQ(1., square.right());
+ EXPECT_EQ(1., square.bottom());
+ EXPECT_EQ(0., square.left());
+ EXPECT_EQ(Point(1,0), square.topPoint());
+ EXPECT_EQ(Point(1,1), square.rightPoint());
+ EXPECT_EQ(Point(0,1), square.bottomPoint());
+ EXPECT_EQ(Point(0,0), square.leftPoint());
+
+ EXPECT_EQ(0., gem.top());
+ EXPECT_EQ(5., gem.right());
+ EXPECT_EQ(4., gem.bottom());
+ EXPECT_EQ(0., gem.left());
+ EXPECT_EQ(Point(4,0), gem.topPoint());
+ EXPECT_EQ(Point(5,3), gem.rightPoint());
+ EXPECT_EQ(Point(1,4), gem.bottomPoint());
+ EXPECT_EQ(Point(0,1), gem.leftPoint());
+
+ EXPECT_EQ(0., diamond.top());
+ EXPECT_EQ(6., diamond.right());
+ EXPECT_EQ(6., diamond.bottom());
+ EXPECT_EQ(0., diamond.left());
+ EXPECT_EQ(Point(3,0), diamond.topPoint());
+ EXPECT_EQ(Point(6,3), diamond.rightPoint());
+ EXPECT_EQ(Point(3,6), diamond.bottomPoint());
+ EXPECT_EQ(Point(0,3), diamond.leftPoint());
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/coord-test.cpp b/tests/coord-test.cpp
new file mode 100644
index 0000000..c96b095
--- /dev/null
+++ b/tests/coord-test.cpp
@@ -0,0 +1,90 @@
+/** @file
+ * @brief Unit tests for functions related to Coord.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2014 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <gtest/gtest.h>
+#include <2geom/coord.h>
+#include <climits>
+#include <stdint.h>
+#include <glib.h>
+#include <iostream>
+
+namespace Geom {
+
+TEST(CoordTest, StringRoundtripShortest) {
+ union {
+ uint64_t u;
+ double d;
+ };
+ for (unsigned i = 0; i < 100000; ++i) {
+ u = uint64_t(g_random_int()) | (uint64_t(g_random_int()) << 32);
+ if (!std::isfinite(d)) continue;
+
+ std::string str = format_coord_shortest(d);
+ double x = parse_coord(str);
+ if (x != d) {
+ std::cout << std::endl << d << " -> " << str << " -> " << x << std::endl;
+ }
+ EXPECT_EQ(d, x);
+ }
+}
+
+TEST(CoordTest, StringRoundtripNice) {
+ union {
+ uint64_t u;
+ double d;
+ };
+ for (unsigned i = 0; i < 100000; ++i) {
+ u = uint64_t(g_random_int()) | (uint64_t(g_random_int()) << 32);
+ if (!std::isfinite(d)) continue;
+
+ std::string str = format_coord_nice(d);
+ double x = parse_coord(str);
+ if (x != d) {
+ std::cout << std::endl << d << " -> " << str << " -> " << x << std::endl;
+ }
+ EXPECT_EQ(d, x);
+ }
+}
+
+} // end namespace Geom
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/dependent-project/.gitignore b/tests/dependent-project/.gitignore
new file mode 100644
index 0000000..0bb214c
--- /dev/null
+++ b/tests/dependent-project/.gitignore
@@ -0,0 +1,2 @@
+build-as-subproject
+build-with-find-package \ No newline at end of file
diff --git a/tests/dependent-project/CMakeLists.txt b/tests/dependent-project/CMakeLists.txt
new file mode 100644
index 0000000..c371114
--- /dev/null
+++ b/tests/dependent-project/CMakeLists.txt
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 3.1)
+project(test_dep_2geom CXX C)
+set(CMAKE_CXX_STANDARD 17)
+
+option(2GEOM_AS_SUBPROJECT "include 2geom as subproject" OFF)
+
+if (2GEOM_AS_SUBPROJECT)
+ message("Using 2geom as subdirectory")
+ set(2GEOM_BUILD_SHARED ON CACHE BOOL "Build 2geom shared version")
+ add_subdirectory("../../" 2geom)
+else()
+ message("Using installed 2geom")
+ find_package(2Geom REQUIRED)
+endif()
+
+add_library(my_lib SHARED my_lib.cpp)
+add_executable(main main.cpp)
+target_link_libraries(main my_lib)
+target_link_libraries(my_lib PUBLIC 2Geom::2geom)
+
+install(TARGETS
+ main
+ my_lib
+ RUNTIME DESTINATION bin
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+ )
diff --git a/tests/dependent-project/main.cpp b/tests/dependent-project/main.cpp
new file mode 100644
index 0000000..6b6469c
--- /dev/null
+++ b/tests/dependent-project/main.cpp
@@ -0,0 +1,12 @@
+#include <2geom/2geom.h>
+#include <iostream>
+#include "my_lib.h"
+
+int main() {
+ Geom::Rect rect1(0, 0, 1, 1);
+ Geom::Rect rect2(0.5, 0.5, 1.5, 1.5);
+
+ std::cout << sum_of_three_points(Geom::Point(1, 1), Geom::Point(1, 2), Geom::Point(2, 3));
+
+ return rect1.intersects(rect2) ? 0 : 1;
+}
diff --git a/tests/dependent-project/my_lib.cpp b/tests/dependent-project/my_lib.cpp
new file mode 100644
index 0000000..d5af62a
--- /dev/null
+++ b/tests/dependent-project/my_lib.cpp
@@ -0,0 +1,6 @@
+#include "my_lib.h"
+
+using namespace Geom;
+Point sum_of_three_points(const Point&a, const Point&b, const Point&c){
+ return a+b+c;
+} \ No newline at end of file
diff --git a/tests/dependent-project/my_lib.h b/tests/dependent-project/my_lib.h
new file mode 100644
index 0000000..2278aaf
--- /dev/null
+++ b/tests/dependent-project/my_lib.h
@@ -0,0 +1,4 @@
+#pragma once
+#include <2geom/2geom.h>
+
+Geom::Point sum_of_three_points(const Geom::Point&a, const Geom::Point&b, const Geom::Point&c); \ No newline at end of file
diff --git a/tests/ellipse-test.cpp b/tests/ellipse-test.cpp
new file mode 100644
index 0000000..38eca0e
--- /dev/null
+++ b/tests/ellipse-test.cpp
@@ -0,0 +1,410 @@
+/** @file
+ * @brief Unit tests for Ellipse and related functions
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <iostream>
+#include <glib.h>
+
+#include <2geom/angle.h>
+#include <2geom/ellipse.h>
+#include <2geom/elliptical-arc.h>
+#include <memory>
+
+#include "testing.h"
+
+#ifndef M_SQRT2
+# define M_SQRT2 1.41421356237309504880
+#endif
+
+using namespace Geom;
+
+TEST(EllipseTest, Arcs) {
+ Ellipse e(Point(5,10), Point(5, 10), 0);
+
+ std::unique_ptr<EllipticalArc> arc1(e.arc(Point(5,0), Point(0,0), Point(0,10)));
+
+ EXPECT_EQ(arc1->initialPoint(), Point(5,0));
+ EXPECT_EQ(arc1->finalPoint(), Point(0,10));
+ EXPECT_EQ(arc1->boundsExact(), Rect::from_xywh(0,0,5,10));
+ EXPECT_EQ(arc1->center(), e.center());
+ EXPECT_EQ(arc1->largeArc(), false);
+ EXPECT_EQ(arc1->sweep(), false);
+
+ std::unique_ptr<EllipticalArc> arc1r(e.arc(Point(0,10), Point(0,0), Point(5,0)));
+
+ EXPECT_EQ(arc1r->boundsExact(), arc1->boundsExact());
+ EXPECT_EQ(arc1r->sweep(), true);
+ EXPECT_EQ(arc1r->largeArc(), false);
+
+ std::unique_ptr<EllipticalArc> arc2(e.arc(Point(5,0), Point(10,20), Point(0,10)));
+
+ EXPECT_EQ(arc2->boundsExact(), Rect::from_xywh(0,0,10,20));
+ EXPECT_EQ(arc2->largeArc(), true);
+ EXPECT_EQ(arc2->sweep(), true);
+
+ std::unique_ptr<EllipticalArc> arc2r(e.arc(Point(0,10), Point(10,20), Point(5,0)));
+
+ EXPECT_EQ(arc2r->boundsExact(), arc2->boundsExact());
+ EXPECT_EQ(arc2r->largeArc(), true);
+ EXPECT_EQ(arc2r->sweep(), false);
+
+ // exactly half arc
+ std::unique_ptr<EllipticalArc> arc3(e.arc(Point(5,0), Point(0,10), Point(5,20)));
+
+ EXPECT_EQ(arc3->boundsExact(), Rect::from_xywh(0,0,5,20));
+ EXPECT_EQ(arc3->largeArc(), false);
+ EXPECT_EQ(arc3->sweep(), false);
+
+ // inner point exactly at midpoint between endpoints
+ std::unique_ptr<EllipticalArc> arc4(e.arc(Point(5,0), Point(2.5,5), Point(0,10)));
+
+ EXPECT_EQ(arc4->initialPoint(), Point(5,0));
+ EXPECT_EQ(arc4->finalPoint(), Point(0,10));
+ EXPECT_EQ(arc4->boundsExact(), Rect::from_xywh(0,0,5,10));
+ EXPECT_EQ(arc4->largeArc(), false);
+ EXPECT_EQ(arc4->sweep(), false);
+
+ std::unique_ptr<EllipticalArc> arc4r(e.arc(Point(0,10), Point(2.5,5), Point(5,0)));
+
+ EXPECT_EQ(arc4r->initialPoint(), Point(0,10));
+ EXPECT_EQ(arc4r->finalPoint(), Point(5,0));
+ EXPECT_EQ(arc4r->boundsExact(), Rect::from_xywh(0,0,5,10));
+ EXPECT_EQ(arc4r->largeArc(), false);
+ EXPECT_EQ(arc4r->sweep(), true);
+}
+
+TEST(EllipseTest, AreNear) {
+ Ellipse e1(Point(5.000001,10), Point(5,10), Angle::from_degrees(45));
+ Ellipse e2(Point(5.000000,10), Point(5,10), Angle::from_degrees(225));
+ Ellipse e3(Point(4.999999,10), Point(10,5), Angle::from_degrees(135));
+ Ellipse e4(Point(5.000001,10), Point(10,5), Angle::from_degrees(315));
+
+ EXPECT_TRUE(are_near(e1, e2, 1e-5));
+ EXPECT_TRUE(are_near(e1, e3, 1e-5));
+ EXPECT_TRUE(are_near(e1, e4, 1e-5));
+
+ Ellipse c1(Point(20.000001,35.000001), Point(5.000001,4.999999), Angle::from_degrees(180.00001));
+ Ellipse c2(Point(19.999999,34.999999), Point(4.999999,5.000001), Angle::from_degrees(179.99999));
+ //std::cout << c1 << "\n" << c2 << std::endl;
+ EXPECT_TRUE(are_near(c1, c2, 2e-5));
+
+ EXPECT_FALSE(are_near(c1, e1, 1e-5));
+ EXPECT_FALSE(are_near(c2, e1, 1e-5));
+ EXPECT_FALSE(are_near(c1, e2, 1e-5));
+ EXPECT_FALSE(are_near(c2, e2, 1e-5));
+ EXPECT_FALSE(are_near(c1, e3, 1e-5));
+ EXPECT_FALSE(are_near(c2, e3, 1e-5));
+ EXPECT_FALSE(are_near(c1, e4, 1e-5));
+ EXPECT_FALSE(are_near(c2, e4, 1e-5));
+}
+
+TEST(EllipseTest, Transformations) {
+ Ellipse e(Point(5,10), Point(5,10), Angle::from_degrees(45));
+
+ Ellipse er = e * Rotate::around(Point(5,10), Angle::from_degrees(45));
+ Ellipse ercmp(Point(5,10), Point(5,10), Angle::from_degrees(90));
+ //std::cout << e << "\n" << er << "\n" << ercmp << std::endl;
+ EXPECT_TRUE(are_near(er, ercmp, 1e-12));
+
+ Ellipse eflip = e * Affine(Scale(-1,1));
+ Ellipse eflipcmp(Point(-5, 10), Point(5,10), Angle::from_degrees(135));
+ EXPECT_TRUE(are_near(eflip, eflipcmp, 1e-12));
+}
+
+TEST(EllipseTest, TimeAt) {
+ Ellipse e(Point(4, 17), Point(22, 34), 2);
+
+ for (unsigned i = 0; i < 100; ++i) {
+ Coord t = g_random_double_range(0, 2*M_PI);
+ Point p = e.pointAt(t);
+ Coord t2 = e.timeAt(p);
+ EXPECT_FLOAT_EQ(t, t2);
+ }
+}
+
+TEST(EllipseTest, LineIntersection) {
+ Ellipse e(Point(0, 0), Point(3, 2), 0);
+ Line l(Point(0, -2), Point(1, 0));
+
+ std::vector<ShapeIntersection> xs = e.intersect(l);
+
+ ASSERT_EQ(xs.size(), 2ul);
+
+ // due to numeric imprecision when evaluating Ellipse,
+ // the points may deviate by around 2e-16
+ EXPECT_NEAR(xs[0].point()[X], 0, 1e-15);
+ EXPECT_NEAR(xs[0].point()[Y], -2, 1e-15);
+ EXPECT_NEAR(xs[1].point()[X], 9./5, 1e-15);
+ EXPECT_NEAR(xs[1].point()[Y], 8./5, 1e-15);
+
+ EXPECT_intersections_valid(e, l, xs, 1e-15);
+
+ // Test with a degenerate ellipse
+ auto degen = Ellipse({0, 0}, {3, 2}, 0);
+ degen *= Scale(1.0, 0.0); // Squash to the X-axis interval [-3, 3].
+
+ g_random_set_seed(0xCAFECAFE);
+ // Intersect with a line
+ for (size_t _ = 0; _ < 10'000; _++) {
+ auto line = Line(Point(g_random_double_range(-3.0, 3.0), g_random_double_range(-3.0, -1.0)),
+ Point(g_random_double_range(-3.0, 3.0), g_random_double_range(1.0, 3.0)));
+ auto xings = degen.intersect(line);
+ EXPECT_EQ(xings.size(), 2u);
+ EXPECT_intersections_valid(degen, line, xings, 1e-14);
+ }
+ // Intersect with another, non-degenerate ellipse
+ for (size_t _ = 0; _ < 10'000; _++) {
+ auto other = Ellipse(Point(g_random_double_range(-1.0, 1.0), g_random_double_range(-1.0, 1.0)),
+ Point(g_random_double_range(1.0, 2.0), g_random_double_range(1.0, 3.0)), 0);
+ auto xings = degen.intersect(other);
+ EXPECT_intersections_valid(degen, other, xings, 1e-14);
+ }
+ // Intersect with another ellipse which is also degenerate
+ for (size_t _ = 0; _ < 10'000; _++) {
+ auto other = Ellipse({0, 0}, {1, 1}, 0); // Unit circle
+ other *= Scale(0.0, g_random_double_range(0.5, 4.0)); // Squash to Y axis
+ other *= Rotate(g_random_double_range(-1.5, 1.5)); // Rotate a little (still passes through the origin)
+ other *= Translate(g_random_double_range(-2.9, 2.9), 0.0);
+ auto xings = degen.intersect(other);
+ EXPECT_EQ(xings.size(), 4u);
+ EXPECT_intersections_valid(degen, other, xings, 1e-14);
+ }
+}
+
+TEST(EllipseTest, EllipseIntersection) {
+ Ellipse e1;
+ Ellipse e2;
+ std::vector<ShapeIntersection> xs;
+
+ e1.set(Point(300, 300), Point(212, 70), -0.785);
+ e2.set(Point(250, 300), Point(230, 90), 1.321);
+ xs = e1.intersect(e2);
+ EXPECT_EQ(xs.size(), 4ul);
+ EXPECT_intersections_valid(e1, e2, xs, 4e-10);
+
+ e1.set(Point(0, 0), Point(1, 1), 0);
+ e2.set(Point(0, 1), Point(1, 1), 0);
+ xs = e1.intersect(e2);
+ EXPECT_EQ(xs.size(), 2ul);
+ EXPECT_intersections_valid(e1, e2, xs, 1e-10);
+
+ e1.set(Point(0, 0), Point(1, 1), 0);
+ e2.set(Point(1, 0), Point(1, 1), 0);
+ xs = e1.intersect(e2);
+ EXPECT_EQ(xs.size(), 2ul);
+ EXPECT_intersections_valid(e1, e2, xs, 1e-10);
+
+ // === Test detection of external tangency between ellipses ===
+ // Perpendicular major axes
+ e1.set({0, 0}, {5, 3}, 0); // rightmost point (5, 0)
+ e2.set({6, 0}, {1, 2}, 0); // leftmost point (5, 0)
+ xs = e1.intersect(e2);
+ ASSERT_GT(xs.size(), 0);
+ EXPECT_intersections_valid(e1, e2, xs, 1e-10);
+ EXPECT_TRUE(are_near(xs[0].point(), Point(5, 0)));
+
+ // Collinear major axes
+ e1.set({30, 0}, {9, 1}, 0); // leftmost point (21, 0)
+ e2.set({18, 0}, {3, 2}, 0); // rightmost point (21, 0)
+ xs = e1.intersect(e2);
+ ASSERT_GT(xs.size(), 0);
+ EXPECT_intersections_valid(e1, e2, xs, 1e-10);
+ EXPECT_TRUE(are_near(xs[0].point(), Point(21, 0)));
+
+ // Circles not aligned to an axis (Pythagorean triple: 3^2 + 4^2 == 5^2)
+ e1.set({0, 0}, {3, 3}, 0); // radius 3
+ e2.set({3, 4}, {2, 2}, 0); // radius 2
+ // We know 2 + 3 == 5 == distance((0, 0), (3, 4)) so there's an external tangency
+ // between these circles, at a point at distance 3 from the origin, on the line x = 0.75 y.
+ xs = e1.intersect(e2);
+ ASSERT_GT(xs.size(), 0);
+ EXPECT_intersections_valid(e1, e2, xs, 1e-6);
+
+ // === Test the detection of internal tangency between ellipses ===
+ // Perpendicular major axes
+ e1.set({0, 0}, {8, 17}, 0); // rightmost point (8, 0)
+ e2.set({6, 0}, {2, 1}, 0); // rightmost point (8, 0)
+ xs = e1.intersect(e2);
+ ASSERT_GT(xs.size(), 0);
+ EXPECT_intersections_valid(e1, e2, xs, 1e-10);
+ EXPECT_TRUE(are_near(xs[0].point(), Point(8, 0)));
+
+ // Collinear major axes
+ e1.set({30, 0}, {9, 5}, 0); // rightmost point (39, 0)
+ e2.set({36, 0}, {3, 1}, 0); // rightmost point (39, 0)
+ xs = e1.intersect(e2);
+ ASSERT_GT(xs.size(), 0);
+ EXPECT_intersections_valid(e1, e2, xs, 1e-6);
+ EXPECT_TRUE(are_near(xs[0].point(), Point(39, 0)));
+
+ // Circles not aligned to an axis (Pythagorean triple: 3^2 + 4^2 == 5^2)
+ e1.set({4, 3}, {5, 5}, 0); // Passes through (0, 0), center on the line y = 0.75 x
+ e2.set({8, 6}, {10, 10}, 0); // Also passes through (0, 0), center on the same line.
+ xs = e1.intersect(e2);
+ ASSERT_GT(xs.size(), 0);
+ EXPECT_intersections_valid(e1, e2, xs, 1e-6);
+ EXPECT_TRUE(are_near(xs[0].point(), Point(0, 0)));
+}
+
+TEST(EllipseTest, BezierIntersection) {
+ Ellipse e(Point(300, 300), Point(212, 70), -3.926);
+ D2<Bezier> b(Bezier(100, 300, 100, 500), Bezier(100, 100, 500, 500));
+
+ std::vector<ShapeIntersection> xs = e.intersect(b);
+
+ EXPECT_EQ(xs.size(), 2ul);
+ EXPECT_intersections_valid(e, b, xs, 6e-12);
+}
+
+TEST(EllipseTest, Coefficients) {
+ std::vector<Ellipse> es;
+ es.emplace_back(Point(-15,25), Point(10,15), Angle::from_degrees(45).radians0());
+ es.emplace_back(Point(-10,33), Point(40,20), M_PI);
+ es.emplace_back(Point(10,-33), Point(40,20), Angle::from_degrees(135).radians0());
+ es.emplace_back(Point(-10,-33), Point(50,10), Angle::from_degrees(330).radians0());
+
+ for (auto & i : es) {
+ Coord a, b, c, d, e, f;
+ i.coefficients(a, b, c, d, e, f);
+ Ellipse te(a, b, c, d, e, f);
+ EXPECT_near(i, te, 1e-10);
+ for (Coord t = -5; t < 5; t += 0.125) {
+ Point p = i.pointAt(t);
+ Coord eq = a*p[X]*p[X] + b*p[X]*p[Y] + c*p[Y]*p[Y]
+ + d*p[X] + e*p[Y] + f;
+ EXPECT_NEAR(eq, 0, 1e-10);
+ }
+ }
+}
+
+TEST(EllipseTest, UnitCircleTransform) {
+ std::vector<Ellipse> es;
+ es.emplace_back(Point(-15,25), Point(10,15), Angle::from_degrees(45));
+ es.emplace_back(Point(-10,33), Point(40,20), M_PI);
+ es.emplace_back(Point(10,-33), Point(40,20), Angle::from_degrees(135));
+ es.emplace_back(Point(-10,-33), Point(50,10), Angle::from_degrees(330));
+
+ for (auto & e : es) {
+ EXPECT_near(e.unitCircleTransform() * e.inverseUnitCircleTransform(), Affine::identity(), 1e-8);
+
+ for (Coord t = -1; t < 10; t += 0.25) {
+ Point p = e.pointAt(t);
+ p *= e.inverseUnitCircleTransform();
+ EXPECT_near(p.length(), 1., 1e-10);
+ p *= e.unitCircleTransform();
+ EXPECT_near(e.pointAt(t), p, 1e-10);
+ }
+ }
+}
+
+TEST(EllipseTest, PointAt) {
+ Ellipse a(Point(0,0), Point(10,20), 0);
+ EXPECT_near(a.pointAt(0), Point(10,0), 1e-10);
+ EXPECT_near(a.pointAt(M_PI/2), Point(0,20), 1e-10);
+ EXPECT_near(a.pointAt(M_PI), Point(-10,0), 1e-10);
+ EXPECT_near(a.pointAt(3*M_PI/2), Point(0,-20), 1e-10);
+
+ Ellipse b(Point(0,0), Point(10,20), M_PI/2);
+ EXPECT_near(b.pointAt(0), Point(0,10), 1e-10);
+ EXPECT_near(b.pointAt(M_PI/2), Point(-20,0), 1e-10);
+ EXPECT_near(b.pointAt(M_PI), Point(0,-10), 1e-10);
+ EXPECT_near(b.pointAt(3*M_PI/2), Point(20,0), 1e-10);
+}
+
+TEST(EllipseTest, UnitTangentAt) {
+ Ellipse a(Point(14,-7), Point(20,10), 0);
+ Ellipse b(Point(-77,23), Point(40,10), Angle::from_degrees(45));
+
+ EXPECT_near(a.unitTangentAt(0), Point(0,1), 1e-12);
+ EXPECT_near(a.unitTangentAt(M_PI/2), Point(-1,0), 1e-12);
+ EXPECT_near(a.unitTangentAt(M_PI), Point(0,-1), 1e-12);
+ EXPECT_near(a.unitTangentAt(3*M_PI/2), Point(1,0), 1e-12);
+
+ EXPECT_near(b.unitTangentAt(0), Point(-M_SQRT2/2, M_SQRT2/2), 1e-12);
+ EXPECT_near(b.unitTangentAt(M_PI/2), Point(-M_SQRT2/2, -M_SQRT2/2), 1e-12);
+ EXPECT_near(b.unitTangentAt(M_PI), Point(M_SQRT2/2, -M_SQRT2/2), 1e-12);
+ EXPECT_near(b.unitTangentAt(3*M_PI/2), Point(M_SQRT2/2, M_SQRT2/2), 1e-12);
+}
+
+TEST(EllipseTest, Bounds)
+{
+ // Create example ellipses
+ std::vector<Ellipse> es;
+ es.emplace_back(Point(-15,25), Point(10,15), Angle::from_degrees(45));
+ es.emplace_back(Point(-10,33), Point(40,20), M_PI);
+ es.emplace_back(Point(10,-33), Point(40,20), Angle::from_degrees(111));
+ es.emplace_back(Point(-10,-33), Point(50,10), Angle::from_degrees(222));
+
+ // for reproducibility
+ g_random_set_seed(1234);
+
+ for (auto & e : es) {
+ Rect r = e.boundsExact();
+ Rect f = e.boundsFast();
+ for (unsigned j = 0; j < 10000; ++j) {
+ Coord t = g_random_double_range(-M_PI, M_PI);
+ auto const p = e.pointAt(t);
+ EXPECT_TRUE(r.contains(p));
+ EXPECT_TRUE(f.contains(p));
+ }
+ }
+
+ Ellipse e(Point(0,0), Point(10, 10), M_PI);
+ Rect bounds = e.boundsExact();
+ Rect coarse = e.boundsFast();
+ EXPECT_EQ(bounds, Rect(Point(-10,-10), Point(10,10)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(0)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(M_PI/2)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(M_PI)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(3*M_PI/2)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(2*M_PI)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(0)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(M_PI/2)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(M_PI)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(3*M_PI/2)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(2*M_PI)));
+
+ e = Ellipse(Point(0,0), Point(10, 10), M_PI/2);
+ bounds = e.boundsExact();
+ coarse = e.boundsFast();
+ EXPECT_EQ(bounds, Rect(Point(-10,-10), Point(10,10)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(0)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(M_PI/2)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(M_PI)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(3*M_PI/2)));
+ EXPECT_TRUE(bounds.contains(e.pointAt(2*M_PI)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(0)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(M_PI/2)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(M_PI)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(3*M_PI/2)));
+ EXPECT_TRUE(coarse.contains(e.pointAt(2*M_PI)));
+}
diff --git a/tests/elliptical-arc-test.cpp b/tests/elliptical-arc-test.cpp
new file mode 100644
index 0000000..1f6eff7
--- /dev/null
+++ b/tests/elliptical-arc-test.cpp
@@ -0,0 +1,275 @@
+/** @file
+ * @brief Unit tests for EllipticalArc.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include "testing.h"
+#include <2geom/elliptical-arc.h>
+#include <glib.h>
+
+using namespace Geom;
+
+TEST(EllipticalArcTest, PointAt) {
+ EllipticalArc a(Point(0,0), Point(10,20), M_PI/2, false, true, Point(-40,0));
+ EXPECT_near(a.pointAt(0), a.initialPoint(), 1e-14);
+ EXPECT_near(a.pointAt(1), a.finalPoint(), 1e-14);
+ EXPECT_near(a.pointAt(0.5), Point(-20,10), 1e-14);
+
+ EllipticalArc b(Point(0,0), Point(10,20), 0, false, true, Point(-40,0));
+ EXPECT_near(b.pointAt(0), b.initialPoint(), 1e-14);
+ EXPECT_near(b.pointAt(1), b.finalPoint(), 1e-14);
+ EXPECT_near(b.pointAt(0.5), Point(-20,40), 1e-14);
+
+ EllipticalArc c(Point(200,0), Point(40,20), Angle::from_degrees(90), false, false, Point(200,100));
+ EXPECT_near(c.pointAt(0), c.initialPoint(), 1e-13);
+ EXPECT_near(c.pointAt(1), c.finalPoint(), 1e-13);
+ EXPECT_near(c.pointAt(0.5), Point(175, 50), 1e-13);
+}
+
+TEST(EllipticalArc, Transform) {
+ EllipticalArc a(Point(0,0), Point(10,20), M_PI/2, false, true, Point(-40,0));
+ EllipticalArc b(Point(-40,0), Point(10,20), M_PI/2, false, true, Point(0,0));
+ EllipticalArc c = a;
+ Affine m = Rotate::around(Point(-20,0), M_PI);
+ c.transform(m);
+
+ for (unsigned i = 0; i <= 100; ++i) {
+ Coord t = i/100.;
+ EXPECT_near(c.pointAt(t), b.pointAt(t), 1e-12);
+ EXPECT_near(a.pointAt(t)*m, c.pointAt(t), 1e-12);
+ }
+}
+
+TEST(EllipticalArcTest, Duplicate) {
+ EllipticalArc a(Point(0,0), Point(10,20), M_PI/2, true, false, Point(-40,0));
+ EllipticalArc *b = static_cast<EllipticalArc*>(a.duplicate());
+ EXPECT_EQ(a, *b);
+ delete b;
+}
+
+TEST(EllipticalArcTest, LineSegmentIntersection) {
+ std::vector<CurveIntersection> r1;
+ EllipticalArc a3(Point(0,0), Point(5,1.5), 0, true, true, Point(0,2));
+ LineSegment ls(Point(0,5), Point(7,-3));
+ r1 = a3.intersect(ls);
+ EXPECT_EQ(r1.size(), 2u);
+ EXPECT_intersections_valid(a3, ls, r1, 1e-10);
+
+ g_random_set_seed(0xB747A380);
+ // Test with randomized arcs and segments.
+ for (size_t _ = 0; _ < 10'000; _++) {
+ auto arc = EllipticalArc({g_random_double_range(1.0, 5.0), 0.0},
+ {g_random_double_range(6.0, 8.0), g_random_double_range(2.0, 7.0)},
+ g_random_double_range(-0.5, 0.5), true, g_random_boolean(),
+ {g_random_double_range(-5.0, -1.0), 0.0});
+ Coord x = g_random_double_range(15, 30);
+ Coord y = g_random_double_range(10, 20);
+ auto seg = LineSegment(Point(-x, y), Point(x, -y));
+ auto xings = arc.intersect(seg);
+ EXPECT_EQ(xings.size(), 1u);
+ EXPECT_intersections_valid(arc, seg, xings, 1e-12);
+ }
+
+ // Test with degenerate arcs
+ EllipticalArc x_squash_pos{{3.0, 0.0}, {3.0, 2.0}, 0, true, true, {-3.0, 0.0}};
+ EllipticalArc x_squash_neg{{3.0, 0.0}, {3.0, 2.0}, 0, true, false, {-3.0, 0.0}};
+ auto const squash_to_x = Scale(1.0, 0.0);
+ x_squash_pos *= squash_to_x; // squash to X axis interval [-3, 3].
+ x_squash_neg *= squash_to_x;
+
+ for (size_t _ = 0; _ < 10'000; _++) {
+ auto seg = LineSegment(Point(g_random_double_range(-3.0, 3.0), g_random_double_range(-3.0, -1.0)),
+ Point(g_random_double_range(-3.0, 3.0), g_random_double_range(1.0, 3.0)));
+ auto xings = x_squash_pos.intersect(seg);
+ EXPECT_EQ(xings.size(), 1u);
+ EXPECT_intersections_valid(x_squash_pos, seg, xings, 1e-12);
+
+ std::unique_ptr<Curve> rev{x_squash_pos.reverse()};
+ xings = rev->intersect(seg);
+ EXPECT_EQ(xings.size(), 1u);
+ EXPECT_intersections_valid(*rev, seg, xings, 1e-12);
+
+ xings = x_squash_neg.intersect(seg);
+ EXPECT_EQ(xings.size(), 1u);
+ EXPECT_intersections_valid(x_squash_neg, seg, xings, 1e-12);
+
+ rev.reset(x_squash_neg.reverse());
+ xings = rev->intersect(seg);
+ EXPECT_EQ(xings.size(), 1u);
+ EXPECT_intersections_valid(*rev, seg, xings, 1e-12);
+ }
+
+ // Now test with an arc squashed to the Y-axis.
+ EllipticalArc y_squash_pos{{0.0, -2.0}, {3.0, 2.0}, 0, true, true, {0.0, 2.0}};
+ EllipticalArc y_squash_neg{{0.0, -2.0}, {3.0, 2.0}, 0, true, false, {0.0, 2.0}};
+ auto const squash_to_y = Scale(0.0, 1.0);
+ y_squash_pos *= squash_to_y; // Y-axis interval [-2, 2].
+ y_squash_neg *= squash_to_y;
+
+ for (size_t _ = 0; _ < 10'000; _++) {
+ auto seg = LineSegment(Point(g_random_double_range(-3.0, -1.0), g_random_double_range(-2.0, 2.0)),
+ Point(g_random_double_range(1.0, 3.0), g_random_double_range(-2.0, 2.0)));
+ auto xings = y_squash_pos.intersect(seg, 1e-10);
+ EXPECT_EQ(xings.size(), 1u);
+ EXPECT_intersections_valid(y_squash_pos, seg, xings, 1e-12);
+
+ std::unique_ptr<Curve> rev{y_squash_pos.reverse()};
+ xings = rev->intersect(seg, 1e-12);
+ EXPECT_EQ(xings.size(), 1u);
+ EXPECT_intersections_valid(*rev, seg, xings, 1e-12);
+
+ xings = y_squash_neg.intersect(seg, 1e-12);
+ EXPECT_EQ(xings.size(), 1u);
+ EXPECT_intersections_valid(y_squash_neg, seg, xings, 1e-12);
+
+ rev.reset(y_squash_neg.reverse());
+ xings = rev->intersect(seg, 1e-12);
+ EXPECT_EQ(xings.size(), 1u);
+ EXPECT_intersections_valid(*rev, seg, xings, 1e-12);
+ }
+
+ // Test whether the coincidence between the common endpoints of an
+ // arc and a segment is correctly detected as an intersection.
+ {
+ Point const from{1, 0};
+ Point const to{0.30901699437494745, 0.9510565162951535};
+ auto arc = EllipticalArc(from, {1, 1}, 0, false, true, to);
+ auto seg = LineSegment({0, 0}, to);
+ auto xings = arc.intersect(seg);
+ ASSERT_EQ(xings.size(), 1);
+ EXPECT_TRUE(are_near(xings[0].point(), to, 1e-12));
+ EXPECT_TRUE(are_near(xings[0].first, 1.0, 1e-24));
+ EXPECT_TRUE(are_near(xings[0].second, 1.0, 1e-24));
+
+ auto seg2 = LineSegment(Point{1, 1}, from);
+ xings = arc.intersect(seg2);
+ ASSERT_EQ(xings.size(), 1);
+ EXPECT_TRUE(are_near(xings[0].point(), from, 1e-12));
+ EXPECT_TRUE(are_near(xings[0].first, 0.0, 1e-24));
+ EXPECT_TRUE(are_near(xings[0].second, 1.0, 1e-24));
+ }
+}
+
+TEST(EllipticalArcTest, ArcIntersection) {
+ std::vector<CurveIntersection> r1, r2;
+
+ EllipticalArc a1(Point(0,0), Point(6,3), 0.1, false, false, Point(10,0));
+ EllipticalArc a2(Point(0,2), Point(6,3), -0.1, false, true, Point(10,2));
+ r1 = a1.intersect(a2);
+ EXPECT_EQ(r1.size(), 2u);
+ EXPECT_intersections_valid(a1, a2, r1, 1e-10);
+
+ EllipticalArc a3(Point(0,0), Point(5,1.5), 0, true, true, Point(0,2));
+ EllipticalArc a4(Point(3,5), Point(5,1.5), M_PI/2, true, true, Point(5,0));
+ r2 = a3.intersect(a4);
+ EXPECT_EQ(r2.size(), 3u);
+ EXPECT_intersections_valid(a3, a4, r2, 1e-10);
+
+ // Make sure intersections are found between two identical arcs on the unit circle.
+ EllipticalArc const upper(Point(1, 0), Point(1, 1), 0, true, true, Point(-1, 0));
+ auto self_intersect = upper.intersect(upper);
+ EXPECT_EQ(self_intersect.size(), 2u);
+
+ // Make sure intersections are found between overlapping arcs.
+ EllipticalArc const right(Point(0, -1), Point(1, 1), 0, true, true, Point(0, 1));
+ auto quartering_overlap_xings = right.intersect(upper);
+ EXPECT_EQ(quartering_overlap_xings.size(), 2u);
+
+ // Make sure intersecections are found between an arc and its sub-arc.
+ EllipticalArc const middle(upper.pointAtAngle(0.25 * M_PI), Point(1, 1), 0, true, true, upper.pointAtAngle(-0.25 * M_PI));
+ EXPECT_EQ(middle.intersect(upper).size(), 2u);
+
+ // Make sure intersections are NOT found between non-overlapping sub-arcs of the same circle.
+ EllipticalArc const arc1{Point(1, 0), Point(1, 1), 0, true, true, Point(0, 1)};
+ EllipticalArc const arc2{Point(-1, 0), Point(1, 1), 0, true, true, Point(0, -1)};
+ EXPECT_EQ(arc1.intersect(arc2).size(), 0u);
+
+ // Overlapping sub-arcs but on an Ellipse with different rays.
+ EllipticalArc const eccentric{Point(2, 0), Point(2, 1), 0, true, true, Point(-2, 0)};
+ EllipticalArc const subarc{eccentric.pointAtAngle(0.8), Point(2, 1), 0, true, true, eccentric.pointAtAngle(2)};
+ EXPECT_EQ(eccentric.intersect(subarc).size(), 2u);
+
+ // Check intersection times for two touching arcs.
+ EllipticalArc const lower{Point(-1, 0), Point(1, 1), 0, false, true, Point(0, -1)};
+ auto expected_neg_x = upper.intersect(lower);
+ ASSERT_EQ(expected_neg_x.size(), 1);
+ auto const &left_pt = expected_neg_x[0];
+ EXPECT_EQ(left_pt.point(), Point(-1, 0));
+ EXPECT_DOUBLE_EQ(left_pt.first, 1.0); // Expect (-1, 0) reached at the end of upper
+ EXPECT_DOUBLE_EQ(left_pt.second, 0.0); // Expect (-1, 0) passed at the start of lower
+}
+
+TEST(EllipticalArcTest, BezierIntersection) {
+ std::vector<CurveIntersection> r1, r2;
+
+ EllipticalArc a3(Point(0,0), Point(1.5,5), M_PI/2, true, true, Point(0,2));
+ CubicBezier bez1(Point(0,3), Point(7,3), Point(0,-1), Point(7,-1));
+ r1 = a3.intersect(bez1);
+ EXPECT_EQ(r1.size(), 2u);
+ EXPECT_intersections_valid(a3, bez1, r1, 1e-10);
+
+ EllipticalArc a4(Point(3,5), Point(5,1.5), 3*M_PI/2, true, true, Point(5,5));
+ CubicBezier bez2(Point(0,5), Point(10,-4), Point(10,5), Point(0,-4));
+ r2 = a4.intersect(bez2);
+ EXPECT_EQ(r2.size(), 4u);
+ EXPECT_intersections_valid(a4, bez2, r2, 1e-10);
+}
+
+TEST(EllipticalArcTest, ExpandToTransformedTest)
+{
+ auto test_curve = [] (EllipticalArc const &c) {
+ constexpr int N = 50;
+ for (int i = 0; i < N; i++) {
+ auto angle = 2 * M_PI * i / N;
+ auto transform = Affine(Rotate(angle)) * Scale(0.9, 1.2);
+
+ auto copy = std::unique_ptr<Curve>(c.duplicate());
+ *copy *= transform;
+ auto box1 = copy->boundsExact();
+
+ auto pt = c.initialPoint() * transform;
+ auto box2 = Rect(pt, pt);
+ c.expandToTransformed(box2, transform);
+
+ for (auto i : { X, Y }) {
+ EXPECT_NEAR(box1[i].min(), box2[i].min(), 2e-15);
+ EXPECT_NEAR(box1[i].max(), box2[i].max(), 2e-15);
+ }
+ }
+ };
+
+ test_curve(EllipticalArc(Point(0, 0), 1.0, 2.0, 0.0, false, false, Point(1, 1)));
+ test_curve(EllipticalArc(Point(0, 0), 3.0, 2.0, M_PI / 6, false, false, Point(1, 1)));
+ test_curve(EllipticalArc(Point(0, 0), 1.0, 2.0, M_PI / 5, true, true, Point(1, 1)));
+ test_curve(EllipticalArc(Point(1, 0), 1.0, 0.0, M_PI / 5, false, false, Point(1, 1)));
+ test_curve(EllipticalArc(Point(1, 0), 0.0, 0.0, 0.0, false, false, Point(2, 0)));
+ test_curve(EllipticalArc(Point(1, 0), 0.0, 0.0, 0.0, false, false, Point(1, 0)));
+}
diff --git a/tests/implicitization-test.cpp b/tests/implicitization-test.cpp
new file mode 100644
index 0000000..bfc4c58
--- /dev/null
+++ b/tests/implicitization-test.cpp
@@ -0,0 +1,130 @@
+/*
+ * Test program for implicitization routines
+ *
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2008 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+
+
+#include <2geom/symbolic/implicit.h>
+
+#include "pick.h"
+
+#include <iostream>
+
+
+
+
+void print_basis(Geom::SL::basis_type const& b)
+{
+ for (size_t i= 0; i < 2; ++i)
+ {
+ for (size_t j= 0; j < 3; ++j)
+ {
+ std::cout << "b[" << i << "][" << j << "] = " << b[i][j] << "\n";
+ }
+ }
+}
+
+
+
+
+int main( int argc, char * argv[] )
+{
+ // degree of polinomial parametrization
+ // warning: not set N to a value greater than 20!
+ // (10 in case you don't utilize the micro-basis)
+ // determinant computation becomes very expensive
+ unsigned int N = 4;
+ // max modulus of polynomial coefficients
+ unsigned int M = 1000;
+
+ if (argc > 1)
+ N = std::atoi(argv[1]);
+ if (argc > 2)
+ M = std::atoi(argv[2]);
+
+ Geom::SL::MVPoly1 f, g;
+ Geom::SL::basis_type b;
+ Geom::SL::MVPoly3 p, q;
+ Geom::SL::Matrix<Geom::SL::MVPoly2> B;
+ Geom::SL::MVPoly2 r;
+
+ // generate two univariate polynomial with degree N
+ // and coeffcient in the range [-M, M]
+ f = pick_multipoly_max<1>(N, M);
+ g = pick_multipoly_max<1>(N, M);
+
+ std::cout << "parametrization: \n";
+ std::cout << "f = " << f << std::endl;
+ std::cout << "g = " << g << "\n\n";
+
+ // computes the micro-basis
+ microbasis(b, f, g);
+ // in case you want utilize directly the initial basis
+ // you should uncomment the next row and comment
+ // the microbasis function call
+ //make_initial_basis(b, f, g);
+
+ std::cout << "generators in vector form : \n";
+ print_basis(b);
+ std::cout << std::endl;
+
+ // micro-basis generators
+ basis_to_poly(p, b[0]);
+ basis_to_poly(q, b[1]);
+
+ std::cout << "generators as polynomial in R[t,x,y] : \n";
+ std::cout << "p = " << p << std::endl;
+ std::cout << "q = " << q << "\n\n";
+
+ // make up the Bezout matrix and compute the determinant
+ B = make_bezout_matrix(p, q);
+ r = determinant_minor(B);
+ r.normalize();
+
+ std::cout << "Bezout matrix: (entries are bivariate polynomials) \n";
+ std::cout << "B = " << B << "\n\n";
+ std::cout << "determinant: \n";
+ std::cout << "r(x, y) = " << r << "\n\n";
+
+ return EXIT_SUCCESS;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/intersection-graph-test.cpp b/tests/intersection-graph-test.cpp
new file mode 100644
index 0000000..19fb25c
--- /dev/null
+++ b/tests/intersection-graph-test.cpp
@@ -0,0 +1,266 @@
+/** @file
+ * @brief Unit tests for PathIntersectionGraph, aka Boolean operations.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include "testing.h"
+#include <iostream>
+
+#include <2geom/intersection-graph.h>
+#include <2geom/pathvector.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/svg-path-writer.h>
+#include <glib.h>
+
+using namespace std;
+using namespace Geom;
+
+Path string_to_path(const char* s) {
+ PathVector pv = parse_svg_path(s);
+ assert(pv.size() == 1u);
+ return pv[0];
+}
+
+enum Operation {
+ UNION,
+ INTERSECTION,
+ XOR,
+ A_MINUS_B,
+ B_MINUS_A
+};
+
+class IntersectionGraphTest : public ::testing::Test {
+protected:
+ IntersectionGraphTest() {
+ rectangle = string_to_path("M 0,0 L 5,0 5,8 0,8 Z");
+ bigrect = string_to_path("M -3,-4 L 7,-4 7,12 -3,12 Z");
+ bigh = string_to_path("M 2,-3 L 3,-2 1,2 3,4 4,2 6,3 2,11 0,10 2,5 1,4 -1,6 -2,5 Z");
+ smallrect = string_to_path("M 7,4 L 9,4 9,7 7,7 Z");
+ g_random_set_seed(2345);
+ }
+
+ void checkRandomPoints(PathVector const &a, PathVector const &b, PathVector const &result,
+ Operation op, unsigned npts = 5000)
+ {
+ Rect bounds = *(a.boundsFast() | b.boundsFast());
+ for (unsigned i = 0; i < npts; ++i) {
+ Point p;
+ p[X] = g_random_double_range(bounds[X].min(), bounds[X].max());
+ p[Y] = g_random_double_range(bounds[Y].min(), bounds[Y].max());
+ bool in_a = a.winding(p) % 2;
+ bool in_b = b.winding(p) % 2;
+ bool in_res = result.winding(p) % 2;
+
+ switch (op) {
+ case UNION:
+ EXPECT_EQ(in_res, in_a || in_b);
+ break;
+ case INTERSECTION:
+ EXPECT_EQ(in_res, in_a && in_b);
+ break;
+ case XOR:
+ EXPECT_EQ(in_res, in_a ^ in_b);
+ break;
+ case A_MINUS_B:
+ EXPECT_EQ(in_res, in_a && !in_b);
+ break;
+ case B_MINUS_A:
+ EXPECT_EQ(in_res, !in_a && in_b);
+ break;
+ }
+ }
+ }
+
+ Path rectangle, bigrect, bigh, smallrect;
+};
+
+TEST_F(IntersectionGraphTest, Union) {
+ PathIntersectionGraph graph(rectangle, bigh);
+ //std::cout << graph << std::endl;
+ PathVector r = graph.getUnion();
+ EXPECT_EQ(r.size(), 1u);
+ EXPECT_EQ(r.curveCount(), 19u);
+
+ checkRandomPoints(rectangle, bigh, r, UNION);
+
+ /*SVGPathWriter wr;
+ wr.feed(r);
+ std::cout << wr.str() << std::endl;*/
+}
+
+TEST_F(IntersectionGraphTest, DisjointUnion) {
+ PathIntersectionGraph graph(rectangle, smallrect);
+
+ PathVector r = graph.getUnion();
+ EXPECT_EQ(r.size(), 2u);
+ checkRandomPoints(rectangle, smallrect, r, UNION);
+}
+
+TEST_F(IntersectionGraphTest, CoverUnion) {
+ PathIntersectionGraph graph(bigrect, bigh);
+ PathVector r = graph.getUnion();
+ EXPECT_EQ(r.size(), 1u);
+ EXPECT_EQ(r, bigrect);
+}
+
+TEST_F(IntersectionGraphTest, Subtraction) {
+ PathIntersectionGraph graph(rectangle, bigh);
+ PathVector a = graph.getAminusB();
+ EXPECT_EQ(a.size(), 4u);
+ EXPECT_EQ(a.curveCount(), 17u);
+ checkRandomPoints(rectangle, bigh, a, A_MINUS_B);
+
+ PathVector b = graph.getBminusA();
+ EXPECT_EQ(b.size(), 4u);
+ EXPECT_EQ(b.curveCount(), 15u);
+ checkRandomPoints(rectangle, bigh, b, B_MINUS_A);
+
+ PathVector x = graph.getXOR();
+ EXPECT_EQ(x.size(), 8u);
+ EXPECT_EQ(x.curveCount(), 32u);
+ checkRandomPoints(rectangle, bigh, x, XOR);
+}
+
+TEST_F(IntersectionGraphTest, PointOnEdge) {
+ PathVector a = string_to_path("M 0,0 L 10,0 10,10 0,10 z");
+ PathVector b = string_to_path("M -5,2 L 0,2 5,5 0,8 -5,8 z");
+
+ PathIntersectionGraph graph(a, b);
+ PathVector u = graph.getUnion();
+ //std::cout << u << std::endl;
+ EXPECT_EQ(u.size(), 1u);
+ EXPECT_EQ(u.curveCount(), 8u);
+ checkRandomPoints(a, b, u, UNION);
+
+ PathVector i = graph.getIntersection();
+ //std::cout << i << std::endl;
+ EXPECT_EQ(i.size(), 1u);
+ EXPECT_EQ(i.curveCount(), 3u);
+ checkRandomPoints(a, b, i, INTERSECTION);
+
+ PathVector s1 = graph.getAminusB();
+ //std::cout << s1 << std::endl;
+ EXPECT_EQ(s1.size(), 1u);
+ EXPECT_EQ(s1.curveCount(), 7u);
+ checkRandomPoints(a, b, s1, A_MINUS_B);
+
+ PathVector s2 = graph.getBminusA();
+ //std::cout << s2 << std::endl;
+ EXPECT_EQ(s2.size(), 1u);
+ EXPECT_EQ(s2.curveCount(), 4u);
+ checkRandomPoints(a, b, s2, B_MINUS_A);
+
+ PathVector x = graph.getXOR();
+ //std::cout << x << std::endl;
+ EXPECT_EQ(x.size(), 2u);
+ EXPECT_EQ(x.curveCount(), 11u);
+ checkRandomPoints(a, b, x, XOR);
+}
+
+TEST_F(IntersectionGraphTest, RhombusInSquare) {
+ PathVector square = string_to_path("M 0,0 L 10,0 10,10 0,10 z");
+ PathVector rhombus = string_to_path("M 5,0 L 10,5 5,10 0,5 z");
+
+ PathIntersectionGraph graph(square, rhombus);
+ //std::cout << graph << std::endl;
+ PathVector u = graph.getUnion();
+ EXPECT_EQ(u.size(), 1u);
+ EXPECT_EQ(u.curveCount(), 4u);
+ checkRandomPoints(square, rhombus, u, UNION);
+
+ PathVector i = graph.getIntersection();
+ EXPECT_EQ(i.size(), 1u);
+ EXPECT_EQ(i.curveCount(), 4u);
+ checkRandomPoints(square, rhombus, i, INTERSECTION);
+
+ PathVector s1 = graph.getAminusB();
+ EXPECT_EQ(s1.size(), 2u);
+ EXPECT_EQ(s1.curveCount(), 8u);
+ checkRandomPoints(square, rhombus, s1, A_MINUS_B);
+
+ PathVector s2 = graph.getBminusA();
+ EXPECT_EQ(s2.size(), 0u);
+ EXPECT_EQ(s2.curveCount(), 0u);
+ checkRandomPoints(square, rhombus, s2, B_MINUS_A);
+}
+
+TEST_F(IntersectionGraphTest, EmptyOperand) {
+ PathVector square = string_to_path("M 0,0 L 20, 0 L 20, 20 L 0, 20 Z");
+ PathVector empty;
+
+ auto graph = PathIntersectionGraph(square, empty);
+ // Taking union with the empty set should be a no-op: A ∪ ∅ = A
+ PathVector u = graph.getUnion();
+ EXPECT_EQ(u.size(), 1u);
+ EXPECT_EQ(u.curveCount(), 4u);
+
+ // Intersection with empty should produce empty: A ∩ ∅ = ∅
+ PathVector i = graph.getIntersection();
+ EXPECT_EQ(i.size(), 0u);
+
+ // Subtracting empty set should be a no-op: A ∖ ∅ = A
+ PathVector rd = graph.getAminusB();
+ EXPECT_EQ(rd.size(), 1u);
+ EXPECT_EQ(rd.curveCount(), 4u);
+
+ // Subtracting FROM the empty set should produce the empty set: ∅ ∖ A = ∅
+ PathVector ld = graph.getBminusA();
+ EXPECT_EQ(ld.size(), 0u);
+}
+
+// this test is disabled, since we cannot handle overlapping segments for now.
+#if 0
+TEST_F(IntersectionGraphTest, EqualUnionAndIntersection) {
+ PathVector shape = string_to_path("M 0,0 L 2,1 -1,2 -1,3 0,3 z");
+ PathIntersectionGraph graph(shape, shape);
+ std::cout << graph << std::endl;
+ PathVector a = graph.getUnion();
+ std::cout << shape << std::endl;
+ std::cout << a << std::endl;
+ checkRandomPoints(shape, shape, a, UNION);
+
+ PathIntersectionGraph graph2(bigh, bigh);
+ PathVector b = graph2.getIntersection();
+ checkRandomPoints(bigh, bigh, b, INTERSECTION);
+ std::cout << b <<std::endl;
+}
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/interval-test.cpp b/tests/interval-test.cpp
new file mode 100644
index 0000000..eccea70
--- /dev/null
+++ b/tests/interval-test.cpp
@@ -0,0 +1,54 @@
+/** @file
+ * @brief Unit tests for Interval, OptInterval, IntInterval, OptIntInterval.
+ *//*
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright 2021 Authors
+ *
+ * SPDX-License-Identifier: LGPL-2.1 OR MPL-1.1
+ */
+
+#include <2geom/interval.h>
+#include <gtest/gtest.h>
+
+TEST(IntervalTest, EqualityTest)
+{
+ Geom::Interval a(3, 5), a2(a), b(4, 7);
+ Geom::OptInterval empty, oa = a;
+
+ EXPECT_TRUE(a == a);
+ EXPECT_FALSE(a != a);
+ EXPECT_TRUE(a == a2);
+ EXPECT_FALSE(a != a2);
+ EXPECT_TRUE(empty == empty);
+ EXPECT_FALSE(empty != empty);
+ EXPECT_FALSE(a == empty);
+ EXPECT_TRUE(a != empty);
+ EXPECT_FALSE(empty == a);
+ EXPECT_TRUE(empty != a);
+ EXPECT_FALSE(a == b);
+ EXPECT_TRUE(a != b);
+ EXPECT_TRUE(a == oa);
+ EXPECT_FALSE(a != oa);
+
+ Geom::IntInterval ia(3, 5), ia2(ia), ib(4, 7);
+ Geom::OptIntInterval iempty, ioa = ia;
+
+ EXPECT_TRUE(ia == ia);
+ EXPECT_FALSE(ia != ia);
+ EXPECT_TRUE(ia == ia2);
+ EXPECT_FALSE(ia != ia2);
+ EXPECT_TRUE(iempty == iempty);
+ EXPECT_FALSE(iempty != iempty);
+ EXPECT_FALSE(ia == iempty);
+ EXPECT_TRUE(ia != iempty);
+ EXPECT_FALSE(iempty == ia);
+ EXPECT_TRUE(iempty != ia);
+ EXPECT_FALSE(ia == ib);
+ EXPECT_TRUE(ia != ib);
+ EXPECT_TRUE(ia == ioa);
+ EXPECT_FALSE(ia != ioa);
+}
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/linalg-test.cpp b/tests/linalg-test.cpp
new file mode 100644
index 0000000..b7e2f42
--- /dev/null
+++ b/tests/linalg-test.cpp
@@ -0,0 +1,502 @@
+
+
+#include "numeric/vector.h"
+#include "numeric/matrix.h"
+
+#include <iostream>
+
+using namespace Geom;
+
+
+template< class charT >
+inline
+std::basic_ostream<charT> &
+operator<< (std::basic_ostream<charT> & os, const std::pair<size_t, size_t>& index_pair)
+{
+ os << "{" << index_pair.first << "," << index_pair.second << "}";
+ return os;
+}
+
+template< typename T, typename U>
+void check_test( const char* description, T output, U expected )
+{
+ bool result = ( output == expected );
+ std::cout << "# " << description << " : ";
+ if ( result )
+ std::cout << "success!" << std::endl;
+ else
+ std::cout << "fail!" << std::endl
+ << " output: " << output << std::endl
+ << " expected: " << expected << std::endl;
+}
+
+
+void vector_test()
+{
+ // Deprecated. Replaced by ** Vector examples **
+ // in nl-vector-test.cpp
+ /*
+ NL::Vector v1(10), v2(10), v3(5);
+ for (unsigned int i = 0; i < v1.size(); ++i)
+ {
+ v1[i] = i;
+ }
+ std::cout << "v1: " << v1 << std::endl;
+ v2 = v1;
+ std::cout << "v2 = v1 : " << v2 << std::endl;
+ bool value = (v1 == v2);
+ std::cout << "(v1 == v2) : " << value << std::endl;
+ v2.scale(10);
+ std::cout << "v2.scale(10) : " << v2 << std::endl;
+ value = (v1 == v2);
+ std::cout << "(v1 == v2) : " << value << std::endl;
+ v2.translate(20);
+ std::cout << "v2.translate(20) : " << v2 << std::endl;
+ v2 += v1;
+ std::cout << "v2 += v1 : " << v2 << std::endl;
+ v2.swap_elements(3, 9);
+ std::cout << "v2.swap_elements(3, 9) : " << v2 << std::endl;
+ v2.reverse();
+ std::cout << "v2.reverse() : " << v2 << std::endl;
+ value = v2.is_positive();
+ std::cout << "v2.is_positive() : " << value << std::endl;
+ v2 -= v1;
+ std::cout << "v2 -= v1 : " << v2 << std::endl;
+ double bound = v2.max();
+ std::cout << "v2.max() : " << bound << std::endl;
+ bound = v2.min();
+ std::cout << "v2.min() : " << bound << std::endl;
+ unsigned int index = v2.max_index();
+ std::cout << "v2.max_index() : " << index << std::endl;
+ index = v2.min_index();
+ std::cout << "v2.min_index() : " << index << std::endl;
+ v2.set_basis(4);
+ std::cout << "v2.set_basis(4) : " << v2 << std::endl;
+ value = v2.is_non_negative();
+ std::cout << "v2.is_non_negative() : " << value << std::endl;
+ v2.set_all(0);
+ std::cout << "v2.set_all(0) : " << v2 << std::endl;
+ value = v2.is_zero();
+ std::cout << "v2.is_zero() : " << value << std::endl;
+ NL::swap(v1, v2);
+ std::cout << "swap(v1, v2) : v1: " << v1 << " v2: " << v2 << std::endl;
+ */
+}
+
+
+void const_vector_view_test()
+{
+ NL::Vector v1(10);
+ for (unsigned int i = 0; i < v1.size(); ++i)
+ v1[i] = i;
+ NL::VectorView vv1(v1, 5, 1, 2);
+ vv1.scale(10);
+ std::cout << "v1 = " << v1 << std::endl;
+
+ NL::ConstVectorView cvv1(v1, 6, 1);
+ check_test( "cvv1(v1, 6, 1)", cvv1.str(), "[10, 2, 30, 4, 50, 6]");
+ NL::ConstVectorView cvv2(v1, 3, 1, 3);
+ check_test( "cvv2(v1, 3, 1, 3)", cvv2.str(), "[10, 4, 70]");
+ NL::ConstVectorView cvv3(vv1, 3, 0, 2);
+ std::cout << "vv1 = " << vv1 << std::endl;
+ check_test( "cvv3(vv1, 3, 0, 2)", cvv3.str(), "[10, 50, 90]");
+ NL::ConstVectorView cvv4(cvv1, 3, 1, 2);
+ check_test( "cvv4(cvv1, 3, 1, 2)", cvv4.str(), "[2, 4, 6]");
+ bool value = (cvv2 == cvv4);
+ check_test( "(cvv2 == cvv4)", value, false);
+
+ value = cvv2.is_zero();
+ check_test( "cvv2.is_zero()", value, false);
+ value = cvv2.is_negative();
+ check_test( "cvv2.is_negative()", value, false);
+ value = cvv2.is_positive();
+ check_test( "cvv2.is_positive()", value, true);
+ value = cvv2.is_non_negative();
+ check_test( "cvv2.is_non_negative()", value, true);
+
+ NL::VectorView vv2(v1, 3, 1, 3);
+ vv2.scale(-1);
+ value = cvv2.is_zero();
+ std::cout << "v1 = " << v1 << std::endl;
+ check_test( "cvv2.is_zero()", value, false);
+ value = cvv2.is_negative();
+ check_test( "cvv2.is_negative()", value, true);
+ value = cvv2.is_positive();
+ check_test( "cvv2.is_positive()", value, false);
+ value = cvv2.is_non_negative();
+ check_test( "cvv2.is_non_negative()", value, false);
+
+ vv2.set_all(0);
+ std::cout << "v1 = " << v1 << std::endl;
+ value = cvv2.is_zero();
+ check_test( "cvv2.is_zero()", value, true);
+ value = cvv2.is_negative();
+ check_test( "cvv2.is_negative()", value, false);
+ value = cvv2.is_positive();
+ check_test( "cvv2.is_positive()", value, false);
+ value = cvv2.is_non_negative();
+ check_test( "cvv2.is_non_negative()", value, true);
+
+ vv1.reverse();
+ vv2[0] = -1;
+ std::cout << "v1 = " << v1 << std::endl;
+ value = cvv2.is_zero();
+ check_test( "cvv2.is_zero()", value, false);
+ value = cvv2.is_negative();
+ check_test( "cvv2.is_negative()", value, false);
+ value = cvv2.is_positive();
+ check_test( "cvv2.is_positive()", value, false);
+ value = cvv2.is_non_negative();
+ check_test( "cvv2.is_non_negative()", value, false);
+
+ vv2 = cvv2;
+ value = (vv2 == cvv2);
+ std::cout << "vv2 = " << vv2 << std::endl;
+ check_test( "(vv2 == cvv2)", value, true);
+ NL::Vector v2(cvv2.size());
+ v2 = cvv4;
+ value = (v2 == cvv2);
+ std::cout << "v2 = " << v2 << std::endl;
+ check_test( "(v2 == cvv2)", value, false);
+ const NL::Vector v3(cvv2.size());
+ NL::ConstVectorView cvv5(v3, v3.size());
+ check_test( "cvv5(v3, v3.size())", cvv4.str(), "[2, 0, 6]");
+
+}
+
+void vector_view_test()
+{
+ // Deprecated. Replaced by ** VectorView examples **
+ // in nl-vector-test.cpp
+ /*
+ NL::Vector v1(10);
+ for (unsigned int i = 0; i < v1.size(); ++i)
+ v1[i] = i;
+ NL::VectorView vv1(v1, 5), vv2(v1, 5, 3), vv3(v1, 5, 0, 2), vv4(v1, 5, 1, 2);
+ std::cout << "v1 = " << v1 << std::endl;
+ check_test( "vv1(v1, 5)", vv1.str(), "[0, 1, 2, 3, 4]");
+ check_test( "vv2(v1, 5, 3)", vv2.str(), "[3, 4, 5, 6, 7]");
+ check_test( "vv3(v1, 5, 0, 2)", vv3.str(), "[0, 2, 4, 6, 8]");
+ check_test( "vv4(v1, 5, 1, 2)", vv4.str(), "[1, 3, 5, 7, 9]");
+
+ NL::VectorView vv5(vv4, 3, 0, 2);
+ std::cout << "vv4 = " << vv4 << std::endl;
+ check_test( "vv5(vv4, 3, 0, 2)", vv5.str(), "[1, 5, 9]");
+ vv5.scale(10);
+ check_test( "vv5.scale(10) : vv5", vv5.str(), "[10, 50, 90]");
+ check_test( " : v1", v1.str(), "[0, 10, 2, 3, 4, 50, 6, 7, 8, 90]");
+ vv5.translate(20);
+ check_test( "vv5.translate(20) : vv5", vv5.str(), "[30, 70, 110]");
+ check_test( " : v1", v1.str(), "[0, 30, 2, 3, 4, 70, 6, 7, 8, 110]");
+ vv1 += vv2;
+ check_test("vv1 += vv2", vv1.str(), "[3, 34, 72, 9, 11]");
+ vv1 -= vv2;
+ check_test("vv1 -= vv2", vv1.str(), "[-6, 23, 2, 3, 4]");
+ NL::ConstVectorView cvv1(vv3, 3);
+ vv5 = cvv1;
+ check_test("vv5 = cvv1", vv5.str(), "[-6, 2, 4]");
+ vv5 += cvv1;
+ check_test("vv5 += cvv1", vv5.str(), "[-12, 4, 8]");
+ vv5 -= cvv1;
+ check_test("vv5 -= cvv1", vv5.str(), "[-6, 2, 4]");
+ NL::Vector v2(vv1);
+ std::cout << "v2 = " << v2 << std::endl;
+ vv1 = v2;
+ check_test( "vv1 = v2", vv1.str(), "[-6, -6, 2, 3, 4]");
+ vv1 += v2;
+ check_test( "vv1 += v2", vv1.str(), "[-12, -12, 4, 6, 8]");
+ vv1 -= v2;
+ check_test( "vv1 -= v2", vv1.str(), "[-6, -6, 2, 3, 4]");
+ NL::swap_view(vv1, vv4);
+ check_test( "swap_view(vv1, vv4)", v1.str(), "[-6, -6, 2, 3, 4, 2, 6, 7, 8, 4]");
+ */
+}
+
+
+void const_matrix_view_test()
+{
+ NL::Matrix m0(8,4);
+ for (size_t i = 0; i < m0.rows(); ++i)
+ {
+ for (size_t j = 0; j < m0.columns(); ++j)
+ {
+ m0(i,j) = 10 * i + j;
+ }
+ }
+ std::cout << "m0 = " << m0 << std::endl;
+
+ // constructor test
+ NL::Matrix m1(m0);
+ NL::ConstMatrixView cmv1(m1, 2, 1, 4, 2);
+ check_test("cmv1(m1, 2, 1, 4, 2)", cmv1.str(), "[[21, 22], [31, 32], [41, 42], [51, 52]]");
+ NL::MatrixView mv1(m1, 2, 0, 4, 4);
+ NL::ConstMatrixView cmv2(mv1, 2, 1, 2, 2);
+ check_test("cmv2(mv1, 2, 1, 2, 2)", cmv2.str(), "[[41, 42], [51, 52]]");
+ NL::ConstMatrixView cmv3(cmv1, 1, 1, 3, 1);
+ check_test("cmv3(cmv1, 1, 1, 2, 1)", cmv3.str(), "[[32], [42], [52]]");
+ const NL::Matrix & m2 = m1;
+ NL::ConstMatrixView cmv4(m2, 2, 1, 4, 2);
+ check_test("cmv4(m2, 2, 1, 4, 2)", cmv4.str(), "[[21, 22], [31, 32], [41, 42], [51, 52]]");
+ const NL::MatrixView & mv2 = mv1;
+ NL::ConstMatrixView cmv5(mv2, 2, 1, 2, 2);
+ check_test("cmv5(mv2, 2, 1, 2, 2)", cmv5.str(), "[[41, 42], [51, 52]]");
+
+ // row and column view test
+ NL::ConstVectorView cvv1 = cmv1.row_const_view(2);
+ check_test("cvv1 = cmv1.row_const_view(2)", cvv1.str(), "[41, 42]");
+ NL::ConstVectorView cvv2 = cmv1.column_const_view(0);
+ check_test("cvv2 = cmv1.column_const_view(0)", cvv2.str(), "[21, 31, 41, 51]");
+
+ // property test
+ bool value = cmv1.is_negative();
+ check_test("cmv1.is_negative()", value, false);
+ value = cmv1.is_non_negative();
+ check_test("cmv1.is_non_negative()", value, true);
+ value = cmv1.is_positive();
+ check_test("cmv1.is_positive()", value, true);
+ value = cmv1.is_zero();
+ check_test("cmv1.is_zero()", value, false);
+
+ m1.scale(-1);
+ value = cmv1.is_negative();
+ check_test("cmv1.is_negative()", value, true);
+ value = cmv1.is_non_negative();
+ check_test("cmv1.is_non_negative()", value, false);
+ value = cmv1.is_positive();
+ check_test("cmv1.is_positive()", value, false);
+ value = cmv1.is_zero();
+ check_test("cmv1.is_zero()", value, false);
+
+ m1.translate(35);
+ value = cmv1.is_negative();
+ check_test("cmv1.is_negative()", value, false);
+ value = cmv1.is_non_negative();
+ check_test("cmv1.is_non_negative()", value, false);
+ value = cmv1.is_positive();
+ check_test("cmv1.is_positive()", value, false);
+ value = cmv1.is_zero();
+ check_test("cmv1.is_zero()", value, false);
+
+ m1.set_all(0);
+ value = cmv1.is_negative();
+ check_test("cmv1.is_negative()", value, false);
+ value = cmv1.is_non_negative();
+ check_test("cmv1.is_non_negative()", value, true);
+ value = cmv1.is_positive();
+ check_test("cmv1.is_positive()", value, false);
+ value = cmv1.is_zero();
+ check_test("cmv1.is_zero()", value, true);
+
+ m1.set_identity();
+ value = cmv1.is_negative();
+ check_test("cmv1.is_negative()", value, false);
+ value = cmv1.is_non_negative();
+ check_test("cmv1.is_non_negative()", value, true);
+ value = cmv1.is_positive();
+ check_test("cmv1.is_positive()", value, false);
+ value = cmv1.is_zero();
+ check_test("cmv1.is_zero()", value, false);
+
+ // max, min test
+ m1 = m0;
+ std::cout << "cmv1 = " << cmv1 << std::endl;
+ std::pair<size_t, size_t> out_elem = cmv1.max_index();
+ std::pair<size_t, size_t> exp_elem(3,1);
+ check_test("cmv1.max_index()", out_elem, exp_elem);
+ double bound = cmv1.max();
+ check_test("cmv1.max()", bound, cmv1(exp_elem.first, exp_elem.second));
+ out_elem = cmv1.min_index();
+ exp_elem.first = 0; exp_elem.second = 0;
+ check_test("cmv1.min_index()", out_elem, exp_elem);
+ bound = cmv1.min();
+ check_test("cmv1.min()", bound, cmv1(exp_elem.first, exp_elem.second));
+
+}
+
+
+void matrix_view_test()
+{
+ NL::Matrix m0(8,4);
+ for (size_t i = 0; i < m0.rows(); ++i)
+ {
+ for (size_t j = 0; j < m0.columns(); ++j)
+ {
+ m0(i,j) = 10 * i + j;
+ }
+ }
+ std::cout << "m0 = " << m0 << std::endl;
+
+ // constructor test
+ NL::Matrix m1(m0);
+ NL::MatrixView mv1(m1, 2, 1, 4, 2);
+ check_test("mv1(m1, 2, 1, 4, 2)", mv1.str(), "[[21, 22], [31, 32], [41, 42], [51, 52]]");
+ NL::MatrixView mv2(mv1, 2, 1, 2, 1);
+ check_test("mv2(mv1, 2, 1, 2, 1)", mv2.str(), "[[42], [52]]");
+
+ // operator = test
+ NL::Matrix m2(4,2);
+ m2.set_all(0);
+ mv1 = m2;
+ check_test("mv1 = m2", m1.str(), "[[0, 1, 2, 3], [10, 11, 12, 13], [20, 0, 0, 23], [30, 0, 0, 33], [40, 0, 0, 43], [50, 0, 0, 53], [60, 61, 62, 63], [70, 71, 72, 73]]");
+ m1 = m0;
+ NL::MatrixView mv3(m2, 0, 0, 4, 2);
+ mv1 = mv3;
+ check_test("mv1 = mv3", m1.str(), "[[0, 1, 2, 3], [10, 11, 12, 13], [20, 0, 0, 23], [30, 0, 0, 33], [40, 0, 0, 43], [50, 0, 0, 53], [60, 61, 62, 63], [70, 71, 72, 73]]");
+ m1 = m0;
+ NL::ConstMatrixView cmv1(m2, 0, 0, 4, 2);
+ mv1 = cmv1;
+ check_test("mv1 = cmv1", m1.str(), "[[0, 1, 2, 3], [10, 11, 12, 13], [20, 0, 0, 23], [30, 0, 0, 33], [40, 0, 0, 43], [50, 0, 0, 53], [60, 61, 62, 63], [70, 71, 72, 73]]");
+
+ // operator == test
+ m2.set_identity();
+ mv1 = m2;
+ bool value = (mv1 == m2);
+ check_test("(mv1 == m2)", value, true);
+ value = (mv1 == mv3);
+ check_test("(mv1 == mv3)", value, true);
+ value = (mv1 == cmv1);
+ check_test("(mv1 == cmv1)", value, true);
+
+ // row and column view test
+ m1 = m0;
+ NL::ConstVectorView cvv1 = mv1.row_const_view(2);
+ check_test("cvv1 = mv1.row_const_view(2)", cvv1.str(), "[41, 42]");
+ NL::ConstVectorView cvv2 = mv1.column_const_view(0);
+ check_test("cvv2 = mv1.column_const_view(0)", cvv2.str(), "[21, 31, 41, 51]");
+ NL::VectorView vv1 = mv1.row_view(2);
+ check_test("vv1 = mv1.row_view(2)", vv1.str(), "[41, 42]");
+ NL::VectorView vv2 = mv1.column_view(0);
+ check_test("vv2 = mv1.column_view(0)", vv2.str(), "[21, 31, 41, 51]");
+
+ // swap_view test
+ m1 = m0;
+ swap_view(mv1, mv3);
+ check_test("swap_view(mv1, mv3) : mv1", mv1.str(), "[[1, 0], [0, 1], [0, 0], [0, 0]]");
+ check_test(" : m1", m1.str(), m0.str());
+ check_test(" : mv3", mv3.str(), "[[21, 22], [31, 32], [41, 42], [51, 52]]");
+ check_test(" : m2", m2.str(), "[[1, 0], [0, 1], [0, 0], [0, 0]]");
+ swap_view(mv1, mv3);
+
+ // modifying operations test
+ m1 = m0;
+ m2.set_all(10);
+ mv1 += m2;
+ check_test("mv1 += m2", m1.str(), "[[0, 1, 2, 3], [10, 11, 12, 13], [20, 31, 32, 23], [30, 41, 42, 33], [40, 51, 52, 43], [50, 61, 62, 53], [60, 61, 62, 63], [70, 71, 72, 73]]");
+ mv1 -= m2;
+ check_test("mv1 -= m2", m1.str(), m0.str());
+ mv1 += mv3;
+ check_test("mv1 += mv3", m1.str(), "[[0, 1, 2, 3], [10, 11, 12, 13], [20, 31, 32, 23], [30, 41, 42, 33], [40, 51, 52, 43], [50, 61, 62, 53], [60, 61, 62, 63], [70, 71, 72, 73]]");
+ mv1 -= mv3;
+ check_test("mv1 -= mv3", m1.str(), m0.str());
+ mv1 += cmv1;
+ check_test("mv1 += cmv1", m1.str(), "[[0, 1, 2, 3], [10, 11, 12, 13], [20, 31, 32, 23], [30, 41, 42, 33], [40, 51, 52, 43], [50, 61, 62, 53], [60, 61, 62, 63], [70, 71, 72, 73]]");
+ mv1 -= cmv1;
+ check_test("mv1 -= cmv1", m1.str(), m0.str());
+
+ m1 = m0;
+ mv1.swap_rows(0,3);
+ check_test("mv1.swap_rows(0,3)", m1.str(), "[[0, 1, 2, 3], [10, 11, 12, 13], [20, 51, 52, 23], [30, 31, 32, 33], [40, 41, 42, 43], [50, 21, 22, 53], [60, 61, 62, 63], [70, 71, 72, 73]]");
+ m1 = m0;
+ mv1.swap_columns(0,1);
+ check_test("mv1.swap_columns(0,3)", m1.str(), "[[0, 1, 2, 3], [10, 11, 12, 13], [20, 22, 21, 23], [30, 32, 31, 33], [40, 42, 41, 43], [50, 52, 51, 53], [60, 61, 62, 63], [70, 71, 72, 73]]");
+
+ m1 = m0;
+ NL::MatrixView mv4(m1, 0, 0, 4, 4);
+ mv4.transpose();
+ check_test("mv4.transpose()", m1.str(), "[[0, 10, 20, 30], [1, 11, 21, 31], [2, 12, 22, 32], [3, 13, 23, 33], [40, 41, 42, 43], [50, 51, 52, 53], [60, 61, 62, 63], [70, 71, 72, 73]]");
+
+}
+
+void matrix_test()
+{
+ NL::Matrix m0(8,4);
+ for (size_t i = 0; i < m0.rows(); ++i)
+ {
+ for (size_t j = 0; j < m0.columns(); ++j)
+ {
+ m0(i,j) = 10 * i + j;
+ }
+ }
+ std::cout << "m0 = " << m0 << std::endl;
+
+ // constructor test
+ NL::Matrix m1(m0);
+ check_test("m1(m0)", m1.str(), m0.str());
+ NL::MatrixView mv1(m0, 2, 1, 4, 2);
+ NL::Matrix m2(mv1);
+ check_test("m2(mv1)", m2.str(), mv1.str());
+ NL::MatrixView cmv1(m0, 2, 1, 4, 2);
+ NL::Matrix m3(cmv1);
+ check_test("m3(cmv1)", m3.str(), cmv1.str());
+
+ // operator = and operator == test
+ m1.set_all(0);
+ m1 = m0;
+ check_test("m1 = m0", m1.str(), m0.str());
+ bool value = (m1 == m0);
+ check_test("m1 == m0", value, true);
+ m2.set_all(0);
+ m2 = mv1;
+ check_test("m2 = mv1", m2.str(), mv1.str());
+ value = (m2 == mv1);
+ check_test("m2 == mv1", value, true);
+ m2.set_all(0);
+ m2 = cmv1;
+ check_test("m2 = cmv1", m2.str(), cmv1.str());
+ value = (m2 == cmv1);
+ check_test("m2 == cmv1", value, true);
+
+ // row and column view test
+ NL::ConstVectorView cvv1 = m2.row_const_view(2);
+ check_test("cvv1 = m2.row_const_view(2)", cvv1.str(), "[41, 42]");
+ NL::ConstVectorView cvv2 = m2.column_const_view(0);
+ check_test("cvv2 = m2.column_const_view(0)", cvv2.str(), "[21, 31, 41, 51]");
+ NL::VectorView vv1 = m2.row_view(2);
+ check_test("vv1 = m2.row_view(2)", vv1.str(), "[41, 42]");
+ NL::VectorView vv2 = m2.column_view(0);
+ check_test("vv2 = m2.column_view(0)", vv2.str(), "[21, 31, 41, 51]");
+
+
+ // modifying operations test
+ NL::Matrix m4(8,4);
+ m4.set_all(0);
+ m1.set_all(0);
+ m1 += m0;
+ check_test("m1 += m0", m1.str(), m0.str());
+ m1 -= m0;
+ check_test("m1 -= m0", m1.str(), m4.str());
+ NL::Matrix m5(4,2);
+ m5.set_all(0);
+ m2.set_all(0);
+ m2 += mv1;
+ check_test("m2 += mv1", m2.str(), mv1.str());
+ m2 -= mv1;
+ check_test("m2 -= mv1", m2.str(), m5.str());
+ m2.set_all(0);
+ m2 += cmv1;
+ check_test("m2 += cmv1", m2.str(), cmv1.str());
+ m2 -= cmv1;
+ check_test("m2 -= cmv1", m2.str(), m5.str());
+
+ // swap test
+ m3.set_identity();
+ m5.set_identity();
+ m1 = m0;
+ m2 = mv1;
+ swap(m2, m3);
+ check_test("swap(m2, m3) : m2", m2.str(), m5.str());
+ check_test(" : m3", m3.str(), mv1.str());
+
+}
+
+int main(int argc, char **argv)
+{
+ //const_vector_view_test();
+ //vector_view_test();
+ const_matrix_view_test();
+ //matrix_view_test();
+ //matrix_test();
+ return 0;
+}
+
+
diff --git a/tests/line-test.cpp b/tests/line-test.cpp
new file mode 100644
index 0000000..0625566
--- /dev/null
+++ b/tests/line-test.cpp
@@ -0,0 +1,185 @@
+/** @file
+ * @brief Unit tests for Line and related functions
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include "testing.h"
+#include <iostream>
+#include <glib.h>
+
+#include <2geom/line.h>
+#include <2geom/affine.h>
+
+using namespace Geom;
+
+TEST(LineTest, VectorAndVersor) {
+ Line a(Point(10, 10), Point(-10, 20));
+ Line b(Point(10, 10), Point(15, 15));
+ EXPECT_EQ(a.vector(), Point(-20, 10));
+ EXPECT_EQ(b.vector(), Point(5, 5));
+ EXPECT_EQ(a.versor(), a.vector().normalized());
+ EXPECT_EQ(b.versor(), b.vector().normalized());
+}
+
+TEST(LineTest, AngleBisector) {
+ Point o(0,0), a(1,1), b(3,0), c(-4, 0);
+ Point d(0.5231, 0.75223);
+
+ // normal
+ Line ab1 = make_angle_bisector_line(a + d, o + d, b + d);
+ Line ab2 = make_angle_bisector_line(a - d, o - d, b - d);
+ EXPECT_FLOAT_EQ(ab1.angle(), Angle::from_degrees(22.5));
+ EXPECT_FLOAT_EQ(ab2.angle(), Angle::from_degrees(22.5));
+
+ // half angle
+ Line bc1 = make_angle_bisector_line(b + d, o + d, c + d);
+ Line bc2 = make_angle_bisector_line(b - d, o - d, c - d);
+ EXPECT_FLOAT_EQ(bc1.angle(), Angle::from_degrees(90));
+ EXPECT_FLOAT_EQ(bc2.angle(), Angle::from_degrees(90));
+
+ // zero angle
+ Line aa1 = make_angle_bisector_line(a + d, o + d, a + d);
+ Line aa2 = make_angle_bisector_line(a - d, o - d, a - d);
+ EXPECT_FLOAT_EQ(aa1.angle(), Angle::from_degrees(45));
+ EXPECT_FLOAT_EQ(aa2.angle(), Angle::from_degrees(45));
+}
+
+TEST(LineTest, Equality) {
+ Line a(Point(0,0), Point(2,2));
+ Line b(Point(2,2), Point(5,5));
+
+ EXPECT_EQ(a, a);
+ EXPECT_EQ(b, b);
+ EXPECT_EQ(a, b);
+}
+
+TEST(LineTest, Reflection) {
+ Line a(Point(10, 0), Point(15,5));
+ Point pa(10,5), ra(15,0);
+
+ Line b(Point(1,-2), Point(2,0));
+ Point pb(5,1), rb(1,3);
+ Affine reflecta = a.reflection(), reflectb = b.reflection();
+
+ Point testra = pa * reflecta;
+ Point testrb = pb * reflectb;
+
+ constexpr Coord eps{1e-12};
+ EXPECT_near(testra[X], ra[X], eps);
+ EXPECT_near(testra[Y], ra[Y], eps);
+ EXPECT_near(testrb[X], rb[X], eps);
+ EXPECT_near(testrb[Y], rb[Y], eps);
+}
+
+TEST(LineTest, RotationToZero) {
+ Line a(Point(-5,23), Point(15,27));
+ Affine mx = a.rotationToZero(X);
+ Affine my = a.rotationToZero(Y);
+
+ for (unsigned i = 0; i <= 12; ++i) {
+ double t = -1 + 0.25 * i;
+ Point p = a.pointAt(t);
+ Point rx = p * mx;
+ Point ry = p * my;
+ //std::cout << rx[X] << " " << ry[Y] << std::endl;
+ // unfortunately this is precise only to about 1e-14
+ EXPECT_NEAR(rx[X], 0, 1e-14);
+ EXPECT_NEAR(ry[Y], 0, 1e-14);
+ }
+}
+
+TEST(LineTest, Coefficients) {
+ std::vector<Line> lines;
+ lines.emplace_back(Point(1e3,1e3), Point(1,1));
+ //the case below will never work without normalizing the line
+ //lines.emplace_back(Point(1e5,1e5), Point(1e-15,0));
+ lines.emplace_back(Point(1e5,1e5), Point(1e5,-1e5));
+ lines.emplace_back(Point(-3,10), Point(3,10));
+ lines.emplace_back(Point(250,333), Point(-72,121));
+
+ for (auto & line : lines) {
+ Coord a, b, c, A, B, C;
+ line.coefficients(a, b, c);
+ /*std::cout << format_coord_nice(a) << " "
+ << format_coord_nice(b) << " "
+ << format_coord_nice(c) << std::endl;*/
+ Line k(a, b, c);
+ //std::cout << k.initialPoint() << " " << k.finalPoint() << std::endl;
+ k.coefficients(A, B, C);
+ /*std::cout << format_coord_nice(A) << " "
+ << format_coord_nice(B) << " "
+ << format_coord_nice(C) << std::endl;*/
+ EXPECT_DOUBLE_EQ(a, A);
+ EXPECT_DOUBLE_EQ(b, B);
+ EXPECT_DOUBLE_EQ(c, C);
+
+ for (unsigned j = 0; j <= 10; ++j) {
+ double t = j / 10.;
+ Point p = line.pointAt(t);
+ /*std::cout << t << " " << p << " "
+ << A*p[X] + B*p[Y] + C << " "
+ << A*(p[X]-1) + B*(p[Y]+1) + C << std::endl;*/
+ EXPECT_near(A*p[X] + B*p[Y] + C, 0., 2e-11);
+ EXPECT_not_near(A*(p[X]-1) + B*(p[Y]+1) + C, 0., 1e-6);
+ }
+ }
+}
+
+TEST(LineTest, Intersection) {
+ Line a(Point(0,3), Point(1,2));
+ Line b(Point(0,-3), Point(1,-2));
+ LineSegment lsa(Point(0,3), Point(1,2));
+ LineSegment lsb(Point(0,-3), Point(1,-2));
+ LineSegment lsc(Point(3,1), Point(3, -1));
+
+ std::vector<ShapeIntersection> r1, r2, r3;
+
+ r1 = a.intersect(b);
+ ASSERT_EQ(r1.size(), 1u);
+ EXPECT_EQ(r1[0].point(), Point(3,0));
+ EXPECT_intersections_valid(a, b, r1, 1e-15);
+
+ r2 = a.intersect(lsc);
+ ASSERT_EQ(r2.size(), 1u);
+ EXPECT_EQ(r2[0].point(), Point(3,0));
+ EXPECT_intersections_valid(a, lsc, r2, 1e-15);
+
+ r3 = b.intersect(lsc);
+ ASSERT_EQ(r3.size(), 1u);
+ EXPECT_EQ(r3[0].point(), Point(3,0));
+ EXPECT_intersections_valid(a, lsc, r3, 1e-15);
+
+ EXPECT_TRUE(lsa.intersect(lsb).empty());
+ EXPECT_TRUE(lsa.intersect(lsc).empty());
+ EXPECT_TRUE(lsb.intersect(lsc).empty());
+ EXPECT_TRUE(a.intersect(lsb).empty());
+ EXPECT_TRUE(b.intersect(lsa).empty());
+}
diff --git a/tests/mersennetwister.h b/tests/mersennetwister.h
new file mode 100644
index 0000000..bc19d8e
--- /dev/null
+++ b/tests/mersennetwister.h
@@ -0,0 +1,427 @@
+/**
+ * 2Geom developers:
+ * For licence reasons, Do not copy code from this header into other files
+ * */
+// MersenneTwister.h
+// Mersenne Twister random number generator -- a C++ class MTRand
+// Based on code by Makoto Matsumoto, Takuji Nishimura, and Shawn Cokus
+// Richard J. Wagner v1.0 15 May 2003 rjwagner@writeme.com
+
+// The Mersenne Twister is an algorithm for generating random numbers. It
+// was designed with consideration of the flaws in various other generators.
+// The period, 2^19937-1, and the order of equidistribution, 623 dimensions,
+// are far greater. The generator is also fast; it avoids multiplication and
+// division, and it benefits from caches and pipelines. For more information
+// see the inventors' web page at http://www.math.keio.ac.jp/~matumoto/emt.html
+
+// Reference
+// M. Matsumoto and T. Nishimura, "Mersenne Twister: A 623-Dimensionally
+// Equidistributed Uniform Pseudo-Random Number Generator", ACM Transactions on
+// Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3-30.
+
+// Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+// Copyright (C) 2000 - 2003, Richard J. Wagner
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. The names of its contributors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// The original code included the following notice:
+//
+// When you use this, send an email to: matumoto@math.keio.ac.jp
+// with an appropriate reference to your work.
+//
+// It would be nice to CC: rjwagner@writeme.com and Cokus@math.washington.edu
+// when you write.
+
+#ifndef MERSENNETWISTER_H
+#define MERSENNETWISTER_H
+
+// Not thread safe (unless auto-initialization is avoided and each thread has
+// its own MTRand object)
+
+#include <iostream>
+#include <limits.h>
+#include <stdio.h>
+#include <time.h>
+#include <math.h>
+
+class MTRand {
+ // Data
+ public:
+ typedef unsigned long uint32; // unsigned integer type, at least 32 bits
+
+ enum { N = 624 }; // length of state vector
+ enum { SAVE = N + 1 }; // length of array for save()
+
+ protected:
+ enum { M = 397 }; // period parameter
+
+ uint32 state[N]; // internal state
+ uint32 *pNext; // next value to get from state
+ int left; // number of values left before reload needed
+
+
+ //Methods
+ public:
+ MTRand( const uint32& oneSeed ); // initialize with a simple uint32
+ MTRand( uint32 *const bigSeed, uint32 const seedLength = N ); // or an array
+ MTRand(); // auto-initialize with /dev/urandom or time() and clock()
+
+ // Do NOT use for CRYPTOGRAPHY without securely hashing several returned
+ // values together, otherwise the generator state can be learned after
+ // reading 624 consecutive values.
+
+ // Access to 32-bit random numbers
+ double rand(); // real number in [0,1]
+ double rand( const double& n ); // real number in [0,n]
+ double randExc(); // real number in [0,1)
+ double randExc( const double& n ); // real number in [0,n)
+ double randDblExc(); // real number in (0,1)
+ double randDblExc( const double& n ); // real number in (0,n)
+ uint32 randInt(); // integer in [0,2^32-1]
+ uint32 randInt( const uint32& n ); // integer in [0,n] for n < 2^32
+ double operator()() { return rand(); } // same as rand()
+
+ // Access to 53-bit random numbers (capacity of IEEE double precision)
+ double rand53(); // real number in [0,1)
+
+ // Access to nonuniform random number distributions
+ double randNorm( const double& mean = 0.0, const double& variance = 1.0 );
+
+ // Re-seeding functions with same behavior as initializers
+ void seed( const uint32 oneSeed );
+ void seed( uint32 *const bigSeed, const uint32 seedLength = N );
+ void seed();
+
+ // Saving and loading generator state
+ void save( uint32* saveArray ) const; // to array of size SAVE
+ void load( uint32 *const loadArray ); // from such array
+ friend std::ostream& operator<<( std::ostream& os, const MTRand& mtrand );
+ friend std::istream& operator>>( std::istream& is, MTRand& mtrand );
+
+ protected:
+ void initialize( const uint32 oneSeed );
+ void reload();
+ uint32 hiBit( const uint32& u ) const { return u & 0x80000000UL; }
+ uint32 loBit( const uint32& u ) const { return u & 0x00000001UL; }
+ uint32 loBits( const uint32& u ) const { return u & 0x7fffffffUL; }
+ uint32 mixBits( const uint32& u, const uint32& v ) const
+ { return hiBit(u) | loBits(v); }
+ uint32 twist( const uint32& m, const uint32& s0, const uint32& s1 ) const
+ { return m ^ (mixBits(s0,s1)>>1) ^ (-loBit(s1) & 0x9908b0dfUL); }
+ static uint32 hash( time_t t, clock_t c );
+};
+
+
+inline MTRand::MTRand( const uint32& oneSeed )
+{ seed(oneSeed); }
+
+inline MTRand::MTRand( uint32 *const bigSeed, const uint32 seedLength )
+{ seed(bigSeed,seedLength); }
+
+inline MTRand::MTRand()
+{ seed(); }
+
+inline double MTRand::rand()
+{ return double(randInt()) * (1.0/4294967295.0); }
+
+inline double MTRand::rand( const double& n )
+{ return rand() * n; }
+
+inline double MTRand::randExc()
+{ return double(randInt()) * (1.0/4294967296.0); }
+
+inline double MTRand::randExc( const double& n )
+{ return randExc() * n; }
+
+inline double MTRand::randDblExc()
+{ return ( double(randInt()) + 0.5 ) * (1.0/4294967296.0); }
+
+inline double MTRand::randDblExc( const double& n )
+{ return randDblExc() * n; }
+
+inline double MTRand::rand53()
+{
+ uint32 a = randInt() >> 5, b = randInt() >> 6;
+ return ( a * 67108864.0 + b ) * (1.0/9007199254740992.0); // by Isaku Wada
+}
+
+inline double MTRand::randNorm( const double& mean, const double& variance )
+{
+ // Return a real number from a normal (Gaussian) distribution with given
+ // mean and variance by Box-Muller method
+ double r = sqrt( -2.0 * log( 1.0-randDblExc()) ) * variance;
+ double phi = 2.0 * 3.14159265358979323846264338328 * randExc();
+ return mean + r * cos(phi);
+}
+
+inline MTRand::uint32 MTRand::randInt()
+{
+ // Pull a 32-bit integer from the generator state
+ // Every other access function simply transforms the numbers extracted here
+
+ if( left == 0 ) reload();
+ --left;
+
+ register uint32 s1;
+ s1 = *pNext++;
+ s1 ^= (s1 >> 11);
+ s1 ^= (s1 << 7) & 0x9d2c5680UL;
+ s1 ^= (s1 << 15) & 0xefc60000UL;
+ return ( s1 ^ (s1 >> 18) );
+}
+
+inline MTRand::uint32 MTRand::randInt( const uint32& n )
+{
+ // Find which bits are used in n
+ // Optimized by Magnus Jonsson (magnus@smartelectronix.com)
+ uint32 used = n;
+ used |= used >> 1;
+ used |= used >> 2;
+ used |= used >> 4;
+ used |= used >> 8;
+ used |= used >> 16;
+
+ // Draw numbers until one is found in [0,n]
+ uint32 i;
+ do
+ i = randInt() & used; // toss unused bits to shorten search
+ while( i > n );
+ return i;
+}
+
+
+inline void MTRand::seed( const uint32 oneSeed )
+{
+ // Seed the generator with a simple uint32
+ initialize(oneSeed);
+ reload();
+}
+
+
+inline void MTRand::seed( uint32 *const bigSeed, const uint32 seedLength )
+{
+ // Seed the generator with an array of uint32's
+ // There are 2^19937-1 possible initial states. This function allows
+ // all of those to be accessed by providing at least 19937 bits (with a
+ // default seed length of N = 624 uint32's). Any bits above the lower 32
+ // in each element are discarded.
+ // Just call seed() if you want to get array from /dev/urandom
+ initialize(19650218UL);
+ register int i = 1;
+ register uint32 j = 0;
+ register int k = ( uint32(N) > seedLength ? uint32(N) : seedLength );
+ for( ; k; --k )
+ {
+ state[i] =
+ state[i] ^ ( (state[i-1] ^ (state[i-1] >> 30)) * 1664525UL );
+ state[i] += ( bigSeed[j] & 0xffffffffUL ) + j;
+ state[i] &= 0xffffffffUL;
+ ++i; ++j;
+ if( i >= N ) { state[0] = state[N-1]; i = 1; }
+ if( j >= seedLength ) j = 0;
+ }
+ for( k = N - 1; k; --k )
+ {
+ state[i] =
+ state[i] ^ ( (state[i-1] ^ (state[i-1] >> 30)) * 1566083941UL );
+ state[i] -= i;
+ state[i] &= 0xffffffffUL;
+ ++i;
+ if( i >= N ) { state[0] = state[N-1]; i = 1; }
+ }
+ state[0] = 0x80000000UL; // MSB is 1, assuring non-zero initial array
+ reload();
+}
+
+
+inline void MTRand::seed()
+{
+ // Seed the generator with an array from /dev/urandom if available
+ // Otherwise use a hash of time() and clock() values
+
+ // First try getting an array from /dev/urandom
+ FILE* urandom = fopen( "/dev/urandom", "rb" );
+ if( urandom )
+ {
+ uint32 bigSeed[N];
+ register uint32 *s = bigSeed;
+ register int i = N;
+ register bool success = true;
+ while( success && i-- )
+ success = fread( s++, sizeof(uint32), 1, urandom );
+ fclose(urandom);
+ if( success ) { seed( bigSeed, N ); return; }
+ }
+
+ // Was not successful, so use time() and clock() instead
+ seed( hash( time(NULL), clock() ) );
+}
+
+
+inline void MTRand::initialize( const uint32 seed )
+{
+ // Initialize generator state with seed
+ // See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier.
+ // In previous versions, most significant bits (MSBs) of the seed affect
+ // only MSBs of the state array. Modified 9 Jan 2002 by Makoto Matsumoto.
+ register uint32 *s = state;
+ register uint32 *r = state;
+ register int i = 1;
+ *s++ = seed & 0xffffffffUL;
+ for( ; i < N; ++i )
+ {
+ *s++ = ( 1812433253UL * ( *r ^ (*r >> 30) ) + i ) & 0xffffffffUL;
+ r++;
+ }
+}
+
+
+inline void MTRand::reload()
+{
+ // Generate N new values in state
+ // Made clearer and faster by Matthew Bellew (matthew.bellew@home.com)
+ register uint32 *p = state;
+ register int i;
+ for( i = N - M; i--; ++p )
+ *p = twist( p[M], p[0], p[1] );
+ for( i = M; --i; ++p )
+ *p = twist( p[M-N], p[0], p[1] );
+ *p = twist( p[M-N], p[0], state[0] );
+
+ left = N, pNext = state;
+}
+
+
+inline MTRand::uint32 MTRand::hash( time_t t, clock_t c )
+{
+ // Get a uint32 from t and c
+ // Better than uint32(x) in case x is floating point in [0,1]
+ // Based on code by Lawrence Kirby (fred@genesis.demon.co.uk)
+
+ static uint32 differ = 0; // guarantee time-based seeds will change
+
+ uint32 h1 = 0;
+ unsigned char *p = (unsigned char *) &t;
+ for( size_t i = 0; i < sizeof(t); ++i )
+ {
+ h1 *= UCHAR_MAX + 2U;
+ h1 += p[i];
+ }
+ uint32 h2 = 0;
+ p = (unsigned char *) &c;
+ for( size_t j = 0; j < sizeof(c); ++j )
+ {
+ h2 *= UCHAR_MAX + 2U;
+ h2 += p[j];
+ }
+ return ( h1 + differ++ ) ^ h2;
+}
+
+
+inline void MTRand::save( uint32* saveArray ) const
+{
+ register uint32 *sa = saveArray;
+ register const uint32 *s = state;
+ register int i = N;
+ for( ; i--; *sa++ = *s++ ) {}
+ *sa = left;
+}
+
+
+inline void MTRand::load( uint32 *const loadArray )
+{
+ register uint32 *s = state;
+ register uint32 *la = loadArray;
+ register int i = N;
+ for( ; i--; *s++ = *la++ ) {}
+ left = *la;
+ pNext = &state[N-left];
+}
+
+
+inline std::ostream& operator<<( std::ostream& os, const MTRand& mtrand )
+{
+ register const MTRand::uint32 *s = mtrand.state;
+ register int i = mtrand.N;
+ for( ; i--; os << *s++ << "\t" ) {}
+ return os << mtrand.left;
+}
+
+
+inline std::istream& operator>>( std::istream& is, MTRand& mtrand )
+{
+ register MTRand::uint32 *s = mtrand.state;
+ register int i = mtrand.N;
+ for( ; i--; is >> *s++ ) {}
+ is >> mtrand.left;
+ mtrand.pNext = &mtrand.state[mtrand.N-mtrand.left];
+ return is;
+}
+
+#endif // MERSENNETWISTER_H
+
+// Change log:
+//
+// v0.1 - First release on 15 May 2000
+// - Based on code by Makoto Matsumoto, Takuji Nishimura, and Shawn Cokus
+// - Translated from C to C++
+// - Made completely ANSI compliant
+// - Designed convenient interface for initialization, seeding, and
+// obtaining numbers in default or user-defined ranges
+// - Added automatic seeding from /dev/urandom or time() and clock()
+// - Provided functions for saving and loading generator state
+//
+// v0.2 - Fixed bug which reloaded generator one step too late
+//
+// v0.3 - Switched to clearer, faster reload() code from Matthew Bellew
+//
+// v0.4 - Removed trailing newline in saved generator format to be consistent
+// with output format of built-in types
+//
+// v0.5 - Improved portability by replacing static const int's with enum's and
+// clarifying return values in seed(); suggested by Eric Heimburg
+// - Removed MAXINT constant; use 0xffffffffUL instead
+//
+// v0.6 - Eliminated seed overflow when uint32 is larger than 32 bits
+// - Changed integer [0,n] generator to give better uniformity
+//
+// v0.7 - Fixed operator precedence ambiguity in reload()
+// - Added access for real numbers in (0,1) and (0,n)
+//
+// v0.8 - Included time.h header to properly support time_t and clock_t
+//
+// v1.0 - Revised seeding to match 26 Jan 2002 update of Nishimura and Matsumoto
+// - Allowed for seeding with arrays of any length
+// - Added access for real numbers in [0,1) with 53-bit resolution
+// - Added access for real numbers from normal (Gaussian) distributions
+// - Increased overall speed by optimizing twist()
+// - Doubled speed of integer [0,n] generation
+// - Fixed out-of-range number generation on 64-bit machines
+// - Improved portability by substituting literal constants for long enum's
+// - Changed license from GNU LGPL to BSD
diff --git a/tests/nl-vector-test.cpp b/tests/nl-vector-test.cpp
new file mode 100644
index 0000000..53e8eaa
--- /dev/null
+++ b/tests/nl-vector-test.cpp
@@ -0,0 +1,333 @@
+/** @file
+ * @brief Unit tests for Vector, VectorView
+ *//*
+ * Authors:
+ * Olof Bjarnason <olof.bjarnason@gmail.com>
+ *
+ * Copyright 2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <gtest/gtest.h>
+
+#include <2geom/numeric/vector.h>
+
+namespace Geom {
+
+////
+// Test fixture used in many tests.
+// v1 = [0, 1, ..., 8, 9]
+////
+class CountingVectorFixture : public ::testing::Test {
+public:
+ CountingVectorFixture() : v1(10) { }
+
+protected:
+ void SetUp() override {
+ for (unsigned int i = 0; i < this->v1.size(); ++i)
+ this->v1[i] = i;
+ }
+
+ NL::Vector v1;
+};
+
+// These types are only here to differentiate
+// between categories of tests - they both use
+// the same v1 test fixture variable.
+class VectorTest : public CountingVectorFixture { };
+class VectorViewTest : public CountingVectorFixture { };
+
+////
+// Helper method to write simple tests
+////
+NL::Vector V3(double a, double b, double c) {
+ NL::Vector v(3);
+ v[0] = a;
+ v[1] = b;
+ v[2] = c;
+ return v;
+}
+
+////
+// ** Vector examples **
+////
+
+TEST_F(VectorTest, VectorStringRepresentation) {
+ EXPECT_EQ(v1.str(), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]");
+}
+
+TEST_F(VectorTest, VectorConstructFromAnother) {
+ NL::Vector v2(v1);
+ EXPECT_EQ(v1.str(), v2.str());
+}
+
+TEST_F(VectorTest, OperatorEqualIsDefined) {
+ EXPECT_TRUE(v1 == v1);
+ NL::Vector v2(v1);
+ EXPECT_TRUE(v1 == v2);
+ // TODO: This operation compares doubles
+ // with operator ==. Should it use a distance
+ // threshold instead?
+}
+
+TEST_F(VectorTest, OperatorNotEqualIsntDefined) {
+ SUCCEED();
+ //NL::Vector v3(4);
+ //EXPECT_TRUE(v1 != v3); // Not expressible in C++;
+ // gives compile time error
+}
+
+TEST_F(VectorTest, VectorAssignment) {
+ NL::Vector v2(v1.size());
+ v2 = v1;
+ EXPECT_EQ(v1, v2);
+}
+
+#ifndef NDEBUG
+TEST_F(VectorTest, AssignedVectorMustBeSameSize) {
+ NL::Vector v2(5);
+ // On Linux, the assertion message is:
+ // Assertion ... failed ...
+ // On OSX, it is:
+ // Assertion failed: (...), function ..., file ..., line ...
+ // Thus we just look for the word "Assertion".
+ EXPECT_DEATH({v2 = v1;}, "Assertion");
+}
+#endif
+
+TEST_F(VectorTest, VectorScalesInplace) {
+ v1.scale(2);
+ EXPECT_EQ(v1.str(), "[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]");
+}
+
+TEST_F(VectorTest, VectorTranslatesInplace) {
+ v1.translate(1);
+ EXPECT_EQ(v1.str(), "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]");
+}
+
+TEST_F(VectorTest, ScaleAndTranslateUsesFluentSyntax) {
+ NL::VectorView vv(v1, 3);
+ EXPECT_EQ(vv.translate(5).scale(10).str(), "[50, 60, 70]");
+}
+
+TEST_F(VectorTest, AddAssignment) {
+ NL::Vector v2(v1);
+ v2 += v1;
+ EXPECT_EQ(v2.str(), "[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]");
+}
+
+TEST_F(VectorTest, SubtractAssignment) {
+ NL::Vector v2(v1);
+ v2 -= v1;
+ EXPECT_EQ(v2.str(), "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]");
+}
+
+TEST_F(VectorTest, SwappingElements) {
+ v1.swap_elements(0, 9);
+ EXPECT_EQ(v1.str(), "[9, 1, 2, 3, 4, 5, 6, 7, 8, 0]");
+}
+
+TEST_F(VectorTest, Reverse) {
+ v1.reverse();
+ EXPECT_EQ(v1.str(), "[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]");
+}
+
+TEST(Vector, IsPositive) {
+ EXPECT_TRUE(V3(1, 1, 1).is_positive());
+ EXPECT_FALSE(V3(0, 0, 0).is_positive());
+ EXPECT_FALSE(V3(-1, 0, 1).is_positive());
+}
+
+TEST_F(VectorTest, IsZero) {
+ EXPECT_FALSE(v1.is_zero());
+ EXPECT_TRUE(V3(0, 0, 0).is_zero());
+}
+
+TEST_F(VectorTest, IsNonNegative) {
+ EXPECT_TRUE(V3(1, 1, 1).is_non_negative());
+ EXPECT_TRUE(V3(0, 0, 0).is_non_negative());
+ EXPECT_FALSE(V3(-1, 1, 1).is_non_negative());
+}
+
+TEST(Vector, Max) {
+ EXPECT_EQ(V3(1, 5, 3).max(), 5);
+}
+
+TEST(Vector, MaxIndex) {
+ EXPECT_EQ(V3(1, 5, 3).max_index(), 1u);
+}
+
+TEST(Vector, Min) {
+ EXPECT_EQ(V3(1, -5, -300).min(), -300);
+}
+
+TEST(Vector, MinIndex) {
+ EXPECT_EQ(V3(1, 5, 3).min_index(), 0u);
+}
+
+TEST_F(VectorTest, SetAll) {
+ v1.set_all(5);
+ EXPECT_EQ(v1.str(), "[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]");
+}
+
+TEST_F(VectorTest, SetBasis) {
+ v1.set_basis(1);
+ EXPECT_EQ(v1.str(), "[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]");
+}
+
+TEST(Vector, SwappingVectors) {
+ NL::Vector a(V3(1, 2, 3));
+ NL::Vector b(V3(7, 7, 7));
+ NL::swap(a, b);
+ EXPECT_EQ(V3(7, 7, 7), a);
+ EXPECT_EQ(V3(1, 2, 3), b);
+}
+
+////
+// ** VectorView tests **
+////
+
+// Construction examples
+
+TEST_F(VectorViewTest, ViewCountOnly) {
+ // VectorView(vector, showCount)
+ EXPECT_EQ(NL::VectorView(v1, 5).str(), "[0, 1, 2, 3, 4]");
+}
+
+TEST_F(VectorViewTest, SkipSomeInitialElements) {
+ // VectorView(vector, showCount, startIndex)
+ EXPECT_EQ(NL::VectorView(v1, 5, 3).str(), "[3, 4, 5, 6, 7]");
+}
+
+TEST_F(VectorViewTest, SparseViewConstruction) {
+ // VectorView(vector, showCount, startIndex, step)
+ EXPECT_EQ(NL::VectorView(v1, 5, 0, 2).str(), "[0, 2, 4, 6, 8]");
+ EXPECT_EQ(NL::VectorView(v1, 5, 1, 2).str(), "[1, 3, 5, 7, 9]");
+}
+
+TEST_F(VectorViewTest, ConstructFromAnotherView) {
+ // VectorView(vectorview, showCount, startIndex, step)
+ NL::VectorView vv(v1, 5, 1, 2);
+ NL::VectorView view(vv, 3, 0, 2);
+ EXPECT_EQ(view.str(), "[1, 5, 9]");
+}
+
+// Operations modify source vectors
+
+TEST_F(VectorViewTest, PartialSourceModification) {
+ NL::VectorView vv(v1, 3);
+ vv.translate(10);
+ EXPECT_EQ(v1.str(),
+ "[10, 11, 12, 3, 4, 5, 6, 7, 8, 9]");
+ EXPECT_EQ(vv.str(),
+ "[10, 11, 12]");
+}
+
+// Scale and translate examples
+
+TEST_F(VectorViewTest, ViewScalesInplace) {
+ v1.scale(10);
+ EXPECT_EQ(NL::VectorView(v1, 3).str(), "[0, 10, 20]");
+}
+
+TEST_F(VectorViewTest, ViewScaleAndTranslateUsesFluentSyntax) {
+ EXPECT_EQ(NL::VectorView(v1, 3).scale(10).translate(1).str(),
+ "[1, 11, 21]");
+}
+
+// Assignment
+
+TEST_F(VectorViewTest, AssignmentFromVectorAvailableForViews) {
+ NL::VectorView vv(v1, v1.size());
+ vv = v1;
+ EXPECT_EQ(vv.str(), v1.str());
+}
+
+#ifndef NDEBUG
+TEST_F(VectorViewTest, AssignmentFromVectorMustBeSameSize) {
+ NL::VectorView vv(v1, 5);
+ EXPECT_DEATH({vv = v1;}, "Assertion");
+}
+#endif
+
+TEST_F(VectorViewTest, AssignmentFromViewAvailableForViews) {
+ NL::VectorView view1(v1, v1.size());
+ view1 = v1;
+ NL::VectorView view2(view1);
+ view2 = view1;
+ EXPECT_EQ(view1.str(), view2.str());
+}
+
+#ifndef NDEBUG
+TEST_F(VectorViewTest, AssignmentFromViewMustBeSameSize) {
+ NL::VectorView view1(v1, v1.size());
+ NL::VectorView view2(view1, view1.size() - 1);
+ EXPECT_DEATH({view2 = view1;}, "Assertion");
+}
+#endif
+
+// Add- and subtract assignment
+
+TEST_F(VectorViewTest, AddAssignAvailableForViews) {
+ NL::VectorView v2(v1);
+ v1 += v2;
+ EXPECT_EQ(v1.str(), "[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]");
+}
+
+TEST_F(VectorViewTest, SubtractAssignAvailableForViews) {
+ NL::Vector v2(v1);
+ v1 -= v2;
+ EXPECT_TRUE(v1.is_zero());
+}
+
+// View swapping
+
+TEST_F(VectorViewTest, SwappingFromSameSourceVectorDoesNotModifySource) {
+ NL::VectorView vv1(v1, 2, 0);
+ NL::VectorView vv2(v1, 2, 8);
+ NL::swap_view(vv1, vv2);
+ EXPECT_EQ(v1.str(), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]");
+}
+
+TEST_F(VectorViewTest, SwappingFromSameSourceVectorModifiesViews) {
+ NL::VectorView viewStart(v1, 2, 0);
+ NL::VectorView viewEnd(v1, 2, 8);
+ EXPECT_EQ(viewStart.str(), "[0, 1]");
+ EXPECT_EQ(viewEnd.str(), "[8, 9]");
+ NL::swap_view(viewStart, viewEnd);
+ EXPECT_EQ(viewStart.str(), "[8, 9]");
+ EXPECT_EQ(viewEnd.str(), "[0, 1]");
+}
+
+#ifndef NDEBUG
+TEST_F(VectorViewTest, SwappingDifferentLengthViewFails) {
+ NL::VectorView vv1(v1, 4);
+ NL::VectorView vv2(v1, 3);
+ EXPECT_DEATH({NL::swap_view(vv1, vv2);}, "Assertion");
+}
+#endif
+
+
+} // namespace Geom
diff --git a/tests/parallelogram-test.cpp b/tests/parallelogram-test.cpp
new file mode 100644
index 0000000..70ccea1
--- /dev/null
+++ b/tests/parallelogram-test.cpp
@@ -0,0 +1,161 @@
+/** @file
+ * @brief Unit tests for Parallelogram
+ *
+ * Includes all tests from RotatedRect to demonstrate that it is a generalized
+ * version of the rotated rectangle.
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ * Sergei Izmailov
+ *
+ * SPDX-License-Identifier: LGPL-2.1 or MPL-1.1
+ */
+
+#include <2geom/coord.h>
+#include <2geom/parallelogram.h>
+#include <2geom/transforms.h>
+
+#include <gtest/gtest.h>
+
+using namespace Geom;
+
+// Analogous to RotatedRect::from_rect_rotate
+static Parallelogram parallelogram_from_rect_rotate(Rect const &rect, Rotate const &rotate, Point const &point)
+{
+ Affine affine = Translate(-point) * rotate * Translate(point);
+ return Parallelogram(rect) * affine;
+}
+static Parallelogram parallelogram_from_rect_rotate(Rect const &rect, Rotate const &rotate)
+{
+ return parallelogram_from_rect_rotate(rect, rotate, rect.midpoint());
+}
+
+TEST(ParallelogramTest, midpoint)
+{
+ Rect r(-0.5, -0.5, 5.5, 5.5);
+ auto center = Point(2.5, 2.5);
+
+ EXPECT_EQ(r.midpoint(), center);
+ for (double angle : { 0, 1, 25, 45, 90, 135 }) {
+ auto rotated_rect = parallelogram_from_rect_rotate(r, Rotate::from_degrees(angle), Point(0, 0));
+ auto rotated_center = center * Rotate(angle / 180.0 * M_PI);
+ EXPECT_TRUE(Geom::are_near(rotated_rect.midpoint(), rotated_center, 1e-6)) << "Angle = " << angle << " deg";
+ }
+}
+
+TEST(ParallelogramTest, containsPoint1)
+{
+ Rect r(0, 0, 1, 1);
+ auto rotated_rect = r;
+ EXPECT_TRUE(rotated_rect.contains(Point(0, 0)));
+ EXPECT_TRUE(rotated_rect.contains(Point(1, 1)));
+ EXPECT_TRUE(rotated_rect.contains(Point(0.5, 0.5)));
+ EXPECT_FALSE(rotated_rect.contains(Point(1.1, 0.5)));
+ EXPECT_FALSE(rotated_rect.contains(Point(0.5, 1.1)));
+}
+
+TEST(ParallelogramTest, containsPoint2)
+{
+ Rect r(0, 0, 1, 1);
+ auto rotated_rect = parallelogram_from_rect_rotate(r, Rotate::from_degrees(45), Point(0, 0));
+ EXPECT_TRUE(rotated_rect.contains(Point(0, 0)));
+ EXPECT_TRUE(rotated_rect.contains(Point(0, 1.2)));
+ EXPECT_TRUE(rotated_rect.contains(Point(0.5, 0.9)));
+ EXPECT_FALSE(rotated_rect.contains(Point(1, 1)));
+ EXPECT_FALSE(rotated_rect.contains(Point(0.1, 0)));
+}
+
+TEST(ParallelogramTest, intersects_aligned)
+{
+ Rect r(0, 0, 1, 1);
+ auto rotated_rect = r;
+ // point within rect
+ EXPECT_TRUE(rotated_rect.intersects(Rect(-1, -1, 2, 2)));
+ EXPECT_TRUE(rotated_rect.intersects(Rect(0.1, 0.1, 0.2, 0.2)));
+ EXPECT_TRUE(rotated_rect.intersects(Rect(-0.1, -0.1, 0.1, 0.1)));
+ EXPECT_FALSE(rotated_rect.intersects(Rect(-0.2, -0.2, -0.1, -0.1)));
+ EXPECT_FALSE(rotated_rect.intersects(Rect(1.1, 1.1, 1.2, 1.2)));
+ // edge intersection
+ EXPECT_TRUE(rotated_rect.intersects(Rect(0.5, -0.1, 0.6, 1.2)));
+ EXPECT_TRUE(rotated_rect.intersects(Rect(-0.1, 0.5, 1.2, 0.6)));
+}
+
+TEST(ParallelogramTest, bounds)
+{
+ auto r = Rect::from_xywh(1.260, 0.547, 8.523, 11.932);
+ auto rrect = parallelogram_from_rect_rotate(r, Rotate::from_degrees(15.59));
+ auto bbox = rrect.bounds();
+ auto expected_bbox = Rect::from_xywh(-0.186, -0.378, 11.415, 13.783);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_TRUE(Geom::are_near(bbox.corner(i), expected_bbox.corner(i), 1e-3));
+ }
+}
+
+TEST(ParallelogramTest, isSheared)
+{
+ Parallelogram p(Rect(2, 4, 7, 8));
+ EXPECT_FALSE(p.isSheared());
+ p *= Rotate(M_PI / 4.0); // 45°
+ EXPECT_FALSE(p.isSheared());
+ p *= HShear(2);
+ EXPECT_TRUE(p.isSheared());
+}
+
+TEST(ParallelogramTest, area)
+{
+ Rect r(2, 4, 7, 8);
+ Parallelogram p(r);
+ EXPECT_DOUBLE_EQ(p.area(), r.area());
+ p *= Rotate(M_PI / 4.0); // 45°
+ EXPECT_DOUBLE_EQ(p.area(), r.area());
+ p *= HShear(2);
+ EXPECT_DOUBLE_EQ(p.area(), r.area());
+ p *= Scale(2);
+ EXPECT_DOUBLE_EQ(p.area(), r.area() * 4);
+}
+
+class ParallelogramTest
+ : public testing::TestWithParam<std::tuple<Rect /*rect*/, double /*degrees*/, bool /*intersects*/>> {
+
+ void SetUp() override { target = Rect::from_xywh(0, 0, 11, 13); }
+
+ public:
+ Rect target;
+};
+
+TEST_P(ParallelogramTest, intersects)
+{
+ Rect rect;
+ double degrees;
+ bool intersects;
+ std::tie(rect, degrees, intersects) = GetParam();
+ EXPECT_EQ(parallelogram_from_rect_rotate(rect, Rotate::from_degrees(degrees)).intersects(target), intersects)
+ << "ERROR: rect {" << rect << "} rotated by {" << degrees << "} degrees " << (!intersects ? "" : "NOT ")
+ << "intersects with {" << target << "} but MUST " << (intersects ? "" : "NOT");
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(intesect_non_aligned, ParallelogramTest,
+ testing::Values(
+ std::make_tuple(Rect::from_xywh(10.456, -4.479, 7, 5), 0, true),
+ std::make_tuple(Rect::from_xywh(10.456, -4.479, 7, 5), 15, false),
+ std::make_tuple(Rect::from_xywh(9.929, 12.313, 7, 5), 93.2, false),
+ std::make_tuple(Rect::from_xywh(9.929, 12.313, 7, 5), 91.37, true),
+ std::make_tuple(Rect::from_xywh(-1, 4, 13, 3), 0, true),
+ std::make_tuple(Rect::from_xywh(4, -2, 3, 16), 0, true),
+ std::make_tuple(Rect::from_xywh(-5.113, -3.283, 5.000, 7.000), 11.81, false),
+ std::make_tuple(Rect::from_xywh(-5.113, -3.283, 5.000, 7.000), 13.35, true),
+ std::make_tuple(Rect::from_xywh(1.260, 0.547, 8.523, 11.932), 15.59, true),
+ std::make_tuple(Rect::from_xywh(5.328, 0.404, 11, 2), 28.16, true),
+ std::make_tuple(Rect::from_xywh(4.853, 10.691, 11, 2), -30.4, true),
+ std::make_tuple(Rect::from_xywh(-4.429, 10.752, 11, 2), 29.7, true),
+ std::make_tuple(Rect::from_xywh(-4.538, 0.314, 11, 2), -34.19, true),
+ std::make_tuple(Rect::from_xywh(8.398, -3.790, 2, 11), -34, true),
+ std::make_tuple(Rect::from_xywh(8.614, 6.163, 2, 11), 30.38, true),
+ std::make_tuple(Rect::from_xywh(0.492, 6.904, 2, 11), -37.29, true),
+ std::make_tuple(Rect::from_xywh(0.202, -3.148, 2, 11), 31.12, true)));
+
+// clang-format on
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/parser-test.py b/tests/parser-test.py
new file mode 100644
index 0000000..86b2deb
--- /dev/null
+++ b/tests/parser-test.py
@@ -0,0 +1,94 @@
+# * A simple toy to test the parser
+# *
+# * Copyright 2008 Aaron Spike <aaron@ekips.org>
+# *
+# * This library is free software; you can redistribute it and/or
+# * modify it either under the terms of the GNU Lesser General Public
+# * License version 2.1 as published by the Free Software Foundation
+# * (the "LGPL") or, at your option, under the terms of the Mozilla
+# * Public License Version 1.1 (the "MPL"). If you do not alter this
+# * notice, a recipient may use your version of this file under either
+# * the MPL or the LGPL.
+# *
+# * You should have received a copy of the LGPL along with this library
+# * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# * You should have received a copy of the MPL along with this library
+# * in the file COPYING-MPL-1.1
+# *
+# * The contents of this file are subject to the Mozilla Public License
+# * Version 1.1 (the "License"); you may not use this file except in
+# * compliance with the License. You may obtain a copy of the License at
+# * http://www.mozilla.org/MPL/
+# *
+# * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+# * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+# * the specific language governing rights and limitations.
+
+import sys
+sys.path.append(os.path.join(os.path.dirname(__file__), "..", "py2geom"))
+import py2geom
+
+class TestSink(py2geom.SVGPathSink):
+ def __init__(self):
+ py2geom.SVGPathSink.__init__(self)
+ self.data = []
+ def __str__(self):
+ return ' '.join(self.data)
+ def moveTo(self, p):
+ x,y = p
+ self.data.append('M %s, %s' % (x,y))
+ def lineTo(self, p):
+ x,y = p
+ self.data.append('L %s, %s' % (x,y))
+ def curveTo(self, c0, c1, p):
+ c0x,c0y = c0
+ c1x,c1y = c1
+ x,y = p
+ self.data.append('C %s, %s %s, %s %s, %s' % (c0x,c0y,c1x,c1y,x,y))
+ def quadTo(self, c, p):
+ cx,cy = c
+ x,y = p
+ self.data.append('Q %s, %s %s, %s' % (cx,cy,x,y))
+ def arcTo(self, rx, ry, angle, large_arc, sweep, p):
+ x,y = p
+ self.data.append('A %s, %s %s %i %i %s, %s' % (rx,ry,angle,large_arc,sweep,x,y))
+ def closePath(self):
+ self.data.append('Z')
+ def flush(self):
+ pass
+
+def test_path(description, in_path, out_path):
+ s = TestSink()
+ py2geom.parse_svg_path(in_path, s)
+ if str(s) == out_path:
+ print 'Success: %s' % description
+ return True
+ else:
+ print 'Error: %s' % description
+ print ' given "%s"' % in_path
+ print ' got "%s"' % str(s)
+ print ' expected "%s"' % out_path
+ return False
+
+def run_tests(tests):
+ successes = 0
+ failures = 0
+ for description, in_path, out_path in tests:
+ if test_path(description, in_path, out_path):
+ successes += 1
+ else:
+ failures += 1
+ print '=' * 20
+ print 'Tests: %s' % (successes + failures)
+ print 'Good: %s' % successes
+ print 'Bad: %s' % failures
+
+if __name__=='__main__':
+ tests = [
+ ('lineto', 'M 10,10 L 4,4', 'M 10.0, 10.0 L 4.0, 4.0'),
+ ('implicit lineto', 'M 10,10 L 4,4 5,5 6,6', 'M 10.0, 10.0 L 4.0, 4.0 L 5.0, 5.0 L 6.0, 6.0'),
+ ('implicit lineto after moveto', 'M1.2.3.4.5.6.7', 'M 1.2, 0.3 L 0.4, 0.5 L 0.6, 0.7'),
+ ('arcto', 'M 300 150 A 150, 120, 30, 1, 0, 200 100', 'M 300.0, 150.0 A 150.0, 120.0 30.0 1 0 200.0, 100.0'),
+ ]
+ run_tests(tests)
diff --git a/tests/path-test.cpp b/tests/path-test.cpp
new file mode 100644
index 0000000..dd6f347
--- /dev/null
+++ b/tests/path-test.cpp
@@ -0,0 +1,991 @@
+#include <cmath>
+#include <vector>
+#include <iterator>
+#include <iostream>
+
+#include <glib.h>
+
+#include <2geom/bezier.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+#include <2geom/path-intersection.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/svg-path-writer.h>
+
+#include "testing.h"
+
+using namespace std;
+using namespace Geom;
+
+Path string_to_path(const char* s) {
+ PathVector pv = parse_svg_path(s);
+ assert(pv.size() == 1);
+ return pv[0];
+}
+
+// Path fixture
+class PathTest : public ::testing::Test {
+protected:
+ PathTest() {
+ line.append(LineSegment(Point(0,0), Point(1,0)));
+ square = string_to_path("M 0,0 1,0 1,1 0,1 z");
+ circle = string_to_path("M 0,0 a 4.5,4.5 0 1 1 -9,0 4.5,4.5 0 1 1 9,0 z");
+ arcs = string_to_path("M 0,0 a 5,10 45 0 1 10,10 a 5,10 45 0 1 0,0 z");
+ diederik = string_to_path("m 262.6037,35.824151 c 0,0 -92.64892,-187.405851 30,-149.999981 104.06976,31.739531 170,109.9999815 170,109.9999815 l -10,-59.9999905 c 0,0 40,79.99999 -40,79.99999 -80,0 -70,-129.999981 -70,-129.999981 l 50,0 C 435.13571,-131.5667 652.76275,126.44872 505.74322,108.05672 358.73876,89.666591 292.6037,-14.175849 292.6037,15.824151 c 0,30 -30,20 -30,20 z");
+ cmds = string_to_path("M 0,0 V 100 H 100 Q 100,0 0,0 L 200,0 C 200,100 300,100 300,0 S 200,-100 200,0");
+
+ p_open = string_to_path("M 0,0 L 0,5 5,5 5,0");
+ p_closed = p_open;
+ p_closed.close(true);
+ p_add = string_to_path("M -1,6 L 6,6");
+
+ p_open.setStitching(true);
+ p_closed.setStitching(true);
+ }
+
+ // Objects declared here can be used by all tests in the test case for Foo.
+ Path line, square, circle, arcs, diederik, cmds;
+ Path p_open, p_closed, p_add;
+};
+
+TEST_F(PathTest, CopyConstruction) {
+ Path pa = p_closed;
+ Path pc(p_closed);
+ EXPECT_EQ(pa, p_closed);
+ EXPECT_EQ(pa.closed(), p_closed.closed());
+ EXPECT_EQ(pc, p_closed);
+ EXPECT_EQ(pc.closed(), p_closed.closed());
+
+ Path poa = cmds;
+ Path poc(cmds);
+ EXPECT_EQ(poa, cmds);
+ EXPECT_EQ(poa.closed(), cmds.closed());
+ EXPECT_EQ(poc, cmds);
+ EXPECT_EQ(poc.closed(), cmds.closed());
+
+ PathVector pvc(pa);
+ EXPECT_EQ(pvc[0], pa);
+ PathVector pva((Geom::Path()));
+ pva[0] = pa;
+ EXPECT_EQ(pva[0], pa);
+}
+
+TEST_F(PathTest, PathInterval) {
+ PathTime n2_before(1, 0.9995), n2_after(2, 0.0005),
+ n3_before(2, 0.9995), n3_after(3, 0.0005),
+ mid2(2, 0.5), mid3(3, 0.5);
+
+ // ival[x][0] - normal
+ // ival[x][1] - reversed
+ // ival[x][2] - crosses start
+ // ival[x][3] - reversed, crosses start
+ PathInterval ival[5][4];
+
+ ival[0][0] = PathInterval(n2_before, n2_after, false, 4);
+ ival[0][1] = PathInterval(n2_after, n2_before, false, 4);
+ ival[0][2] = PathInterval(n2_before, n2_after, true, 4);
+ ival[0][3] = PathInterval(n2_after, n2_before, true, 4);
+ ival[1][0] = PathInterval(n2_before, n3_after, false, 4);
+ ival[1][1] = PathInterval(n3_after, n2_before, false, 4);
+ ival[1][2] = PathInterval(n2_before, n3_after, true, 4);
+ ival[1][3] = PathInterval(n3_after, n2_before, true, 4);
+ ival[2][0] = PathInterval(n2_before, mid2, false, 4);
+ ival[2][1] = PathInterval(mid2, n2_before, false, 4);
+ ival[2][2] = PathInterval(n2_before, mid2, true, 4);
+ ival[2][3] = PathInterval(mid2, n2_before, true, 4);
+ ival[3][0] = PathInterval(mid2, mid3, false, 4);
+ ival[3][1] = PathInterval(mid3, mid2, false, 4);
+ ival[3][2] = PathInterval(mid2, mid3, true, 4);
+ ival[3][3] = PathInterval(mid3, mid2, true, 4);
+ ival[4][0] = PathInterval(n2_after, n3_before, false, 4);
+ ival[4][1] = PathInterval(n3_before, n2_after, false, 4);
+ ival[4][2] = PathInterval(n2_after, n3_before, true, 4);
+ ival[4][3] = PathInterval(n3_before, n2_after, true, 4);
+
+ EXPECT_TRUE(ival[0][0].contains(n2_before));
+ EXPECT_TRUE(ival[0][0].contains(n2_after));
+ EXPECT_TRUE(ival[0][1].contains(n2_before));
+ EXPECT_TRUE(ival[0][1].contains(n2_after));
+
+ for (unsigned i = 0; i <= 4; ++i) {
+ EXPECT_FALSE(ival[i][0].reverse());
+ EXPECT_TRUE(ival[i][1].reverse());
+ EXPECT_TRUE(ival[i][2].reverse());
+ EXPECT_FALSE(ival[i][3].reverse());
+ }
+
+ for (unsigned i = 0; i <= 4; ++i) {
+ for (unsigned j = 0; j <= 3; ++j) {
+ //std::cout << i << " " << j << " " << ival[i][j] << std::endl;
+ EXPECT_TRUE(ival[i][j].contains(ival[i][j].inside(1e-3)));
+ }
+ }
+
+ PathTime n1(1, 0.0), n1x(0, 1.0),
+ n2(2, 0.0), n2x(1, 1.0),
+ n3(3, 0.0), n3x(2, 1.0);
+ PathTime tests[8] = { n1, n1x, n2, n2x, n3, n3x, mid2, mid3 };
+
+ // 0: false for both
+ // 1: true for normal, false for cross_start
+ // 2: false for normal, true for cross_start
+ // 3: true for both
+
+ int const NORMAL = 1, CROSS = 2, BOTH = 3;
+
+ int includes[5][8] = {
+ { CROSS, CROSS, NORMAL, NORMAL, CROSS, CROSS, CROSS, CROSS },
+ { CROSS, CROSS, NORMAL, NORMAL, NORMAL, NORMAL, NORMAL, CROSS },
+ { CROSS, CROSS, NORMAL, NORMAL, CROSS, CROSS, BOTH, CROSS },
+ { CROSS, CROSS, CROSS, CROSS, NORMAL, NORMAL, BOTH, BOTH },
+ { CROSS, CROSS, CROSS, CROSS, CROSS, CROSS, NORMAL, CROSS }
+ };
+ unsigned sizes[5][2] = {
+ { 2, 4 },
+ { 3, 3 },
+ { 2, 4 },
+ { 2, 4 },
+ { 1, 5 }
+ };
+
+ for (unsigned i = 0; i < 5; ++i) {
+ for (unsigned j = 0; j < 8; ++j) {
+ EXPECT_EQ(ival[i][0].contains(tests[j]), bool(includes[i][j] & NORMAL));
+ EXPECT_EQ(ival[i][1].contains(tests[j]), bool(includes[i][j] & NORMAL));
+ EXPECT_EQ(ival[i][2].contains(tests[j]), bool(includes[i][j] & CROSS));
+ EXPECT_EQ(ival[i][3].contains(tests[j]), bool(includes[i][j] & CROSS));
+ }
+ EXPECT_EQ(ival[i][0].curveCount(), sizes[i][0]);
+ EXPECT_EQ(ival[i][1].curveCount(), sizes[i][0]);
+ EXPECT_EQ(ival[i][2].curveCount(), sizes[i][1]);
+ EXPECT_EQ(ival[i][3].curveCount(), sizes[i][1]);
+ }
+}
+
+TEST_F(PathTest, Continuity) {
+ line.checkContinuity();
+ square.checkContinuity();
+ circle.checkContinuity();
+ diederik.checkContinuity();
+ cmds.checkContinuity();
+}
+
+TEST_F(PathTest, RectConstructor) {
+ Rect r(Point(0,0), Point(10,10));
+ Path rpath(r);
+
+ EXPECT_EQ(rpath.size(), 4u);
+ EXPECT_TRUE(rpath.closed());
+ for (unsigned i = 0; i < 4; ++i) {
+ EXPECT_TRUE(dynamic_cast<LineSegment const *>(&rpath[i]) != NULL);
+ EXPECT_EQ(rpath[i].initialPoint(), r.corner(i));
+ }
+}
+
+TEST_F(PathTest, Reversed) {
+ std::vector<Path> a, r;
+ a.push_back(p_open);
+ a.push_back(p_closed);
+ a.push_back(circle);
+ a.push_back(diederik);
+ a.push_back(cmds);
+
+ for (auto & i : a) {
+ r.push_back(i.reversed());
+ }
+
+ for (unsigned i = 0; i < a.size(); ++i) {
+ EXPECT_EQ(r[i].size(), a[i].size());
+ EXPECT_EQ(r[i].initialPoint(), a[i].finalPoint());
+ EXPECT_EQ(r[i].finalPoint(), a[i].initialPoint());
+ EXPECT_EQ(r[i].reversed(), a[i]);
+ Point p1 = r[i].pointAt(0.75);
+ Point p2 = a[i].pointAt(a[i].size() - 0.75);
+ EXPECT_FLOAT_EQ(p1[X], p2[X]);
+ EXPECT_FLOAT_EQ(p1[Y], p2[Y]);
+ EXPECT_EQ(r[i].closed(), a[i].closed());
+ a[i].checkContinuity();
+ }
+}
+
+TEST_F(PathTest, ValueAt) {
+ EXPECT_EQ(Point(0,0), line.initialPoint());
+ EXPECT_EQ(Point(1,0), line.finalPoint());
+
+ EXPECT_EQ(Point(0.5, 0.0), line.pointAt(0.5));
+
+ EXPECT_EQ(Point(0,0), square.initialPoint());
+ EXPECT_EQ(Point(0,0), square.finalPoint());
+ EXPECT_EQ(Point(1,0), square.pointAt(1));
+ EXPECT_EQ(Point(0.5,1), square.pointAt(2.5));
+ EXPECT_EQ(Point(0,0.5), square.pointAt(3.5));
+ EXPECT_EQ(Point(0,0), square.pointAt(4));
+}
+
+TEST_F(PathTest, NearestPoint) {
+ EXPECT_EQ(0, line.nearestTime(Point(0,0)).asFlatTime());
+ EXPECT_EQ(0.5, line.nearestTime(Point(0.5,0)).asFlatTime());
+ EXPECT_EQ(0.5, line.nearestTime(Point(0.5,1)).asFlatTime());
+ EXPECT_EQ(1, line.nearestTime(Point(100,0)).asFlatTime());
+ EXPECT_EQ(0, line.nearestTime(Point(-100,1000)).asFlatTime());
+
+ EXPECT_EQ(0, square.nearestTime(Point(0,0)).asFlatTime());
+ EXPECT_EQ(1, square.nearestTime(Point(1,0)).asFlatTime());
+ EXPECT_EQ(3, square.nearestTime(Point(0,1)).asFlatTime());
+
+ //cout << diederik.nearestTime(Point(247.32293,-43.339507)) << endl;
+
+ Point p(511.75,40.85);
+ EXPECT_FLOAT_EQ(6.5814033, diederik.nearestTime(p).asFlatTime());
+ /*cout << diederik.pointAt(diederik.nearestTime(p)) << endl
+ << diederik.pointAt(6.5814033) << endl
+ << distance(diederik.pointAt(diederik.nearestTime(p)), p) << " "
+ << distance(diederik.pointAt(6.5814033), p) << endl;*/
+
+}
+
+TEST_F(PathTest, Winding) {
+ // test points in special positions
+ EXPECT_EQ(line.winding(Point(-1, 0)), 0);
+ EXPECT_EQ(line.winding(Point(2, 0)), 0);
+ EXPECT_EQ(line.winding(Point(0, 1)), 0);
+ EXPECT_EQ(line.winding(Point(0, -1)), 0);
+ EXPECT_EQ(line.winding(Point(1, 1)), 0);
+ EXPECT_EQ(line.winding(Point(1, -1)), 0);
+
+ EXPECT_EQ(square.winding(Point(0, -1)), 0);
+ EXPECT_EQ(square.winding(Point(1, -1)), 0);
+ EXPECT_EQ(square.winding(Point(0, 2)), 0);
+ EXPECT_EQ(square.winding(Point(1, 2)), 0);
+ EXPECT_EQ(square.winding(Point(-1, 0)), 0);
+ EXPECT_EQ(square.winding(Point(-1, 1)), 0);
+ EXPECT_EQ(square.winding(Point(2, 0)), 0);
+ EXPECT_EQ(square.winding(Point(2, 1)), 0);
+ EXPECT_EQ(square.winding(Point(0.5, 0.5)), 1);
+
+ EXPECT_EQ(circle.winding(Point(-4.5,0)), 1);
+ EXPECT_EQ(circle.winding(Point(-3.5,0)), 1);
+ EXPECT_EQ(circle.winding(Point(-4.5,1)), 1);
+ EXPECT_EQ(circle.winding(Point(-10,0)), 0);
+ EXPECT_EQ(circle.winding(Point(1,0)), 0);
+
+ Path yellipse = string_to_path("M 0,0 A 40 20 90 0 0 0,-80 40 20 90 0 0 0,0 z");
+ EXPECT_EQ(yellipse.winding(Point(-1, 0)), 0);
+ EXPECT_EQ(yellipse.winding(Point(-1, -80)), 0);
+ EXPECT_EQ(yellipse.winding(Point(1, 0)), 0);
+ EXPECT_EQ(yellipse.winding(Point(1, -80)), 0);
+ EXPECT_EQ(yellipse.winding(Point(0, -40)), -1);
+ std::vector<double> r[4];
+ r[0] = yellipse[0].roots(0, Y);
+ r[1] = yellipse[0].roots(-80, Y);
+ r[2] = yellipse[1].roots(0, Y);
+ r[3] = yellipse[1].roots(-80, Y);
+ for (auto & i : r) {
+ for (double j : i) {
+ std::cout << format_coord_nice(j) << " ";
+ }
+ std::cout << std::endl;
+ }
+ std::cout << yellipse[0].unitTangentAt(0) << " "
+ << yellipse[0].unitTangentAt(1) << " "
+ << yellipse[1].unitTangentAt(0) << " "
+ << yellipse[1].unitTangentAt(1) << std::endl;
+
+ Path half_ellipse = string_to_path("M 0,0 A 40 20 90 0 0 0,-80 L -20,-40 z");
+ EXPECT_EQ(half_ellipse.winding(Point(-1, 0)), 0);
+ EXPECT_EQ(half_ellipse.winding(Point(-1, -80)), 0);
+ EXPECT_EQ(half_ellipse.winding(Point(1, 0)), 0);
+ EXPECT_EQ(half_ellipse.winding(Point(1, -80)), 0);
+ EXPECT_EQ(half_ellipse.winding(Point(0, -40)), -1);
+
+ // extra nasty cases with exact double roots
+ Path hump = string_to_path("M 0,0 Q 1,1 2,0 L 2,2 0,2 Z");
+ EXPECT_EQ(hump.winding(Point(0.25, 0.5)), 1);
+ EXPECT_EQ(hump.winding(Point(1.75, 0.5)), 1);
+
+ Path hump2 = string_to_path("M 0,0 L 2,0 2,2 Q 1,1 0,2 Z");
+ EXPECT_EQ(hump2.winding(Point(0.25, 1.5)), 1);
+ EXPECT_EQ(hump2.winding(Point(1.75, 1.5)), 1);
+}
+
+/// Regression test for issue https://gitlab.com/inkscape/lib2geom/-/issues/58
+TEST_F(PathTest, Issue58)
+{
+ auto const random_point_in = [](Geom::Rect const &box) -> Point {
+ Coord const x = g_random_double_range(box[X].min(), box[X].max());
+ Coord const y = g_random_double_range(box[Y].min(), box[Y].max());
+ return {x, y};
+ };
+
+ auto const verify_windings = [](Ellipse const &e, Path const &path, Point const &pt) {
+ int const winding = path.winding(pt);
+ if (e.contains(pt)) {
+ EXPECT_EQ(winding, 1);
+ } else {
+ EXPECT_EQ(winding, 0);
+ }
+ };
+
+ // Example elliptical path from issue https://gitlab.com/inkscape/lib2geom/-/issues/58
+ char const *const issue_d = "M 495.8157837290847 280.07459226562503"
+ "A 166.63407933993605 132.04407218873035 0 0 1 329.1817043891487 412.11866445435544"
+ "A 166.63407933993605 132.04407218873035 0 0 1 162.54762504921263 280.07459226562503"
+ "A 166.63407933993605 132.04407218873035 0 0 1 329.1817043891487 148.0305200768947"
+ "A 166.63407933993605 132.04407218873035 0 0 1 495.8157837290847 280.07459226562503"
+ "z";
+ auto const pv = parse_svg_path(issue_d);
+ auto const issue_ellipse = Ellipse(Point(329.1817043891487, 280.07459226562503),
+ Point(166.63407933993605, 132.04407218873035), 0);
+
+ auto box = issue_ellipse.boundsExact();
+ box.expandBy(1.0);
+
+ g_random_set_seed(0xE111BB5E);
+ for (size_t _ = 0; _ < 10'000; _++) {
+ verify_windings(issue_ellipse, pv[0], random_point_in(box));
+ }
+}
+
+TEST_F(PathTest, SVGRoundtrip) {
+ SVGPathWriter sw;
+
+ Path transformed = diederik * (Rotate(1.23456789) * Scale(1e-8) * Translate(1e-9, 1e-9));
+
+ for (unsigned i = 0; i < 4; ++i) {
+ sw.setOptimize(i & 1);
+ sw.setUseShorthands(i & 2);
+
+ sw.feed(line);
+ //cout << sw.str() << endl;
+ Path line_svg = string_to_path(sw.str().c_str());
+ EXPECT_TRUE(line_svg == line);
+ sw.clear();
+
+ sw.feed(square);
+ //cout << sw.str() << endl;
+ Path square_svg = string_to_path(sw.str().c_str());
+ EXPECT_TRUE(square_svg == square);
+ sw.clear();
+
+ sw.feed(circle);
+ //cout << sw.str() << endl;
+ Path circle_svg = string_to_path(sw.str().c_str());
+ EXPECT_TRUE(circle_svg == circle);
+ sw.clear();
+
+ sw.feed(arcs);
+ //cout << sw.str() << endl;
+ Path arcs_svg = string_to_path(sw.str().c_str());
+ EXPECT_TRUE(arcs_svg == arcs);
+ sw.clear();
+
+ sw.feed(diederik);
+ //cout << sw.str() << endl;
+ Path diederik_svg = string_to_path(sw.str().c_str());
+ EXPECT_TRUE(diederik_svg == diederik);
+ sw.clear();
+
+ sw.feed(transformed);
+ //cout << sw.str() << endl;
+ Path transformed_svg = string_to_path(sw.str().c_str());
+ EXPECT_TRUE(transformed_svg == transformed);
+ sw.clear();
+
+ sw.feed(cmds);
+ //cout << sw.str() << endl;
+ Path cmds_svg = string_to_path(sw.str().c_str());
+ EXPECT_TRUE(cmds_svg == cmds);
+ sw.clear();
+ }
+}
+
+TEST_F(PathTest, Portion) {
+ PathTime a(0, 0.5), b(3, 0.5);
+ PathTime c(1, 0.25), d(1, 0.75);
+
+ EXPECT_EQ(square.portion(a, b), string_to_path("M 0.5, 0 L 1,0 1,1 0,1 0,0.5"));
+ EXPECT_EQ(square.portion(b, a), string_to_path("M 0,0.5 L 0,1 1,1 1,0 0.5,0"));
+ EXPECT_EQ(square.portion(a, b, true), string_to_path("M 0.5,0 L 0,0 0,0.5"));
+ EXPECT_EQ(square.portion(b, a, true), string_to_path("M 0,0.5 L 0,0 0.5,0"));
+ EXPECT_EQ(square.portion(c, d), string_to_path("M 1,0.25 L 1,0.75"));
+ EXPECT_EQ(square.portion(d, c), string_to_path("M 1,0.75 L 1,0.25"));
+ EXPECT_EQ(square.portion(c, d, true), string_to_path("M 1,0.25 L 1,0 0,0 0,1 1,1 1,0.75"));
+ EXPECT_EQ(square.portion(d, c, true), string_to_path("M 1,0.75 L 1,1 0,1 0,0 1,0 1,0.25"));
+
+ // verify that no matter how an endpoint is specified, the result is the same
+ PathTime a1(0, 1.0), a2(1, 0.0);
+ PathTime b1(2, 1.0), b2(3, 0.0);
+ Path result = string_to_path("M 1,0 L 1,1 0,1");
+ EXPECT_EQ(square.portion(a1, b1), result);
+ EXPECT_EQ(square.portion(a1, b2), result);
+ EXPECT_EQ(square.portion(a2, b1), result);
+ EXPECT_EQ(square.portion(a2, b2), result);
+}
+
+TEST_F(PathTest, AppendSegment) {
+ Path p_open = line, p_closed = line;
+ p_open.setStitching(true);
+ p_open.append(new LineSegment(Point(10,20), Point(10,25)));
+ EXPECT_EQ(p_open.size(), 3u);
+ EXPECT_NO_THROW(p_open.checkContinuity());
+
+ p_closed.setStitching(true);
+ p_closed.close(true);
+ p_closed.append(new LineSegment(Point(10,20), Point(10,25)));
+ EXPECT_EQ(p_closed.size(), 4u);
+ EXPECT_NO_THROW(p_closed.checkContinuity());
+}
+
+TEST_F(PathTest, AppendPath) {
+ p_open.append(p_add);
+ Path p_expected = string_to_path("M 0,0 L 0,5 5,5 5,0 -1,6 6,6");
+ EXPECT_EQ(p_open.size(), 5u);
+ EXPECT_EQ(p_open, p_expected);
+ EXPECT_NO_THROW(p_open.checkContinuity());
+
+ p_expected.close(true);
+ p_closed.append(p_add);
+ EXPECT_EQ(p_closed.size(), 6u);
+ EXPECT_EQ(p_closed, p_expected);
+ EXPECT_NO_THROW(p_closed.checkContinuity());
+}
+
+TEST_F(PathTest, AppendPortion) {
+ // A closed path with two curves:
+ Path bigon = string_to_path("M 0,0 Q 1,1 2,0 Q 1,-1 0,0 Z");
+ Path target{Point(0, 0)};
+
+ PathTime end_time{1, 1.0}; // End of the closed path
+ PathTime mid_time{1, 0.0}; // Middle of the closed path (juncture between the two curves)
+ bigon.appendPortionTo(target, end_time, mid_time, true /* do cross start */);
+
+ // We expect that the target path now contains the entire first curve "M 0,0 Q 1,1 2,0",
+ // since we started at the end of a closed path and requested to cross its start.
+ EXPECT_EQ(target.size(), 1);
+ EXPECT_EQ(target, string_to_path("M 0,0 Q 1,1 2,0"));
+
+ // Similar test but with reversal (swapped times)
+ Path target_reverse{Point(2, 0)};
+ bigon.appendPortionTo(target_reverse, mid_time, end_time, true /* do cross start please */);
+ // What do we expect? To cross start going from the midpoint to the endpoint requires
+ // not taking the obvious route (bigon[1]) but rather taking bigon[0] in reverse.
+ EXPECT_EQ(target_reverse.size(), 1);
+ EXPECT_EQ(target_reverse, string_to_path("M 2,0 Q 1,1 0,0"));
+
+ // Similar test but using start time
+ PathTime start_time{0, 0.0};
+ Path mid_target{Point(2, 0)};
+ bigon.appendPortionTo(mid_target, mid_time, start_time, true /* cross start to 0:0 */);
+ // We expect to go forward from mid_time and cross over the start to start_time.
+ EXPECT_EQ(mid_target.size(), 1);
+ EXPECT_EQ(mid_target, string_to_path("M 2,0 Q 1,-1 0,0"));
+
+ // Use start time with reversal
+ Path mid_reverse{Point(0, 0)};
+ bigon.appendPortionTo(mid_reverse, start_time, mid_time, true /* Cross start, going backwards. */);
+ // We expect that we don't go forwards from start_time to mid_time, but rather cross over the starting
+ // point and backtrack over bigon[1] to the midpoint.
+ EXPECT_EQ(mid_reverse.size(), 1);
+ EXPECT_EQ(mid_reverse, string_to_path("M 0,0 Q 1,-1 2,0"));
+}
+
+TEST_F(PathTest, ReplaceMiddle) {
+ p_open.replace(p_open.begin() + 1, p_open.begin() + 2, p_add);
+ EXPECT_EQ(p_open.size(), 5u);
+ EXPECT_NO_THROW(p_open.checkContinuity());
+
+ p_closed.replace(p_closed.begin() + 1, p_closed.begin() + 2, p_add);
+ EXPECT_EQ(p_closed.size(), 6u);
+ EXPECT_NO_THROW(p_closed.checkContinuity());
+}
+
+TEST_F(PathTest, ReplaceStart) {
+ p_open.replace(p_open.begin(), p_open.begin() + 2, p_add);
+ EXPECT_EQ(p_open.size(), 3u);
+ EXPECT_NO_THROW(p_open.checkContinuity());
+
+ p_closed.replace(p_closed.begin(), p_closed.begin() + 2, p_add);
+ EXPECT_EQ(p_closed.size(), 5u);
+ EXPECT_NO_THROW(p_closed.checkContinuity());
+}
+
+TEST_F(PathTest, ReplaceEnd) {
+ p_open.replace(p_open.begin() + 1, p_open.begin() + 3, p_add);
+ EXPECT_EQ(p_open.size(), 3u);
+ EXPECT_NO_THROW(p_open.checkContinuity());
+
+ p_closed.replace(p_closed.begin() + 1, p_closed.begin() + 3, p_add);
+ EXPECT_EQ(p_closed.size(), 5u);
+ EXPECT_NO_THROW(p_closed.checkContinuity());
+}
+
+TEST_F(PathTest, ReplaceClosing) {
+ p_open.replace(p_open.begin() + 1, p_open.begin() + 4, p_add);
+ EXPECT_EQ(p_open.size(), 3u);
+ EXPECT_NO_THROW(p_open.checkContinuity());
+
+ p_closed.replace(p_closed.begin() + 1, p_closed.begin() + 4, p_add);
+ EXPECT_EQ(p_closed.size(), 4u);
+ EXPECT_NO_THROW(p_closed.checkContinuity());
+}
+
+TEST_F(PathTest, ReplaceEverything) {
+ p_open.replace(p_open.begin(), p_open.end(), p_add);
+ EXPECT_EQ(p_open.size(), 1u);
+ EXPECT_NO_THROW(p_open.checkContinuity());
+
+ // TODO: in this specific case, it may make sense to set the path to open...
+ // Need to investigate what behavior is sensible here
+ p_closed.replace(p_closed.begin(), p_closed.end(), p_add);
+ EXPECT_EQ(p_closed.size(), 2u);
+ EXPECT_NO_THROW(p_closed.checkContinuity());
+}
+
+TEST_F(PathTest, EraseLast) {
+ p_open.erase_last();
+ Path p_expected = string_to_path("M 0,0 L 0,5 5,5");
+ EXPECT_EQ(p_open, p_expected);
+ EXPECT_NO_THROW(p_open.checkContinuity());
+}
+
+TEST_F(PathTest, AreNear) {
+ Path nudged_arcs1 = string_to_path("M 0,0 a 5,10 45 0 1 10,10.0000005 a 5,10 45 0 1 0,0 z");
+ Path nudged_arcs2 = string_to_path("M 0,0 a 5,10 45 0 1 10,10.00005 a 5,10 45 0 1 0,0 z");
+ EXPECT_EQ(are_near(diederik, diederik, 0), true);
+ EXPECT_EQ(are_near(cmds, diederik, 1e-6), false);
+ EXPECT_EQ(are_near(arcs, nudged_arcs1, 1e-6), true);
+ EXPECT_EQ(are_near(arcs, nudged_arcs2, 1e-6), false);
+}
+
+TEST_F(PathTest, Roots) {
+ Path path;
+ path.start(Point(0, 0));
+ path.appendNew<Geom::LineSegment>(Point(1, 1));
+ path.appendNew<Geom::LineSegment>(Point(2, 0));
+
+ EXPECT_FALSE(path.closed());
+
+ // Trivial case: make sure that path is not closed
+ std::vector<PathTime> roots = path.roots(0.5, Geom::X);
+ EXPECT_EQ(roots.size(), 1u);
+ EXPECT_EQ(path.valueAt(roots[0], Geom::Y), 0.5);
+
+ // Now check that it is closed if we make it so
+ path.close(true);
+ roots = path.roots(0.5, Geom::X);
+ EXPECT_EQ(roots.size(), 2u);
+}
+
+TEST_F(PathTest, PartingPoint)
+{
+ // === Test complete overlaps between identical curves ===
+ // Line segment
+ auto line = string_to_path("M 0,0 L 3.33, 7.77");
+ auto pt = parting_point(line, line);
+ EXPECT_TRUE(are_near(pt.point(), line.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.t, 1.0));
+
+ // Cubic Bézier
+ auto bezier = string_to_path("M 0,0 C 1,1 14,1 15,0");
+ pt = parting_point(bezier, bezier);
+ EXPECT_TRUE(are_near(pt.point(), bezier.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.t, 1.0));
+
+ // Eliptical arc
+ auto const arc = string_to_path("M 0,0 A 100,20 0,0,0 200,0");
+ pt = parting_point(arc, arc);
+ EXPECT_TRUE(are_near(pt.point(), arc.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.t, 1.0));
+
+ // === Test complete overlap between degree-elevated and degree-shrunk Béziers ===
+ auto artificially_cubic = string_to_path("M 0,0 C 10,10 20,10 30,0");
+ auto really_quadratic = string_to_path("M 0,0 Q 15,15 30,0");
+ pt = parting_point(artificially_cubic, really_quadratic);
+ EXPECT_TRUE(are_near(pt.point(), artificially_cubic.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.asFlatTime(), 1.0));
+ EXPECT_TRUE(are_near(pt.second.asFlatTime(), 1.0));
+
+ // === Test complete overlaps between a curve and its subdivision ===
+ // Straight line
+ line = string_to_path("M 0,0 L 15,15");
+ auto subdivided_line = string_to_path("M 0,0 L 3,3 L 4,4 L 9,9 L 15,15");
+ pt = parting_point(line, subdivided_line);
+ EXPECT_TRUE(are_near(pt.point(), line.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.t, 1.0));
+
+ // Cubic Bézier
+ bezier = string_to_path("M 0,0 C 0,40 50,40 50,0");
+ auto de_casteljau = string_to_path("M 0,0 C 0,10 3.125,17.5 7.8125,22.5 12.5,27.5 18.75,30 25,30"
+ " 31.25,30 37.5,27.5 42.1875,22.5 46.875,17.5 50,10 50,0");
+ pt = parting_point(bezier, de_casteljau);
+ EXPECT_TRUE(are_near(pt.point(), bezier.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.t, 1.0));
+
+ // Eliptical arc
+ auto subdivided_arc = string_to_path("M 0,0 A 100,20, 0,0,0 100,20 A 100,20 0,0,0 200,0");
+ pt = parting_point(arc, subdivided_arc);
+ EXPECT_TRUE(are_near(pt.point(), arc.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.t, 1.0));
+
+ // === Test complete overlap between different subdivisions ===
+ auto line1 = string_to_path("M 0,0 L 3,3 L 5,5 L 10,10");
+ auto line2 = string_to_path("M 0,0 L 2,2 L 4.2,4.2 L 4.5,4.5 L 6,6 L 10,10");
+ pt = parting_point(line1, line2);
+ EXPECT_TRUE(are_near(pt.point(), line1.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.asFlatTime(), line1.timeRange().max()));
+ EXPECT_TRUE(are_near(pt.second.asFlatTime(), line2.timeRange().max()));
+
+ // === Test complete overlaps in the presence of degenerate segments ===
+ // Straight line
+ line = string_to_path("M 0,0 L 15,15");
+ subdivided_line = string_to_path("M 0,0 L 3,3 H 3 V 3 L 3,3 L 4,4 H 4 V 4 L 4,4 L 9,9 H 9 L 15,15");
+ pt = parting_point(line, subdivided_line);
+ EXPECT_TRUE(are_near(pt.point(), line.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.asFlatTime(), 1.0));
+
+ // Eliptical arc
+ auto arc_degen = string_to_path("M 0,0 A 100,20, 0,0,0 100,20 H 100 V 20 L 100,20 A 100,20 0,0,0 200,0");
+ pt = parting_point(arc, arc_degen);
+ EXPECT_TRUE(are_near(pt.point(), arc.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.asFlatTime(), 1.0));
+
+ // === Paths that overlap but one is shorter than the other ===
+ // Straight lines
+ auto long_line = string_to_path("M 0,0 L 20,10");
+ auto short_line = string_to_path("M 0,0 L 4,2");
+ pt = parting_point(long_line, short_line);
+ EXPECT_TRUE(are_near(pt.point(), short_line.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.t, 0.2));
+ EXPECT_TRUE(are_near(pt.second.t, 1.0));
+
+ // Cubic Bézier
+ auto const s_shape = string_to_path("M 0,0 C 10, 0 0,10 10,10");
+ auto half_s = string_to_path("M 0,0 C 5,0 5,2.5 5,5");
+ pt = parting_point(s_shape, half_s);
+ EXPECT_TRUE(are_near(pt.first.t, 0.5));
+ EXPECT_TRUE(are_near(pt.second.t, 1.0));
+
+ // Elliptical arc
+ auto quarter_ellipse = string_to_path("M 0,0 A 100,20, 0,0,0 100,20");
+ pt = parting_point(arc, quarter_ellipse);
+ EXPECT_TRUE(are_near(pt.point(), quarter_ellipse.finalPoint()));
+ EXPECT_TRUE(are_near(pt.first.t, 0.5));
+ EXPECT_TRUE(are_near(pt.second.t, 1.0));
+
+ // === Paths that overlap initially but then they split ===
+ // Straight lines
+ auto boring_line = string_to_path("M 0,0 L 50,10");
+ auto line_then_arc = string_to_path("M 0,0 L 5,1 A 1,1 0,0,0 7,1");
+ pt = parting_point(boring_line, line_then_arc);
+ EXPECT_TRUE(are_near(pt.point(), Point(5, 1)));
+ EXPECT_TRUE(are_near(pt.first.t, 0.1));
+ EXPECT_TRUE(are_near(pt.second.asFlatTime(), 1.0));
+
+ // Cubic Bézier
+ auto half_s_then_line = string_to_path("M 0,0 C 5,0 5,2.5 5,5 L 10,10");
+ pt = parting_point(s_shape, half_s_then_line);
+ EXPECT_TRUE(are_near(pt.point(), Point(5, 5)));
+ EXPECT_TRUE(are_near(pt.first.t, 0.5));
+ EXPECT_TRUE(are_near(pt.second.asFlatTime(), 1.0));
+
+ // Elliptical arc
+ auto quarter_ellipse_then_quadratic = string_to_path("M 0,0 A 100,20, 0,0,0 100,20 Q 120,40 140,60");
+ pt = parting_point(arc, quarter_ellipse_then_quadratic);
+ EXPECT_TRUE(are_near(pt.point(), Point(100, 20)));
+ EXPECT_TRUE(are_near(pt.first.t, 0.5));
+ EXPECT_TRUE(are_near(pt.second.asFlatTime(), 1.0));
+
+ // === Paths that split at a common node ===
+ // Polylines
+ auto branch_90 = string_to_path("M 0,0 H 3 H 6 V 7");
+ auto branch_45 = string_to_path("M 0,0 H 2 H 6 L 7,7");
+ pt = parting_point(branch_90, branch_45);
+ EXPECT_TRUE(are_near(pt.point(), Point(6, 0)));
+ EXPECT_TRUE(are_near(pt.first.asFlatTime(), 2.0));
+ EXPECT_TRUE(are_near(pt.second.asFlatTime(), 2.0));
+
+ // Arcs
+ auto quarter_circle_then_horiz = string_to_path("M 0,0 A 1,1 0,0,0 1,1 H 10");
+ auto quarter_circle_then_slant = string_to_path("M 0,0 A 1,1 0,0,0 1,1 L 10, 1.1");
+ pt = parting_point(quarter_circle_then_horiz, quarter_circle_then_slant);
+ EXPECT_TRUE(are_near(pt.point(), Point(1, 1)));
+ EXPECT_TRUE(are_near(pt.first.asFlatTime(), 1.0));
+ EXPECT_TRUE(are_near(pt.second.asFlatTime(), 1.0));
+
+ // Last common nodes followed by degenerates
+ auto degen_horiz = string_to_path("M 0,0 A 1,1 0,0,0 1,1 V 1 H 1 L 1,1 H 10");
+ auto degen_slant = string_to_path("M 0,0 A 1,1 0,0,0 1,1 V 1 H 1 L 1,1 L 10, 1.1");
+ pt = parting_point(quarter_circle_then_horiz, quarter_circle_then_slant);
+ EXPECT_TRUE(are_near(pt.point(), Point(1, 1)));
+
+ // === Paths that split at the starting point ===
+ auto vertical = string_to_path("M 0,0 V 1");
+ auto quarter = string_to_path("M 0,0 A 1,1 0,0,0, 1,1");
+ pt = parting_point(vertical, quarter);
+ EXPECT_TRUE(are_near(pt.point(), Point(0, 0)));
+ EXPECT_TRUE(are_near(pt.first.asFlatTime(), 0.0));
+ EXPECT_TRUE(are_near(pt.second.asFlatTime(), 0.0));
+
+ // === Symmetric split (both legs of the same length) ===
+ auto left_leg = string_to_path("M 1,0 L 0,10");
+ auto right_leg = string_to_path("M 1,0 L 2,10");
+ pt = parting_point(left_leg, right_leg);
+ EXPECT_TRUE(are_near(pt.point(), Point(1, 0)));
+ EXPECT_TRUE(are_near(pt.first.asFlatTime(), 0.0));
+ EXPECT_TRUE(are_near(pt.second.asFlatTime(), 0.0));
+
+ // === Different starting points ===
+ auto start_at_0_0 = string_to_path("M 0,0 C 1,0 0,1 1,1");
+ auto start_at_10_10 = string_to_path("M 10,10 L 50,50");
+ pt = parting_point(start_at_0_0, start_at_10_10);
+ EXPECT_TRUE(are_near(pt.point(), Point (5,5)));
+ EXPECT_DOUBLE_EQ(pt.first.t, -1.0);
+ EXPECT_DOUBLE_EQ(pt.second.t, -1.0);
+ EXPECT_EQ(pt.first.curve_index, 0);
+ EXPECT_EQ(pt.second.curve_index, 0);
+}
+
+TEST_F(PathTest, InitialFinalTangents) {
+ // Test tangents for an open path
+ auto L_shape = string_to_path("M 1,1 H 0 V 0");
+ EXPECT_EQ(L_shape.initialUnitTangent(), Point(-1.0, 0.0));
+ EXPECT_EQ(L_shape.finalUnitTangent(), Point(0.0, -1.0));
+
+ // Closed path with non-degenerate closing segment
+ auto triangle = string_to_path("M 0,0 H 2 L 0,3 Z");
+ EXPECT_EQ(triangle.initialUnitTangent(), Point(1.0, 0.0));
+ EXPECT_EQ(triangle.finalUnitTangent(), Point(0.0, -1.0));
+
+ // Closed path with a degenerate closing segment
+ auto full360 = string_to_path("M 0,0 A 1,1, 0,1,1, 0,2 A 1,1 0,1,1 0,0 Z");
+ EXPECT_EQ(full360.initialUnitTangent(), Point(1.0, 0.0));
+ EXPECT_EQ(full360.finalUnitTangent(), Point(1.0, 0.0));
+
+ // Test multiple degenerate segments at the start
+ auto start_degen = string_to_path("M 0,0 L 0,0 H 0 V 0 Q 1,0 1,1");
+ EXPECT_EQ(start_degen.initialUnitTangent(), Point(1.0, 0.0));
+
+ // Test multiple degenerate segments at the end
+ auto end_degen = string_to_path("M 0,0 L 1,1 H 1 V 1 L 1,1");
+ double comp = 1.0 / sqrt(2.0);
+ EXPECT_EQ(end_degen.finalUnitTangent(), Point(comp, comp));
+
+ // Test a long and complicated path with both tangents along the positive x-axis.
+ auto complicated = string_to_path("M 0,0 H 0 L 1,0 C 2,1 3,2 1,0 L 1,0 H 1 Q 2,3 0,5 H 2");
+ EXPECT_EQ(complicated.initialUnitTangent(), Point(1.0, 0.0));
+ EXPECT_EQ(complicated.finalUnitTangent(), Point(1.0, 0.0));
+}
+
+TEST_F(PathTest, WithoutDegenerates) {
+ // Ensure nothing changes when there are no degenerate segments to remove.
+ auto plain_open = string_to_path("M 0,0 Q 5,5 10,10");
+ EXPECT_EQ(plain_open, plain_open.withoutDegenerateCurves());
+
+ auto closed_nondegen_closing = string_to_path("M 0,0 L 5,5 H 0 Z");
+ EXPECT_EQ(closed_nondegen_closing,closed_nondegen_closing.withoutDegenerateCurves());
+
+ // Ensure that a degenerate closing segment is left alone.
+ auto closed_degen_closing = string_to_path("M 0,0 L 2,4 H 0 L 0,0 Z");
+ EXPECT_EQ(closed_degen_closing, closed_degen_closing.withoutDegenerateCurves());
+
+ // Ensure that a trivial path is left alone (both open and closed).
+ auto trivial_open = string_to_path("M 0,0");
+ EXPECT_EQ(trivial_open, trivial_open.withoutDegenerateCurves());
+
+ auto trivial_closed = string_to_path("M 0,0 Z");
+ EXPECT_EQ(trivial_closed, trivial_closed.withoutDegenerateCurves());
+
+ // Ensure that initial degenerate segments are removed
+ auto degen_start = string_to_path("M 0,0 L 0,0 H 0 V 0 Q 5,5 10,10");
+ auto degen_start_cleaned = degen_start.withoutDegenerateCurves();
+ EXPECT_EQ(degen_start_cleaned, string_to_path("M 0,0 Q 5,5 10,10"));
+ EXPECT_NE(degen_start.size(), degen_start_cleaned.size());
+
+ // Ensure that degenerate segments are removed from the middle
+ auto degen_middle = string_to_path("M 0,0 L 1,1 H 1 V 1 L 1,1 Q 6,6 10,10");
+ auto degen_middle_cleaned = degen_middle.withoutDegenerateCurves();
+ EXPECT_EQ(degen_middle_cleaned, string_to_path("M 0,0 L 1,1 Q 6,6 10,10"));
+ EXPECT_NE(degen_middle.size(), degen_middle_cleaned.size());
+
+ // Ensure that degenerate segment are removed from the end of an open path
+ auto end_open = string_to_path("M 0,0 L 1,1 H 1 V 1 L 1,1");
+ auto end_open_cleaned = end_open.withoutDegenerateCurves();
+ EXPECT_EQ(end_open_cleaned, string_to_path("M 0,0 L 1,1"));
+ EXPECT_NE(end_open.size(), end_open_cleaned.size());
+
+ // Ensure removal of degenerates just before the closing segment
+ auto end_nondegen = string_to_path("M 0,0 L 1,1 L 0,1 H 0 V 1 Z");
+ auto end_nondegen_cleaned = end_nondegen.withoutDegenerateCurves();
+ EXPECT_EQ(end_nondegen_cleaned, string_to_path("M 0,0 L 1,1 L 0,1 Z"));
+ EXPECT_NE(end_nondegen.size(), end_nondegen_cleaned.size());
+}
+
+/** Test Path::extrema() */
+TEST_F(PathTest, GetExtrema) {
+
+ // Circle of radius 4.5 centered at (-4.5, 0).
+ auto extrema_x = circle.extrema(X);
+ EXPECT_EQ(extrema_x.min_point, Point(-9, 0));
+ EXPECT_EQ(extrema_x.max_point, Point( 0, 0));
+ EXPECT_DOUBLE_EQ(extrema_x.min_time.asFlatTime(), 1.0);
+ EXPECT_DOUBLE_EQ(extrema_x.max_time.asFlatTime(), 0.0);
+ EXPECT_EQ(extrema_x.glance_direction_at_min, -1.0);
+ EXPECT_EQ(extrema_x.glance_direction_at_max, 1.0);
+
+ auto extrema_y = circle.extrema(Y);
+ EXPECT_EQ(extrema_y.min_point, Point(-4.5, -4.5));
+ EXPECT_EQ(extrema_y.max_point, Point(-4.5, 4.5));
+ EXPECT_DOUBLE_EQ(extrema_y.min_time.asFlatTime(), 1.5);
+ EXPECT_DOUBLE_EQ(extrema_y.max_time.asFlatTime(), 0.5);
+ EXPECT_FLOAT_EQ(extrema_y.glance_direction_at_min, 1.0);
+ EXPECT_FLOAT_EQ(extrema_y.glance_direction_at_max, -1.0);
+
+ // Positively oriented unit square
+ extrema_x = square.extrema(X);
+ EXPECT_DOUBLE_EQ(extrema_x.min_point[X], 0.0);
+ EXPECT_DOUBLE_EQ(extrema_x.max_point[X], 1.0);
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, -1.0);
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_max, 1.0);
+
+ extrema_y = square.extrema(Y);
+ EXPECT_DOUBLE_EQ(extrema_y.min_point[Y], 0.0);
+ EXPECT_DOUBLE_EQ(extrema_y.max_point[Y], 1.0);
+ EXPECT_FLOAT_EQ(extrema_y.glance_direction_at_min, 1.0);
+ EXPECT_FLOAT_EQ(extrema_y.glance_direction_at_max, -1.0);
+
+ // Path glancing its min X line while going towards negative Y
+ auto down_glance = string_to_path("M 1,18 L 0,0 1,-20");
+ extrema_x = down_glance.extrema(X);
+ EXPECT_EQ(extrema_x.min_point, Point(0, 0));
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, -1.0);
+ EXPECT_DOUBLE_EQ(extrema_x.min_time.asFlatTime(), 1.0);
+
+ // Similar but not at a node
+ auto down_glance_smooth = string_to_path("M 1,20 C 0,20 0,-20 1,-20");
+ extrema_x = down_glance_smooth.extrema(X);
+ EXPECT_TRUE(are_near(extrema_x.min_point[Y], 0.0));
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, -1.0);
+ EXPECT_DOUBLE_EQ(extrema_x.min_time.asFlatTime(), 0.5);
+
+ // Path coming down to the min X and then retreating horizontally
+ auto retreat = string_to_path("M 1,20 L 0,0 H 5 L 4,-20");
+ extrema_x = retreat.extrema(X);
+ EXPECT_EQ(extrema_x.min_point, Point(0, 0));
+ EXPECT_EQ(extrema_x.max_point, Point(5, 0));
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, -1.0);
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_max, -1.0);
+ EXPECT_DOUBLE_EQ(extrema_x.min_time.asFlatTime(), 1.0);
+ EXPECT_DOUBLE_EQ(extrema_x.max_time.asFlatTime(), 2.0);
+
+ // Perfectly horizontal path
+ auto horizontal = string_to_path("M 0,0 H 12");
+ extrema_x = horizontal.extrema(X);
+ extrema_y = horizontal.extrema(Y);
+ EXPECT_EQ(extrema_x.min_point, Point(0, 0));
+ EXPECT_EQ(extrema_x.max_point, Point(12, 0));
+ EXPECT_DOUBLE_EQ(extrema_y.min_point[Y], 0.0);
+ EXPECT_DOUBLE_EQ(extrema_y.max_point[Y], 0.0);
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, 0.0);
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_max, 0.0);
+ EXPECT_FLOAT_EQ(extrema_y.glance_direction_at_min, 1.0);
+ EXPECT_FLOAT_EQ(extrema_y.glance_direction_at_max, 1.0);
+ EXPECT_DOUBLE_EQ(extrema_x.min_time.asFlatTime(), 0.0);
+ EXPECT_DOUBLE_EQ(extrema_x.max_time.asFlatTime(), 1.0);
+
+ // Perfectly vertical path
+ auto vertical = string_to_path("M 0,0 V 42");
+ extrema_y = vertical.extrema(Y);
+ extrema_x = vertical.extrema(X);
+ EXPECT_DOUBLE_EQ(extrema_x.min_point[Y], 0.0);
+ EXPECT_DOUBLE_EQ(extrema_x.max_point[Y], 0.0);
+ EXPECT_EQ(extrema_y.min_point, Point(0, 0));
+ EXPECT_EQ(extrema_y.max_point, Point(0, 42));
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, 1.0);
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_max, 1.0);
+ EXPECT_FLOAT_EQ(extrema_y.glance_direction_at_min, 0.0);
+ EXPECT_FLOAT_EQ(extrema_y.glance_direction_at_max, 0.0);
+ EXPECT_DOUBLE_EQ(extrema_y.min_time.asFlatTime(), 0.0);
+ EXPECT_DOUBLE_EQ(extrema_y.max_time.asFlatTime(), 1.0);
+
+ // Detect downward glance at the closing point (degenerate closing segment)
+ auto closed = string_to_path("M 0,0 L 1,-2 H 3 V 5 H 1 L 0,0 Z");
+ extrema_x = closed.extrema(X);
+ EXPECT_EQ(extrema_x.min_point, Point(0, 0));
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, -1.0);
+
+ // Same but with a non-degenerate closing segment
+ auto closed_nondegen = string_to_path("M 0,0 L 1,-2 H 3 V 5 H 1 Z");
+ extrema_x = closed_nondegen.extrema(X);
+ EXPECT_EQ(extrema_x.min_point, Point(0, 0));
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, -1.0);
+
+ // Collapsed Bezier not glancing up nor down
+ auto collapsed = string_to_path("M 10, 0 Q -10 0 10, 0");
+ extrema_x = collapsed.extrema(X);
+ EXPECT_EQ(extrema_x.min_point, Point(0, 0));
+ EXPECT_EQ(extrema_x.max_point, Point(10, 0));
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, 0.0);
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_max, 0.0);
+
+ // Degenerate segments at min X
+ auto degen = string_to_path("M 0.01,20 L 0, 0 H 0 V 0 L 0,0 V 0 L 0.02 -30");
+ extrema_x = degen.extrema(X);
+ EXPECT_EQ(extrema_x.min_point, Point(0, 0));
+ EXPECT_FLOAT_EQ(extrema_x.glance_direction_at_min, -1.0);
+}
+
+/** Regression test for issue https://gitlab.com/inkscape/lib2geom/-/issues/50 */
+TEST_F(PathTest, PizzaSlice)
+{
+ auto pv = parse_svg_path("M 0 0 L 0.30901699437494745 0.9510565162951535 "
+ "A 1 1 0 0 1 -0.8090169943749473 0.5877852522924732 z");
+ auto &sector = pv[0];
+ Path piece;
+ EXPECT_NO_THROW(piece = sector.portion(PathTime(0, 0.0), PathTime(2, 0.0), false));
+ EXPECT_FALSE(piece.closed());
+ EXPECT_TRUE(piece.size() == 2 ||
+ (piece.size() == 3 && piece[2].isDegenerate()));
+ EXPECT_EQ(piece.finalPoint(), Point(-0.8090169943749473, 0.5877852522924732));
+
+ // Test slicing in the middle of an arc and past its end
+ pv = parse_svg_path("M 0,0 H 1 A 1,1 0 0 1 0.3080657835086775,0.9513650577098072 z");
+ EXPECT_NO_THROW(piece = pv[0].portion(PathTime(1, 0.5), PathTime(2, 1.0)));
+ EXPECT_FALSE(piece.closed());
+ EXPECT_EQ(piece.finalPoint(), pv[0].finalPoint());
+
+ // Test slicing from before the start to a point on the arc
+ EXPECT_NO_THROW(piece = pv[0].portion(PathTime(0, 0.5), PathTime(1, 0.5)));
+ EXPECT_FALSE(piece.closed());
+ EXPECT_EQ(piece.initialPoint(), pv[0].pointAt(PathTime(0, 0.5)));
+ EXPECT_EQ(piece.finalPoint(), pv[0].pointAt(PathTime(1, 0.5)));
+
+ // Test slicing a part of the arc
+ EXPECT_NO_THROW(piece = pv[0].portion(PathTime(1, 0.25), PathTime(1, 0.75)));
+ EXPECT_FALSE(piece.closed());
+ EXPECT_EQ(piece.size(), 1);
+
+ // Test slicing in reverse
+ EXPECT_NO_THROW(piece = pv[0].portion(PathTime(2, 1.0), PathTime(1, 0.5)));
+ EXPECT_FALSE(piece.closed());
+ EXPECT_EQ(piece.finalPoint(), pv[0].pointAt(PathTime(1, 0.5)));
+
+ EXPECT_NO_THROW(piece = pv[0].portion(PathTime(1, 0.5), PathTime(0, 0.5)));
+ EXPECT_FALSE(piece.closed());
+ EXPECT_EQ(piece.initialPoint(), pv[0].pointAt(PathTime(1, 0.5)));
+ EXPECT_EQ(piece.finalPoint(), pv[0].pointAt(PathTime(0, 0.5)));
+
+ EXPECT_NO_THROW(piece = pv[0].portion(PathTime(1, 0.75), PathTime(1, 0.25)));
+ EXPECT_FALSE(piece.closed());
+ EXPECT_EQ(piece.size(), 1);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/pick.h b/tests/pick.h
new file mode 100644
index 0000000..3e43bd5
--- /dev/null
+++ b/tests/pick.h
@@ -0,0 +1,172 @@
+/*
+ * Routines for generating anything randomly
+ *
+ * Authors:
+ * Marco Cecchetti <mrcekets at gmail.com>
+ *
+ * Copyright 2008 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#ifndef _GEOM_SL_PICK_H_
+#define _GEOM_SL_PICK_H_
+
+
+#include <2geom/symbolic/multipoly.h>
+#include <2geom/symbolic/matrix.h>
+
+inline
+size_t pick_uint(size_t max)
+{
+ return (std::rand() % (max+1));
+}
+
+inline
+int pick_int(size_t max)
+{
+ int s = pick_uint(2);
+ if (s == 0) s = -1;
+ return s * (std::rand() % (max+1));
+}
+
+inline
+Geom::SL::multi_index_type pick_multi_index(size_t N, size_t max)
+{
+ Geom::SL::multi_index_type I(N);
+ for (size_t i = 0; i < I.size(); ++i)
+ I[i] = pick_uint(max);
+ return I;
+}
+
+template <size_t N>
+inline
+typename Geom::SL::mvpoly<N, double>::type
+pick_polyN(size_t d, size_t m)
+{
+ typename Geom::SL::mvpoly<N, double>::type p;
+ size_t d0 = pick_uint(d);
+ for (size_t i = 0; i <= d0; ++i)
+ {
+ p.coefficient(i, pick_polyN<N-1>(d, m));
+ }
+ return p;
+}
+
+template <>
+inline
+double pick_polyN<0>(size_t /*d*/, size_t m)
+{
+ return pick_int(m);
+}
+
+
+template <size_t N>
+inline
+typename Geom::SL::mvpoly<N, double>::type
+pick_poly_max(size_t d, size_t m)
+{
+ typename Geom::SL::mvpoly<N, double>::type p;
+ for (size_t i = 0; i <= d; ++i)
+ {
+ p.coefficient(i, pick_poly_max<N-1>(d-i, m));
+ }
+ return p;
+}
+
+template <>
+inline
+double pick_poly_max<0>(size_t /*d*/, size_t m)
+{
+ return pick_int(m);
+}
+
+
+template <size_t N>
+inline
+Geom::SL::MultiPoly<N, double>
+pick_multipoly(size_t d, size_t m)
+{
+ return Geom::SL::MultiPoly<N, double>(pick_polyN<N>(d, m));
+}
+
+template <size_t N>
+inline
+Geom::SL::MultiPoly<N, double>
+pick_multipoly_max(size_t d, size_t m)
+{
+ return Geom::SL::MultiPoly<N, double>(pick_poly_max<N>(d, m));
+}
+
+
+
+inline
+Geom::SL::Matrix< Geom::SL::MultiPoly<2, double> >
+pick_matrix(size_t n, size_t d, size_t m)
+{
+ Geom::SL::Matrix< Geom::SL::MultiPoly<2, double> > M(n, n);
+ for (size_t i = 0; i < n; ++i)
+ {
+ for (size_t j = 0; j < n; ++j)
+ {
+ M(i,j) = pick_multipoly_max<2>(d, m);
+ }
+ }
+ return M;
+}
+
+
+inline
+Geom::SL::Matrix< Geom::SL::MultiPoly<2, double> >
+pick_symmetric_matrix(size_t n, size_t d, size_t m)
+{
+ Geom::SL::Matrix< Geom::SL::MultiPoly<2, double> > M(n, n);
+ for (size_t i = 0; i < n; ++i)
+ {
+ for (size_t j = 0; j < i; ++j)
+ {
+ M(i,j) = M(j,i) = pick_multipoly_max<2>(d, m);
+ }
+ }
+ for (size_t i = 0; i < n; ++i)
+ {
+ M(i,i) = pick_multipoly_max<2>(d, m);
+ }
+ return M;
+}
+
+
+#endif // _GEOM_SL_PICK_H_
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/planar-graph-test.cpp b/tests/planar-graph-test.cpp
new file mode 100644
index 0000000..f19e2eb
--- /dev/null
+++ b/tests/planar-graph-test.cpp
@@ -0,0 +1,457 @@
+/** @file
+ * @brief Unit tests for PlanarGraph class template
+ */
+/*
+ * Authors:
+ * Rafał Siejakowski <rs@rs-math.net>
+ *
+ * Copyright 2022 the Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <gtest/gtest.h>
+#include <iostream>
+
+#include <2geom/point.h>
+#include <2geom/pathvector.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/svg-path-writer.h>
+
+#include "planar-graph.h"
+#include "testing.h"
+
+using namespace Geom;
+
+#define PV(d) (parse_svg_path(d))
+#define PTH(d) (std::move(PV(d)[0]))
+#define REV(d) ((PV(d)[0]).reversed())
+
+/** An edge label for the purpose of tests. */
+struct TestLabel
+{
+ unsigned reversal_count = 0, merge_count = 0, detachment_count = 0;
+ void onReverse() { reversal_count++; }
+ void onMergeWith(TestLabel const &) { merge_count++; }
+ void onDetach() { detachment_count++; }
+};
+
+using TestGraph = PlanarGraph<TestLabel>;
+
+static std::vector<TestLabel> extract_labels(TestGraph const &graph)
+{
+ // Find labels of edges remaining in the graph.
+ std::vector<TestLabel> result;
+ for (auto &e : graph.getEdges()) {
+ if (!e.detached) {
+ result.push_back(e.label);
+ }
+ }
+ return result;
+}
+
+class PlanarGraphTest : public ::testing::Test
+{
+};
+
+/** Test edge insertion and vertex clumping to within the tolerance. */
+TEST(PlanarGraphTest, EdgeInsertion)
+{
+ double const precision = 1e-3;
+ auto graph = TestGraph(precision);
+ graph.insertEdge(PTH("M 0, 0 L 1, 0"));
+ graph.insertEdge(PTH("M 0, 1 L 1, 1")); // } Endpoints near
+ graph.insertEdge(PTH("M 1, 0 L 1, 1.0009")); // } but not exact.
+
+ auto vertices = graph.getVertices();
+
+ // Test vertex clumping within the given precision
+ EXPECT_EQ(vertices.size(), 4);
+ EXPECT_EQ(graph.numEdges(), 3);
+
+ // Test lexicographic vertex position sorting by X and then Y
+ EXPECT_EQ(vertices.front().point(), Point(0, 0));
+ auto after = std::next(vertices.begin());
+ EXPECT_EQ(after->point(), Point(0, 1));
+ ++after;
+ EXPECT_EQ(after->point(), Point(1, 0));
+ EXPECT_TRUE(are_near(vertices.back().point(), Point(1, 1), precision));
+
+ EXPECT_FALSE(graph.isRegularized());
+}
+
+/** Test PlanarGraph<T>::insertDetached(). */
+TEST(PlanarGraphTest, InsertDetached)
+{
+ TestGraph graph;
+ auto detached = graph.insertDetached(PTH("M 0,0 A 1,1 0,0,1 2,0 V -2 H 0 Z"));
+
+ auto const &edges = graph.getEdges();
+ EXPECT_EQ(edges.size(), 1);
+ EXPECT_TRUE(edges.at(detached).detached);
+ EXPECT_TRUE(edges.at(detached).inserted_as_detached);
+
+ EXPECT_EQ(graph.numVertices(), 0);
+ EXPECT_EQ(graph.numEdges(false), 0);
+ EXPECT_TRUE(graph.isRegularized());
+}
+
+/** Test signed area calculation. */
+TEST(PlanarGraphTest, ClosedPathArea)
+{
+ // Square with counter-clockwise oriented boundary, when imagining that the y-axis
+ // points up – expect the area to be +1.
+ auto square_positive = PTH("M 0,0 H 1 V 1 H 0 Z");
+ EXPECT_DOUBLE_EQ(TestGraph::closedPathArea(square_positive), 1.0);
+
+ // Expect negative area for a negatively oriented path.
+ auto triangle_negative = PTH("M 0,0 V 1 L 1,1 Z");
+ EXPECT_DOUBLE_EQ(TestGraph::closedPathArea(triangle_negative), -0.5);
+}
+
+/** Test the detection of direction of deviation of initially tangent paths. */
+TEST(PlanarGraphTest, Deviation)
+{
+ auto vertical_up = PTH("M 0,0 V 1");
+ auto arc_right1 = PTH("M 0,0 A 1,1 0,1,0 2,0");
+ auto arc_left1 = PTH("M 0,0 A 1,1 0,1,1 -2,0");
+ auto arc_right2 = PTH("M 0,0 A 2,2 0,1,0, 4,0");
+ auto arc_left2 = PTH("M 0,0 A 2,2 0,1,1 -4,0");
+ // A very "flat" Bézier curve deviating to the right but slower than the large arc
+ auto bezier_right = PTH("M 0,0 C 0,50 1,20 2,10");
+
+ EXPECT_TRUE(TestGraph::deviatesLeft(arc_left1, arc_left2));
+ EXPECT_TRUE(TestGraph::deviatesLeft(arc_left2, vertical_up));
+ EXPECT_TRUE(TestGraph::deviatesLeft(vertical_up, arc_right2));
+ EXPECT_TRUE(TestGraph::deviatesLeft(vertical_up, bezier_right));
+ EXPECT_TRUE(TestGraph::deviatesLeft(bezier_right, arc_right2));
+ EXPECT_TRUE(TestGraph::deviatesLeft(arc_right2, arc_right1));
+ EXPECT_TRUE(TestGraph::deviatesLeft(arc_left1, arc_right1));
+ EXPECT_TRUE(TestGraph::deviatesLeft(arc_left2, arc_right1));
+
+ EXPECT_FALSE(TestGraph::deviatesLeft(arc_right1, vertical_up));
+ EXPECT_FALSE(TestGraph::deviatesLeft(arc_right1, arc_right2));
+ EXPECT_FALSE(TestGraph::deviatesLeft(vertical_up, arc_left2));
+ EXPECT_FALSE(TestGraph::deviatesLeft(arc_left2, arc_left1));
+ EXPECT_FALSE(TestGraph::deviatesLeft(arc_right1, arc_left1));
+ EXPECT_FALSE(TestGraph::deviatesLeft(arc_right1, arc_left2));
+}
+
+/** Test sorting of incidences at a vertex by the outgoing heading. */
+TEST(PlanarGraphTest, BasicAzimuthalSort)
+{
+ TestGraph graph;
+
+ // Imagine the Y-axis pointing up (as in mathematics)!
+ bool const clockwise = true;
+ unsigned const num_rays = 9;
+ unsigned edges[num_rays];
+
+ // Insert the edges randomly but store them in what we know to be the
+ // clockwise order of outgoing azimuths from the vertex at the origin.
+ edges[7] = graph.insertEdge(PTH("M -0.2, -1 L 0, 0"));
+ edges[1] = graph.insertEdge(PTH("M -1, 0.2 L 0, 0"));
+ edges[4] = graph.insertEdge(PTH("M 0, 0 L 1, 0.2"));
+ edges[6] = graph.insertEdge(PTH("M 0.1, -1 L 0, 0"));
+ edges[2] = graph.insertEdge(PTH("M 0, 0 L -0.3, 1"));
+ edges[0] = graph.insertEdge(PTH("M -1, 0 H 0"));
+ edges[5] = graph.insertEdge(PTH("M 0, 0 L 1, -0.2"));
+ edges[3] = graph.insertEdge(PTH("M 0.2, 1 L 0, 0"));
+ edges[8] = graph.insertEdge(PTH("M -1, -0.1 L 0, 0"));
+
+ // We expect the incidence to edges[0] to be the last one
+ // in the sort order so it should appear first when going clockwise.
+ auto [origin, incidence] = graph.getIncidence(edges[0], TestGraph::Incidence::END);
+ ASSERT_TRUE(origin);
+ ASSERT_TRUE(incidence);
+
+ // Expect ±pi as the azimuth
+ EXPECT_DOUBLE_EQ(std::abs(incidence->azimuth), M_PI);
+
+ // Test sort order
+ for (unsigned i = 0; i < num_rays; i++) {
+ EXPECT_EQ(incidence->index, edges[i]);
+ incidence = (TestGraph::Incidence *)&graph.nextIncidence(*origin, *incidence, clockwise);
+ }
+}
+
+/** Test retrieval of a path inserted as an edge in both orientations. */
+TEST(PlanarGraphTest, PathRetrieval)
+{
+ TestGraph graph;
+
+ Path const path = PTH("M 0,0 L 1,1 C 2,2 4,2 5,1");
+ Path const htap = path.reversed();
+
+ auto edge = graph.insertEdge(path);
+
+ ASSERT_EQ(graph.numEdges(), 1);
+
+ auto [start_point, start_incidence] = graph.getIncidence(edge, TestGraph::Incidence::START);
+ ASSERT_TRUE(start_point);
+ ASSERT_TRUE(start_incidence);
+ EXPECT_EQ(graph.getOutgoingPath(start_incidence), path);
+ EXPECT_EQ(graph.getIncomingPath(start_incidence), htap);
+
+ auto [end_point, end_incidence] = graph.getIncidence(edge, TestGraph::Incidence::END);
+ ASSERT_TRUE(end_point);
+ ASSERT_TRUE(end_incidence);
+ EXPECT_EQ(graph.getIncomingPath(end_incidence), path);
+ EXPECT_EQ(graph.getOutgoingPath(end_incidence), htap);
+}
+
+/** Make sure the edge labels are correctly stored. */
+TEST(PlanarGraphTest, LabelRetrieval)
+{
+ TestGraph graph;
+ TestLabel label;
+
+ label.reversal_count = 420;
+ label.merge_count = 69;
+ label.detachment_count = 111;
+
+ auto edge = graph.insertEdge(PTH("M 0,0 L 1,1"), std::move(label));
+
+ auto retrieved = graph.getEdge(edge).label;
+ EXPECT_EQ(retrieved.reversal_count, 420);
+ EXPECT_EQ(retrieved.merge_count, 69);
+ EXPECT_EQ(retrieved.detachment_count, 111);
+}
+
+/** Regularization of duplicate edges. */
+TEST(PlanarGraphTest, MergeDuplicate)
+{
+ char const *const d = "M 2, 3 H 0 C 1,4 1,5 0,6 H 10 L 8, 0";
+ 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";
+
+ // Test removal of perfect overlap:
+ TestGraph graph;
+ graph.insertEdge(PTH(d));
+ graph.insertEdge(PTH(d)); // exact duplicate
+ graph.regularize();
+
+ EXPECT_TRUE(graph.isRegularized());
+
+ auto remaining = extract_labels(graph);
+
+ // Expect there to be only 1 edge after regularization.
+ ASSERT_EQ(remaining.size(), 1);
+
+ EXPECT_EQ(remaining[0].merge_count, 1); // expect one merge,
+ EXPECT_EQ(remaining[0].reversal_count, 0); // no reversals,
+ EXPECT_EQ(remaining[0].detachment_count, 0); // no detachments.
+
+ // Test removal of imperfect overlaps within numerical precision
+ TestGraph fuzzy{1e-3};
+ fuzzy.insertEdge(PTH(d));
+ fuzzy.insertEdge(PTH(near_d));
+ fuzzy.regularize();
+
+ EXPECT_TRUE(fuzzy.isRegularized());
+
+ auto fuzmaining = extract_labels(fuzzy);
+ ASSERT_EQ(fuzmaining.size(), 1);
+
+ EXPECT_EQ(fuzmaining[0].merge_count, 1); // expect one merge,
+ EXPECT_EQ(fuzmaining[0].reversal_count, 0); // no reversals,
+ EXPECT_EQ(fuzmaining[0].detachment_count, 0); // no detachments.
+
+ // Test overlap of edges with oppositie orientations.
+ TestGraph twoway;
+ twoway.insertEdge(PTH(d));
+ twoway.insertEdge(REV(d));
+ twoway.regularize();
+
+ EXPECT_TRUE(twoway.isRegularized());
+
+ auto left = extract_labels(twoway);
+ ASSERT_EQ(left.size(), 1);
+
+ EXPECT_EQ(left[0].merge_count, 1); // expect one merge,
+ EXPECT_TRUE(left[0].reversal_count == 0 || left[0].reversal_count == 1); // 0 or 1 reversals
+ EXPECT_EQ(left[0].detachment_count, 0); // no detachments.
+}
+
+/** Regularization of a shorter edge overlapping a longer one. */
+TEST(PlanarGraphTest, MergePartial)
+{
+ TestGraph graph;
+ auto longer = graph.insertEdge(PTH("M 0, 0 L 10, 10"));
+ auto shorter = graph.insertEdge(PTH("M 0, 0 L 6, 6"));
+
+ EXPECT_EQ(graph.numVertices(), 3);
+
+ graph.regularize();
+
+ EXPECT_EQ(graph.numVertices(), 3);
+ EXPECT_TRUE(graph.isRegularized());
+
+ auto labels = extract_labels(graph);
+ ASSERT_EQ(labels.size(), 2);
+
+ EXPECT_EQ(labels[longer].merge_count, 0);
+ EXPECT_EQ(labels[longer].reversal_count, 0);
+ EXPECT_EQ(labels[longer].detachment_count, 0);
+
+ EXPECT_EQ(labels[shorter].merge_count, 1);
+ EXPECT_EQ(labels[shorter].reversal_count, 0);
+ EXPECT_EQ(labels[shorter].detachment_count, 0);
+
+ // Now the same thing but with edges of opposite orientations.
+ TestGraph graphopp;
+ longer = graphopp.insertEdge(PTH("M 0, 0 L 10, 0"));
+ shorter = graphopp.insertEdge(PTH("M 10, 0 L 5, 0"));
+
+ EXPECT_EQ(graphopp.numVertices(), 3);
+
+ graphopp.regularize();
+
+ EXPECT_EQ(graphopp.numVertices(), 3);
+ EXPECT_TRUE(graphopp.isRegularized());
+
+ labels = extract_labels(graphopp);
+ ASSERT_EQ(labels.size(), 2);
+
+ EXPECT_EQ(labels[longer].merge_count, 0);
+ EXPECT_EQ(labels[longer].reversal_count, 0);
+ EXPECT_EQ(labels[longer].detachment_count, 0);
+
+ EXPECT_EQ(labels[shorter].merge_count, 1);
+ EXPECT_EQ(labels[shorter].reversal_count, 0);
+ EXPECT_EQ(labels[shorter].detachment_count, 0);
+}
+
+/** Regularization of a Y-split. */
+TEST(PlanarGraphTest, MergeY)
+{
+ TestGraph graph;
+ auto left = graph.insertEdge(PTH("M 1 0 V 1 L 0, 2"));
+ auto right = graph.insertEdge(PTH("M 1,0 V 1 L 2, 2"));
+
+ EXPECT_EQ(graph.numVertices(), 3);
+ graph.regularize();
+ EXPECT_EQ(graph.numVertices(), 4);
+
+ auto edges = graph.getEdges();
+ EXPECT_EQ(edges.size(), 3);
+
+ EXPECT_TRUE(are_near(edges[right].start->point(), Point(1, 1)));
+}
+
+/** Test reversal of a wrongly oriented teardrop */
+TEST(PlanarGraphTest, Teardrop)
+{
+ TestGraph graph;
+ 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"));
+ // Insert a few unrelated edges
+ auto before = graph.insertEdge(PTH("M 1,0 H 10"));
+ auto after = graph.insertEdge(PTH("M 1,0 H -10"));
+
+ EXPECT_EQ(graph.numVertices(), 3);
+
+ graph.regularize();
+
+ EXPECT_EQ(graph.numVertices(), 3);
+ auto [start_vertex, start_incidence] = graph.getIncidence(loop, TestGraph::Incidence::START);
+ auto [end_vertex, end_incidence] = graph.getIncidence(loop, TestGraph::Incidence::END);
+
+ EXPECT_EQ(start_vertex, end_vertex);
+ ASSERT_NE(start_vertex, nullptr);
+
+ // Check that the incidences have been swapped
+ EXPECT_EQ(start_vertex->cyclicNextIncidence(end_incidence), start_incidence);
+ EXPECT_EQ(start_vertex->cyclicPrevIncidence(start_incidence), end_incidence);
+ auto [b, before_incidence] = graph.getIncidence(before, TestGraph::Incidence::START);
+ EXPECT_EQ(start_vertex->cyclicNextIncidence(before_incidence), end_incidence);
+ auto [a, after_incidence] = graph.getIncidence(after, TestGraph::Incidence::START);
+ EXPECT_EQ(start_vertex->cyclicPrevIncidence(after_incidence), start_incidence);
+}
+
+/** Test the regularization of a lasso-shaped path. */
+TEST(PlanarGraphTest, ReglueLasso)
+{
+ TestGraph graph;
+ // Insert a lasso-shaped path (a teardrop with initial self-overlap).
+ auto original_lasso = graph.insertEdge(PTH("M 0,0 V 1 C 0,2 1,3 1,4 "
+ "A 1,1 0,1,1 -1,4 C -1,3 0,2 0,1 V 0"));
+ EXPECT_EQ(graph.numVertices(), 1);
+
+ graph.regularize();
+ EXPECT_EQ(graph.numVertices(), 2);
+ EXPECT_EQ(graph.numEdges(false), 2);
+ EXPECT_TRUE(graph.getEdge(original_lasso).detached);
+
+ auto const &edges = graph.getEdges();
+ // Find the edge from origin and ensure it got glued.
+ auto from_origin = std::find_if(edges.begin(), edges.end(), [](auto const &edge) -> bool {
+ return !edge.detached && (edge.start->point() == Point(0, 0) ||
+ edge.end->point() == Point(0, 0));
+ });
+ ASSERT_NE(from_origin, edges.end());
+ ASSERT_EQ(from_origin->label.merge_count, 1);
+}
+
+/** Test the removal of a collapsed loop. */
+TEST(PlanarGraphTest, RemoveCollapsed)
+{
+ TestGraph graph;
+ // Insert a collapsed loop
+ auto collapsed = graph.insertEdge(PTH("M 0,0 L 1,1 L 0,0"));
+ ASSERT_EQ(graph.numEdges(), 1);
+ graph.regularize();
+ ASSERT_EQ(graph.numEdges(false), 0);
+ ASSERT_TRUE(graph.getEdge(collapsed).detached);
+
+ TestGraph fuzzy(1e-3);
+ // Insert a nearly collapsed loop
+ auto nearly = fuzzy.insertEdge(PTH("M 0,0 H 2 V 0.001 L 1,0 H 0"));
+ ASSERT_EQ(fuzzy.numEdges(), 1);
+ fuzzy.regularize();
+ ASSERT_EQ(fuzzy.numEdges(false), 0);
+ ASSERT_TRUE(fuzzy.getEdge(nearly).detached);
+}
+
+/** Test regularization of straddling runs. */
+TEST(PlanarGraphTest, RemoveWisp)
+{
+ TestGraph graph;
+ // Insert a horizontal segment at the origin towards positive X:
+ graph.insertEdge(PTH("M 0 0 H 1"));
+ // Insert a path with a collapsed Bézier curve towards negative X:
+ graph.insertEdge(PTH("M 0 0 C -1 0 -1 0 0 0"));
+ graph.regularize();
+
+ // Ensure that the folded Bézier is removed (and no segfault occurs).
+ EXPECT_EQ(graph.numEdges(false), 1);
+}
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/point-test.cpp b/tests/point-test.cpp
new file mode 100644
index 0000000..16596a5
--- /dev/null
+++ b/tests/point-test.cpp
@@ -0,0 +1,119 @@
+/** @file
+ * @brief Unit tests for Point, IntPoint and related functions.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2014-2015 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <gtest/gtest.h>
+#include <2geom/point.h>
+
+namespace Geom {
+
+TEST(PointTest, Normalize) {
+ Point a(1e-18, 0);
+ Point b = a;
+ a.normalize();
+
+ EXPECT_EQ(a, Point(1, 0));
+ EXPECT_EQ(b.normalized(), a);
+ EXPECT_NE(b, a);
+}
+
+TEST(PointTest, ScalarOps) {
+ Point a(1,2);
+ EXPECT_EQ(a * 2, Point(2, 4));
+ EXPECT_EQ(2 * a, Point(2, 4));
+ EXPECT_EQ(a / 2, Point(0.5, 1));
+
+ Point b = a;
+ a *= 2;
+ a /= 2;
+ EXPECT_EQ(a, b);
+}
+
+TEST(PointTest, Rounding) {
+ Point a(-0.7, 0.7);
+ IntPoint aceil(0, 1), afloor(-1, 0), around(-1, 1);
+ EXPECT_TRUE(a.ceil() == aceil);
+ EXPECT_TRUE(a.floor() == afloor);
+ EXPECT_TRUE(a.round() == around);
+}
+
+TEST(PointTest, Near) {
+ EXPECT_TRUE(are_near(Point(), Point(0, 1e-6)));
+ EXPECT_FALSE(are_near(Point(), Point(0, 1e-4)));
+
+ EXPECT_TRUE(are_near_rel(Point(100, 0), Point(100, 1e-4)));
+ EXPECT_FALSE(are_near_rel(Point(100, 0), Point(100, 1e-2)));
+}
+
+TEST(PointTest, Multiplicative) {
+ EXPECT_EQ(Point(2, 3) * Point(4, 5), Point(8, 15));
+ EXPECT_EQ(IntPoint(2, 3) * IntPoint(4, 5), IntPoint(8, 15));
+ EXPECT_EQ(Point(10, 11) / Point(2, 3), Point(5, 11.0 / 3.0));
+ EXPECT_EQ(IntPoint(10, 11) / IntPoint(2, 3), IntPoint(5, 11 / 3));
+}
+
+TEST(PointTest, PointCtors) {
+ Point a(2, 3);
+ EXPECT_EQ(a[X], 2);
+ EXPECT_EQ(a[Y], 3);
+
+ a.~Point();
+ new (&a) Point;
+ EXPECT_EQ(a, Point(0, 0));
+
+ a = Point(IntPoint(4, 5));
+ EXPECT_EQ(a[X], 4);
+ EXPECT_EQ(a[Y], 5);
+}
+
+TEST(PointTest, IntPointCtors) {
+ IntPoint a(2, 3);
+ EXPECT_EQ(a[X], 2);
+ EXPECT_EQ(a[Y], 3);
+
+ a.~IntPoint();
+ new (&a) IntPoint;
+ EXPECT_EQ(a, IntPoint(0, 0));
+}
+
+} // end namespace Geom
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/polybez-cases.svg b/tests/polybez-cases.svg
new file mode 100644
index 0000000..1fa653a
--- /dev/null
+++ b/tests/polybez-cases.svg
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="1024"
+ height="768"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.47 r22583"
+ sodipodi:docname="polybez-cases.svg">
+ <defs
+ id="defs4">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective10" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.2"
+ inkscape:cx="338.56564"
+ inkscape:cy="493.11416"
+ inkscape:document-units="px"
+ inkscape:current-layer="sweep1"
+ showgrid="true"
+ inkscape:snap-grids="true"
+ inkscape:window-width="1440"
+ inkscape:window-height="825"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2820"
+ empspacing="5"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="false" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-284.36218)">
+ <g
+ id="g2829">
+ <path
+ id="path2824"
+ d="m 335,532 0,-15 10,-10 10,0 0,12 -7,0 0,7 7,0 0,6 -20,0 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ id="path2826"
+ d="m 338,524 0,-10 5,-5 12,0 0,15 -17,0 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 370,507 15,0 -15,20 15,0 -15,-20 z"
+ id="path2828" />
+ <g
+ id="g2836">
+ <path
+ id="path2830"
+ d="m 395,507 0,20 15,0 0,-20 -15,0 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ id="path2832"
+ d="m 400,512 0,10 5,0 0,-10 -5,0 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2840">
+ <path
+ id="path2834"
+ d="m 420,507 0,20 10,0 0,-20 -10,0 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ id="path2838"
+ d="m 420,512 10,0 0,10 -10,0 0,-10 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2844">
+ <path
+ transform="translate(0,284.36218)"
+ id="path2861"
+ d="m 335,273 5,-10 5,0 -5,10 -5,0 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,284.36218)"
+ id="path2863"
+ d="m 335,263 5,10 5,0 -5,-10 -5,0 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ id="g2848">
+ <path
+ transform="translate(0,284.36218)"
+ id="path2865"
+ d="m 355,273 c 0,-10 5,-15 15,-15 10,0 15,15 5,15 -10,0 -19.82143,0 -20,0 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,284.36218)"
+ id="path2867"
+ d="m 355,258 15,15 5,0 -15,-15 -5,0 z"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 390,273 c 0,0 0,-20 0,-15 0,5 5,-5 5,-5 l 5,0 0,20 -10,0 z"
+ id="path2869"
+ transform="translate(0,284.36218)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 405,273 c 0,0 0,-10 0,-15 0,-5 0,-5 5,-5 5,0 5,0 5,5 0,5 -10,14.82143 -10,15 z"
+ id="path2871"
+ transform="translate(0,284.36218)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 339.46429,565.30861 c -4.5365,-2.22147 -11.77743,5.69134 -5.21169,7.66819 2.96423,0.36599 10.83771,-2.10029 8.78922,3.46024 -1.13154,2.4938 -6.48404,5.18119 -7.60122,3.87235 4.97889,-1.66476 5.51215,4.06019 3.64354,6.27906 2.59991,2.27135 9.90909,-2.08687 10.22789,-0.44567 -5.83309,2.53694 -12.2305,3.77526 -18.58139,3.51925 -5.55304,-1.87666 0.0508,-8.52008 3.64017,-9.2859 3.14953,-0.57328 11.95334,1.68857 9.98614,-3.95731 -2.41191,-3.92307 -8.83522,2.26895 -9.24309,-2.87701 1.90388,-1.51652 8.28914,-1.45159 6.19223,-5.58351 -2.15792,-0.14937 -0.81405,-2.34484 -1.8418,-2.64969 z"
+ id="path2873" />
+ <g
+ id="sweep1">
+ <path
+ id="path2892"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 307,259 c 0,0 8,8 -3,11 -5,1 -5,9 -5,9 m 4,-17 1,3 m -6,-8 3,12 m -9,-11 1,4 2,3 -1,4 3,3 -2,5 m -9,-19 4,14"
+ transform="translate(0,284.36218)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 296,280 5,-11"
+ id="path2908"
+ transform="translate(0,284.36218)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 309,283 -5,-18"
+ id="path2904"
+ transform="translate(0,284.36218)" />
+ </g>
+ </g>
+</svg>
diff --git a/tests/polynomial-test.cpp b/tests/polynomial-test.cpp
new file mode 100644
index 0000000..699820a
--- /dev/null
+++ b/tests/polynomial-test.cpp
@@ -0,0 +1,126 @@
+/** @file
+ * @brief Unit tests for Polynomial and related functions.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2015-2019 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include "testing.h"
+#include <iostream>
+#include <glib.h>
+
+#include <2geom/polynomial.h>
+
+using namespace Geom;
+
+TEST(PolynomialTest, SolveQuadratic) {
+ for (unsigned i = 0; i < 1000; ++i) {
+ Coord x1 = g_random_double_range(-100, 100);
+ Coord x2 = g_random_double_range(-100, 100);
+
+ Coord a = g_random_double_range(-10, 10);
+ Coord b = -a * (x1 + x2);
+ Coord c = a * x1 * x2;
+
+ std::vector<Coord> result = solve_quadratic(a, b, c);
+
+ EXPECT_EQ(result.size(), 2u);
+ if (x1 < x2) {
+ EXPECT_FLOAT_EQ(result[0], x1);
+ EXPECT_FLOAT_EQ(result[1], x2);
+ } else {
+ EXPECT_FLOAT_EQ(result[0], x2);
+ EXPECT_FLOAT_EQ(result[1], x1);
+ }
+ }
+}
+
+TEST(PolynomialTest, SolvePathologicalQuadratic) {
+ std::vector<Coord> r;
+
+ r = solve_quadratic(1, -1e9, 1);
+ ASSERT_EQ(r.size(), 2u);
+ EXPECT_FLOAT_EQ(r[0], 1e-9);
+ EXPECT_FLOAT_EQ(r[1], 1e9);
+
+ r = solve_quadratic(1, -4, 3.999999);
+ ASSERT_EQ(r.size(), 2u);
+ EXPECT_FLOAT_EQ(r[0], 1.999);
+ EXPECT_FLOAT_EQ(r[1], 2.001);
+
+ r = solve_quadratic(1, 0, -4);
+ ASSERT_EQ(r.size(), 2u);
+ EXPECT_FLOAT_EQ(r[0], -2);
+ EXPECT_FLOAT_EQ(r[1], 2);
+
+ r = solve_quadratic(1, 0, -16);
+ ASSERT_EQ(r.size(), 2u);
+ EXPECT_FLOAT_EQ(r[0], -4);
+ EXPECT_FLOAT_EQ(r[1], 4);
+
+ r = solve_quadratic(1, 0, -100);
+ ASSERT_EQ(r.size(), 2u);
+ EXPECT_FLOAT_EQ(r[0], -10);
+ EXPECT_FLOAT_EQ(r[1], 10);
+}
+
+TEST(PolynomialTest, SolveCubic) {
+ for (unsigned i = 0; i < 1000; ++i) {
+ Coord x1 = g_random_double_range(-100, 100);
+ Coord x2 = g_random_double_range(-100, 100);
+ Coord x3 = g_random_double_range(-100, 100);
+
+ Coord a = g_random_double_range(-10, 10);
+ Coord b = -a * (x1 + x2 + x3);
+ Coord c = a * (x1*x2 + x2*x3 + x1*x3);
+ Coord d = -a * x1 * x2 * x3;
+
+ std::vector<Coord> result = solve_cubic(a, b, c, d);
+ std::vector<Coord> x(3); x[0] = x1; x[1] = x2; x[2] = x3;
+ std::sort(x.begin(), x.end());
+
+ ASSERT_EQ(result.size(), 3u);
+ EXPECT_FLOAT_EQ(result[0], x[0]);
+ EXPECT_FLOAT_EQ(result[1], x[1]);
+ EXPECT_FLOAT_EQ(result[2], x[2]);
+ }
+
+ // corner cases
+ // (x^2 + 7)(x - 2)
+ std::vector<Coord> r1 = solve_cubic(1, -2, 7, -14);
+ EXPECT_EQ(r1.size(), 1u);
+ EXPECT_FLOAT_EQ(r1[0], 2);
+
+ // (x + 1)^2 (x-2)
+ std::vector<Coord> r2 = solve_cubic(1, 0, -3, -2);
+ ASSERT_EQ(r2.size(), 3u);
+ EXPECT_FLOAT_EQ(r2[0], -1);
+ EXPECT_FLOAT_EQ(r2[1], -1);
+ EXPECT_FLOAT_EQ(r2[2], 2);
+}
diff --git a/tests/rect-test.cpp b/tests/rect-test.cpp
new file mode 100644
index 0000000..93733e5
--- /dev/null
+++ b/tests/rect-test.cpp
@@ -0,0 +1,368 @@
+/** @file
+ * @brief Unit tests for Rect, OptRect, IntRect, and OptIntRect.
+ * Uses the Google Testing Framework
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright 2010 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <gtest/gtest.h>
+#include <2geom/coord.h>
+#include <2geom/rect.h>
+
+namespace Geom {
+
+typedef ::testing::Types<Coord, IntCoord> CoordTypes;
+
+TEST(RectTest, Upconversion) {
+ IntRect ir(0, -27, 10, 202);
+ Rect r_a(ir);
+ Rect r_b = ir;
+ OptIntRect oir_a(ir);
+ OptIntRect oir_b = ir;
+ OptRect or_a(oir_a);
+ OptRect or_b = oir_b;
+
+ EXPECT_EQ(r_a, ir);
+ EXPECT_EQ(r_a, r_b);
+ EXPECT_EQ(r_a, *oir_a);
+ EXPECT_EQ(r_a, *oir_b);
+ EXPECT_EQ(r_a, *or_a);
+ EXPECT_EQ(r_a, *or_b);
+ EXPECT_EQ(oir_a, oir_b);
+ EXPECT_EQ(or_a, or_b);
+}
+
+TEST(RectTest, Rounding) {
+ Rect r(-0.5, -0.5, 5.5, 5.5);
+ Rect r_small(0.3, 0.0, 0.6, 10.0);
+ Rect r_int(0,0,10,10);
+ IntRect out(-1, -1, 6, 6);
+ IntRect out_small(0, 0, 1, 10);
+ IntRect out_int(0,0,10,10);
+ OptIntRect in = IntRect(0, 0, 5, 5);
+ EXPECT_EQ(r.roundOutwards(), out);
+ EXPECT_EQ(r_small.roundOutwards(), out_small);
+ EXPECT_EQ(r_int.roundOutwards(), out_int);
+ EXPECT_EQ(r.roundInwards(), in);
+ EXPECT_EQ(r_small.roundInwards(), OptIntRect());
+}
+
+template <typename C>
+class GenericRectTest : public ::testing::Test {
+public:
+ typedef typename CoordTraits<C>::PointType CPoint;
+ typedef typename CoordTraits<C>::RectType CRect;
+ typedef typename CoordTraits<C>::OptRectType OptCRect;
+ CRect a, a2, b, c, d;
+ CRect int_ab, int_bc, uni_ab, uni_bc;
+ GenericRectTest()
+ : a(0, 0, 10, 10)
+ , a2(0, 0, 10, 10)
+ , b(-5, -5, 5, 5)
+ , c(-10, -10, -1, -1)
+ , d(1, 1, 9, 9)
+ , int_ab(0, 0, 5, 5)
+ , int_bc(-5, -5, -1, -1)
+ , uni_ab(-5, -5, 10, 10)
+ , uni_bc(-10, -10, 5, 5)
+ {}
+};
+
+TYPED_TEST_CASE(GenericRectTest, CoordTypes);
+
+TYPED_TEST(GenericRectTest, EqualityTest) {
+ typename TestFixture::CRect a(0, 0, 10, 10), a2(a), b(-5, -5, 5, 5);
+ typename TestFixture::OptCRect empty, oa = a;
+
+ EXPECT_TRUE (a == a);
+ EXPECT_FALSE(a != a);
+ EXPECT_TRUE (a == a2);
+ EXPECT_FALSE(a != a2);
+ EXPECT_TRUE (empty == empty);
+ EXPECT_FALSE(empty != empty);
+ EXPECT_FALSE(a == empty);
+ EXPECT_TRUE (a != empty);
+ EXPECT_FALSE(empty == a);
+ EXPECT_TRUE (empty != a);
+ EXPECT_FALSE(a == b);
+ EXPECT_TRUE (a != b);
+ EXPECT_TRUE (a == oa);
+ EXPECT_FALSE(a != oa);
+}
+
+TYPED_TEST(GenericRectTest, Intersects) {
+ typename TestFixture::CRect a(0, 0, 10, 10), b(-5, -5, 5, 5), c(-10, -10, -1, -1), d(1, 1, 9, 9);
+ typename TestFixture::OptCRect empty, oa(a), oc(c), od(d);
+ EXPECT_TRUE(a.intersects(a));
+ EXPECT_TRUE(a.intersects(b));
+ EXPECT_TRUE(b.intersects(a));
+ EXPECT_TRUE(b.intersects(c));
+ EXPECT_TRUE(c.intersects(b));
+ EXPECT_TRUE(a.intersects(d));
+ EXPECT_TRUE(d.intersects(a));
+ EXPECT_FALSE(a.intersects(c));
+ EXPECT_FALSE(c.intersects(a));
+ EXPECT_FALSE(c.intersects(d));
+ EXPECT_FALSE(empty.intersects(empty));
+ EXPECT_FALSE(empty.intersects(oa));
+ EXPECT_FALSE(oa.intersects(empty));
+ EXPECT_TRUE(oa.intersects(od));
+ EXPECT_FALSE(oa.intersects(oc));
+}
+
+/**
+ JonCruz failure: (10, 20)-(55,30) and (45,20)-(100,30) should intersect.
+*/
+
+TYPED_TEST(GenericRectTest, JonCruzRect) {
+ typename TestFixture::CRect a(10, 20, 55, 30), b(45, 20, 100,30);
+ typename TestFixture::OptCRect empty, oa(a), ob(b);
+ EXPECT_TRUE(a.intersects(a));
+ EXPECT_TRUE(a.intersects(b));
+ EXPECT_TRUE(b.intersects(a));
+ EXPECT_TRUE(oa.intersects(oa));
+ EXPECT_TRUE(oa.intersects(ob));
+ EXPECT_TRUE(ob.intersects(oa));
+}
+
+TYPED_TEST(GenericRectTest, Intersection) {
+ typename TestFixture::CRect a(0, 0, 10, 10), b(-5, -5, 5, 5), c(-10, -10, -1, -1), d(1, 1, 9, 9);
+ typename TestFixture::CRect int_ab(0, 0, 5, 5), int_bc(-5, -5, -1, -1);
+ typename TestFixture::OptCRect empty, oa(a), ob(b);
+
+ EXPECT_EQ(a & a, a);
+ EXPECT_EQ(a & b, int_ab);
+ EXPECT_EQ(b & c, int_bc);
+ EXPECT_EQ(intersect(b, c), int_bc);
+ EXPECT_EQ(intersect(a, a), a);
+ EXPECT_EQ(a & c, empty);
+ EXPECT_EQ(a & d, d);
+ EXPECT_EQ(a & empty, empty);
+ EXPECT_EQ(empty & empty, empty);
+
+ oa &= ob;
+ EXPECT_EQ(oa, int_ab);
+ oa = a;
+ oa &= b;
+ EXPECT_EQ(oa, int_ab);
+ oa = a;
+ oa &= empty;
+ EXPECT_EQ(oa, empty);
+}
+
+TYPED_TEST(GenericRectTest, Contains) {
+ typename TestFixture::CRect a(0, 0, 10, 10), b(-5, -5, 5, 5), c(-10, -10, -1, -1), d(1, 1, 9, 9);
+ typename TestFixture::CRect int_ab(0, 0, 5, 5), int_bc(-5, -5, -1, -1);
+ typename TestFixture::OptCRect empty, oa(a), od(d);
+ EXPECT_TRUE(a.contains(a));
+ EXPECT_FALSE(a.contains(b));
+ EXPECT_FALSE(b.contains(a));
+ EXPECT_FALSE(a.contains(c));
+ EXPECT_FALSE(c.contains(a));
+ EXPECT_TRUE(a.contains(d));
+ EXPECT_FALSE(d.contains(a));
+ EXPECT_TRUE(a.contains(int_ab));
+ EXPECT_TRUE(b.contains(int_ab));
+ EXPECT_TRUE(b.contains(int_bc));
+ EXPECT_TRUE(c.contains(int_bc));
+ EXPECT_FALSE(int_ab.contains(a));
+ EXPECT_FALSE(int_ab.contains(b));
+ EXPECT_FALSE(int_bc.contains(b));
+ EXPECT_FALSE(int_bc.contains(c));
+ EXPECT_FALSE(empty.contains(empty));
+ EXPECT_FALSE(empty.contains(od));
+ EXPECT_TRUE(oa.contains(empty));
+ EXPECT_TRUE(oa.contains(od));
+ EXPECT_FALSE(od.contains(oa));
+}
+
+TYPED_TEST(GenericRectTest, Union) {
+ typename TestFixture::CRect a(0, 0, 10, 10), old_a(a), b(-5, -5, 5, 5), c(-10, -10, -1, -1), d(1, 1, 9, 9);
+ typename TestFixture::CRect int_ab(0, 0, 5, 5), int_bc(-5, -5, -1, -1);
+ typename TestFixture::CRect uni_ab(-5, -5, 10, 10), uni_bc(-10, -10, 5, 5);
+ typename TestFixture::OptCRect empty, oa(a), ob(b);
+ EXPECT_EQ(a | b, uni_ab);
+ EXPECT_EQ(b | c, uni_bc);
+ EXPECT_EQ(a | a, a);
+ EXPECT_EQ(a | d, a);
+ EXPECT_EQ(a | int_ab, a);
+ EXPECT_EQ(b | int_ab, b);
+ EXPECT_EQ(uni_ab | a, uni_ab);
+ EXPECT_EQ(uni_bc | c, uni_bc);
+ EXPECT_EQ(a | empty, a);
+ EXPECT_EQ(empty | empty, empty);
+
+ a |= b;
+ EXPECT_EQ(a, uni_ab);
+ a = old_a;
+ a |= ob;
+ EXPECT_EQ(a, uni_ab);
+ a = old_a;
+ a |= empty;
+ EXPECT_EQ(a, old_a);
+ oa |= ob;
+ EXPECT_EQ(oa, uni_ab);
+ oa = old_a;
+ oa |= b;
+ EXPECT_EQ(oa, uni_ab);
+}
+
+TYPED_TEST(GenericRectTest, Area) {
+ typename TestFixture::CRect a(0, 0, 10, 10), b(-5, -5, 5, 5), c(-10, -10, -1, -1), d(1, 1, 9, 9);
+ typename TestFixture::CRect zero(0,0,0,0);
+ EXPECT_EQ(a.area(), 100);
+ EXPECT_EQ(a.area(), a.width() * a.height());
+ EXPECT_EQ(b.area(), 100);
+ EXPECT_EQ(c.area(), 81);
+ EXPECT_EQ(d.area(), 64);
+ EXPECT_FALSE(a.hasZeroArea());
+ EXPECT_TRUE(zero.hasZeroArea());
+}
+
+TYPED_TEST(GenericRectTest, Emptiness) {
+ typename TestFixture::OptCRect empty, oa(0, 0, 10, 10);
+ EXPECT_TRUE(empty.empty());
+ EXPECT_FALSE(empty);
+ EXPECT_TRUE(!empty);
+ EXPECT_FALSE(oa.empty());
+ EXPECT_TRUE(oa);
+ EXPECT_FALSE(!oa);
+}
+
+TYPED_TEST(GenericRectTest, Dimensions) {
+ typedef typename TestFixture::CPoint CPoint;
+ typename TestFixture::CRect a(-10, -20, 10, 20), b(-15, 30, 45, 90);
+ EXPECT_EQ(a.width(), 20);
+ EXPECT_EQ(a.height(), 40);
+ EXPECT_EQ(a.left(), -10);
+ EXPECT_EQ(a.top(), -20);
+ EXPECT_EQ(a.right(), 10);
+ EXPECT_EQ(a.bottom(), 20);
+ EXPECT_EQ(a.min(), CPoint(-10, -20));
+ EXPECT_EQ(a.max(), CPoint(10, 20));
+ EXPECT_EQ(a.minExtent(), a.width());
+ EXPECT_EQ(a.maxExtent(), a.height());
+ EXPECT_EQ(a.dimensions(), CPoint(20, 40));
+ EXPECT_EQ(a.midpoint(), CPoint(0, 0));
+
+ EXPECT_EQ(b.width(), 60);
+ EXPECT_EQ(b.height(), 60);
+ EXPECT_EQ(b.left(), -15);
+ EXPECT_EQ(b.top(), 30);
+ EXPECT_EQ(b.right(), 45);
+ EXPECT_EQ(b.bottom(), 90);
+ EXPECT_EQ(b.min(), CPoint(-15, 30));
+ EXPECT_EQ(b.max(), CPoint(45, 90));
+ EXPECT_EQ(b.minExtent(), b.maxExtent());
+ EXPECT_EQ(b.dimensions(), CPoint(60, 60));
+ EXPECT_EQ(b.midpoint(), CPoint(15, 60));
+}
+
+TYPED_TEST(GenericRectTest, Modification) {
+ typedef typename TestFixture::CRect CRect;
+ typedef typename TestFixture::OptCRect OptCRect;
+ typedef typename TestFixture::CPoint CPoint;
+ CRect a(-1, -1, 1, 1);
+ a.expandBy(9);
+ EXPECT_EQ(a, CRect(-10, -10, 10, 10));
+ a.setMin(CPoint(0, 0));
+ EXPECT_EQ(a, CRect(0, 0, 10, 10));
+ a.setMax(CPoint(20, 30));
+ EXPECT_EQ(a, CRect(0, 0, 20, 30));
+ a.setMax(CPoint(-5, -5));
+ EXPECT_EQ(a, CRect(-5, -5, -5, -5));
+ a.expandTo(CPoint(5, 5));
+ EXPECT_EQ(a, CRect(-5, -5, 5, 5));
+ a.expandTo(CPoint(0, 0));
+ EXPECT_EQ(a, CRect(-5, -5, 5, 5));
+ a.expandTo(CPoint(0, 15));
+ EXPECT_EQ(a, CRect(-5, -5, 5, 15));
+ a.expandBy(-10);
+ EXPECT_EQ(a, CRect(0, 5, 0, 5));
+ EXPECT_EQ(a.midpoint(), CPoint(0, 5));
+ a.unionWith(CRect(-20, 0, -10, 20));
+ EXPECT_EQ(a, CRect(-20, 0, 0, 20));
+ OptCRect oa(a);
+ oa.intersectWith(CRect(-10, -5, 5, 15));
+ EXPECT_EQ(oa, OptCRect(-10, 0, 0, 15));
+}
+
+TYPED_TEST(GenericRectTest, OptRectDereference) {
+ typename TestFixture::CRect a(0, 0, 5, 5);
+ typename TestFixture::OptCRect oa(0, 0, 10, 10);
+ EXPECT_NE(a, oa);
+ a = *oa;
+ EXPECT_EQ(a, oa);
+}
+
+TYPED_TEST(GenericRectTest, Offset) {
+ typename TestFixture::CRect a(0, 0, 5, 5), old_a(a), app1(-5, 0, 0, 5), amp1(5, 0, 10, 5),
+ app2(5, -10, 10, -5), amp2(-5, 10, 0, 15);
+ typename TestFixture::CPoint p1(-5, 0), p2(5, -10);
+ EXPECT_EQ(a + p1, app1);
+ EXPECT_EQ(a + p2, app2);
+ EXPECT_EQ(a - p1, amp1);
+ EXPECT_EQ(a - p2, amp2);
+
+ a += p1;
+ EXPECT_EQ(a, app1);
+ a = old_a;
+ a += p2;
+ EXPECT_EQ(a, app2);
+ a = old_a;
+ a -= p1;
+ EXPECT_EQ(a, amp1);
+ a = old_a;
+ a -= p2;
+ EXPECT_EQ(a, amp2);
+}
+
+TYPED_TEST(GenericRectTest, NearestEdgePoint) {
+ typename TestFixture::CRect a(0, 0, 10, 10);
+ typename TestFixture::CPoint p1(-5, 5), p2(15, 17), p3(6, 5), p4(3, 9);
+ typename TestFixture::CPoint r1(0, 5), r2(10, 10), r3(10, 5), r4(3, 10);
+
+ EXPECT_EQ(a.nearestEdgePoint(p1), r1);
+ EXPECT_EQ(a.nearestEdgePoint(p2), r2);
+ EXPECT_EQ(a.nearestEdgePoint(p3), r3);
+ EXPECT_EQ(a.nearestEdgePoint(p4), r4);
+}
+
+} // end namespace Geom
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/root-find-test.cpp b/tests/root-find-test.cpp
new file mode 100644
index 0000000..b866f66
--- /dev/null
+++ b/tests/root-find-test.cpp
@@ -0,0 +1,156 @@
+#include <2geom/polynomial.h>
+#include <vector>
+#include <iterator>
+
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-poly.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/solver.h>
+#include <time.h>
+
+using namespace std;
+using namespace Geom;
+
+Poly lin_poly(double a, double b) { // ax + b
+ Poly p;
+ p.push_back(b);
+ p.push_back(a);
+ return p;
+}
+
+Linear linear(double ax, double b) {
+ return Linear(b, ax+b);
+}
+
+double uniform() {
+ return double(rand()) / RAND_MAX;
+}
+
+int main() {
+ Poly a, b, r;
+ double timer_precision = 0.01;
+ double units = 1e6; // us
+
+ a = Poly::linear(1, -0.3)*Poly::linear(1, -0.25)*Poly::linear(1, -0.2);
+
+ std::cout << a <<std::endl;
+ SBasis B = poly_to_sbasis(a);
+ std::cout << B << std::endl;
+ Bezier bez;
+ sbasis_to_bezier(bez, B);
+ cout << bez << endl;
+ //copy(bez.begin(), bez.end(), ostream_iterator<double>(cout, ", "));
+ cout << endl;
+ cout << endl;
+ cout << endl;
+
+ std::vector<std::vector<double> > trials;
+
+ // evenly spaced roots
+ for(int N = 2; N <= 5; N++)
+ {
+ std::vector<double> r;
+ for(int i = 0; i < N; i++)
+ r.push_back(double(i)/(N-1));
+ trials.push_back(r);
+ }
+ // sort of evenish
+ for(int N = 0; N <= 5; N++)
+ {
+ std::vector<double> r;
+ for(int i = 0; i < N; i++)
+ r.push_back(double(i+0.5)/(2*N));
+ trials.push_back(r);
+ }
+ // one at 0.1
+ for(int N = 0; N <= 5; N++)
+ {
+ std::vector<double> r;
+ for(int i = 0; i < N; i++)
+ r.push_back(i+0.1);
+ trials.push_back(r);
+ }
+ for(int N = 0; N <= 6; N++)
+ {
+ std::vector<double> r;
+ for(int i = 0; i < N; i++)
+ r.push_back(i*0.8+0.1);
+ trials.push_back(r);
+ }
+ for(int N = 0; N <= 20; N++)
+ {
+ std::vector<double> r;
+ for(int i = 0; i < N/2; i++) {
+ r.push_back(0.1);
+ r.push_back(0.9);
+ }
+ trials.push_back(r);
+ }
+ for(int i = 0; i <= 20; i++)
+ {
+ std::vector<double> r;
+ for(int i = 0; i < 4; i++) {
+ r.push_back(uniform()*5 - 2.5);
+ r.push_back(0.9);
+ }
+ trials.push_back(r);
+ }
+ double ave_left = 0;
+ cout << "err from exact\n";
+ for(auto & trial : trials) {
+ SBasis B = Linear(1.,1);
+ sort(trial.begin(), trial.end());
+ for(double j : trial) {
+ B = B*linear(1, -j);
+ }
+ double left_time;
+ clock_t end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ unsigned iterations = 0;
+ while(end_t > clock()) {
+ roots(B);
+ iterations++;
+ }
+ left_time = timer_precision*units/iterations;
+ vector<double> rt = roots(B);
+ double err = 0;
+ for(double r : rt) {
+ double best = fabs(r - trial[0]);
+ for(unsigned j = 1; j < trial.size(); j++) {
+ if(fabs(r - trial[j]) < best)
+ best = fabs(r - trial[j]);
+ }
+ err += best;
+ }
+ if(err > 1e-8){
+ for(double j : trial) {
+ cout << j << ", ";
+ }
+ cout << endl;
+ }
+ cout << " e: " << err << std::endl;
+ ave_left += left_time;
+ }
+ cout << "average time = " << ave_left/trials.size() << std::endl;
+
+ for(int i = 10; i >= 0; i--) {
+ vector<double> rt = roots(Linear(i,-1));
+ for(double j : rt) {
+ cout << j << ", ";
+ }
+ cout << endl;
+ }
+
+ return 0;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/rtree-performance-test.cpp b/tests/rtree-performance-test.cpp
new file mode 100644
index 0000000..cf4bcd7
--- /dev/null
+++ b/tests/rtree-performance-test.cpp
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2010 Evangelos Katsikaros <vkatsikaros at yahoo dot gr>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <2geom/toys/toy-framework-2.h>
+
+#include <sstream>
+#include <getopt.h>
+
+#include <SpatialIndex.h>
+#include <glib.h>
+//#include <glib/gtypes.h>
+
+using namespace Geom;
+
+// cmd argument stuff
+char* arg_area_limit = NULL;
+bool arg_area_limit_set = false;
+bool arg_debug = false;
+
+int limit = 0;
+
+// spatial index ID management
+SpatialIndex::id_type indexID;
+
+// list of rectangles
+GList *items = NULL;
+
+// tree of rectangles
+SpatialIndex::ISpatialIndex *tree;
+
+SpatialIndex::id_type test_indexID;
+
+void add_rectangle( int x, int y );
+
+/* Simple Visitor used to search the tree. When Data is encountered
+ * we are supposed to call the render function of the Data
+ * */
+class SearchVisitor : public SpatialIndex::IVisitor {
+public:
+
+ void visitNode(const SpatialIndex::INode& n){
+ }
+
+ void visitData(const SpatialIndex::IData& d){
+ /* this prototype: do nothing
+ * otherwise, render on buffer
+ * */
+ }
+
+ void visitData(std::vector<const SpatialIndex::IData*>& v) {
+ }
+};
+
+
+/* we use the this visitor after each insertion in the tree
+ * The purpose is to validate that everything was stored properly
+ * and test the GList pointer storage. It has no other functional
+ * purpose.
+ * */
+class TestSearchVisitor : public SpatialIndex::IVisitor {
+public:
+
+ void visitNode(const SpatialIndex::INode& n) {
+ }
+
+ void visitData(const SpatialIndex::IData& d){
+ if( test_indexID == d.getIdentifier() ){
+ byte* pData = 0;
+ uint32_t cLen = sizeof(GList*);
+ d.getData(cLen, &pData);
+ //do something...
+ GList* gl = reinterpret_cast<GList*>(pData);
+ Geom::Rect *member_data = (Geom::Rect *)gl->data;
+ double lala = member_data->bottom();
+ std::cout << " Tree: " << lala << std::endl;
+
+ delete[] pData;
+ }
+ }
+
+ void visitData(std::vector<const SpatialIndex::IData*>& v) {
+ }
+};
+
+
+int main(int argc, char **argv) {
+
+ int c;
+
+ //--------------------------------------------------------------------------
+ // read cmd options
+ while (1) {
+ static struct option long_options[] =
+ {
+ /* These options set a flag. */
+ /* These options don't set a flag.
+ We distinguish them by their indices. */
+ {"area-limit", required_argument, 0, 'l'},
+ {"help", no_argument, 0, 'h'},
+ {"debug", no_argument, 0, 'd'},
+ {0, 0, 0, 0}
+ };
+ /* getopt_long stores the option index here. */
+ int option_index = 0;
+
+ c = getopt_long (argc, argv, "l:h:d",
+ long_options, &option_index);
+
+ /* Detect the end of the options. */
+ if (c == -1){
+ break;
+ }
+
+ switch (c)
+ {
+ case 'l':
+ arg_area_limit = optarg;
+ arg_area_limit_set = true;
+ break;
+ case 'h':
+ std::cerr << "Usage: " << argv[0] << " options\n" << std::endl ;
+ std::cerr <<
+ " -l --area-limit=NUMBER minimum number in node.\n" <<
+ " -d --debug Enable debug info (list/tree related).\n" <<
+ " -h --help Print this help.\n" << std::endl;
+ exit(1);
+ break;
+ case 'd':
+ arg_debug = true;
+ break;
+ case '?':
+ /* getopt_long already printed an error message. */
+ break;
+
+ default:
+ abort ();
+ }
+ }
+
+ // use some of the cmd options
+ if( arg_area_limit_set ) {
+ std::stringstream s1( arg_area_limit );
+ s1 >> limit;
+ }
+ else {
+ limit = 100;
+ }
+ // end cmd options
+ //--------------------------------------------------------------------------
+
+
+ double plow[2], phigh[2];
+ // spatial index memory storage manager
+ SpatialIndex::IStorageManager *mem_mngr;
+ // initialize spatial indexing stuff
+ mem_mngr = SpatialIndex::StorageManager::createNewMemoryStorageManager();
+ // fillFactor, indexCapacity, leafCapacity, dimensionality=2, variant=R*, indexIdentifier
+ tree = SpatialIndex::RTree::createNewRTree(*mem_mngr, 0.7, 25, 25, 2, SpatialIndex::RTree::RV_RSTAR, indexID);
+
+ //-------------------------------------------
+ /* generate items. add_rectangle() stores them in both list and tree
+ * add rect every (20, 20).
+ * In area ((0,0), (1000, 1000)) add every (100,100)
+ * */
+ for( int x_coord = -limit; x_coord <= limit; x_coord += 20 ) {
+ for( int y_coord = -limit; y_coord <= limit; y_coord += 20 ) {
+ if( x_coord >= 0 && x_coord <= 1000 &&
+ y_coord >= 0 && y_coord <= 1000 )
+ {
+ if( x_coord % 100 == 0 && y_coord % 100 == 0) {
+ add_rectangle( x_coord, y_coord );
+ }
+ else{
+ add_rectangle( x_coord, y_coord );
+ }
+ }
+ else{
+ add_rectangle( x_coord, y_coord );
+ }
+
+ }
+ }
+ std::cout << "Area of objects: ( -" << limit
+ << ", -" << limit
+ << " ), ( " << limit
+ << ", " << limit
+ << " )" << std::endl;
+ std::cout << "Number of Objects (indexID): " << indexID << std::endl;
+ // std::cout << "GListElements: " << g_list_length << std::endl;
+
+ //-------------------------------------------
+ // Traverse list
+ Geom::Point sa_start = Point( 0, 0 );
+ Geom::Point sa_end = Point( 1000, 1000 );
+ Geom::Rect search_area = Rect( sa_start, sa_end );
+
+ Timer list_timer;
+ list_timer.ask_for_timeslice();
+ list_timer.start();
+
+ for (GList *list = items; list; list = list->next) {
+ Geom::Rect *child = (Geom::Rect *)list->data;
+ if ( search_area.intersects( *child ) )
+ {
+ /* this prototype: do nothing
+ * otherwise, render on buffer
+ * */
+ }
+ }
+ Timer::Time the_list_time = list_timer.lap();
+
+ std::cout << std::endl;
+ std::cout << "GList (full scan): " << the_list_time << std::endl;
+
+ //-------------------------------------------
+ // Search tree - good case
+ Timer tree_timer;
+ tree_timer.ask_for_timeslice();
+ tree_timer.start();
+
+ /* We search only the (0,0), (1000, 1000) where the items are less dense.
+ * We expect a good performance versus the list
+ * */
+ // TODO IMPORTANT !!! check the plow, phigh
+ // plow[0] = x1; plow[1] = y1;
+ // phigh[0] = x2; phigh[1] = y2;
+
+ plow[0] = 0;
+ plow[1] = 0;
+ phigh[0] = 1000;
+ phigh[1] = 1000;
+
+ SpatialIndex::Region search_region = SpatialIndex::Region(plow, phigh, 2);
+ SearchVisitor vis = SearchVisitor();
+ tree->intersectsWithQuery( search_region, vis );
+
+ Timer::Time the_tree_time = tree_timer.lap();
+ std::cout << "Rtree (good): " << the_tree_time << std::endl;
+
+
+ //-------------------------------------------
+ // Search tree - worst case
+ Timer tree_timer_2;
+ tree_timer_2.ask_for_timeslice();
+ tree_timer_2.start();
+
+ /* search the whole area, so all items are returned */
+ plow[0] = -limit - 100;
+ plow[1] = -limit - 100;
+ phigh[0] = limit + 100;
+ phigh[1] = limit + 100;
+
+ SpatialIndex::Region search_region_2 = SpatialIndex::Region(plow, phigh, 2);
+ SearchVisitor vis_2 = SearchVisitor();
+ tree->intersectsWithQuery( search_region_2, vis_2 );
+
+ Timer::Time the_tree_time_2 = tree_timer_2.lap();
+ std::cout << "Rtree (full scan): " << the_tree_time_2 << std::endl;
+
+ return 0;
+}
+
+
+
+/* Adds rectangles in a GList and a SpatialIndex rtree
+ * */
+void add_rectangle( int x, int y ) {
+
+ Geom::Point starting_point = Point( x, y );
+ Geom::Point ending_point = Point( x + 10, y + 10 );
+ Geom::Rect rect_to_add = Rect( starting_point, ending_point );
+ items = g_list_append( items, &rect_to_add );
+
+ if( arg_debug ) {
+ // fetch the last rect from the list
+ Geom::Rect *member_data = (Geom::Rect *)( g_list_last( items ) )->data;
+ double lala = member_data->bottom();
+ std::cout << "List (" << indexID << "): " << lala;
+ }
+
+ /* Create a SpatialIndex region
+ * plow = left-bottom corner
+ * phigh = top-right corner
+ * [0] = dimension X
+ * [1] = dimension Y
+ * */
+ double plow[2], phigh[2];
+
+ plow[0] = rect_to_add.left() ;
+ plow[1] = rect_to_add.bottom();
+ phigh[0] = rect_to_add.right();
+ phigh[1] = rect_to_add.top();
+
+ SpatialIndex::Region r = SpatialIndex::Region(plow, phigh, 2);
+ /* Store Glist pointer size and GList pointer as the associated data
+ * In inkscape this can be used to directly call render hooked functions
+ * from SPCanvasItems
+ * */
+ tree->insertData( sizeof(GList*), reinterpret_cast<const byte*>( g_list_last( items ) ), r, indexID);
+
+ // tree->insertData(0, 0, r, indexID);
+ /* not used. Store zero size and a null pointer as the associated data.
+ * indexId is used to retrieve from a mapping each rect
+ * (example a hash map, or the indexID is also vector index)
+ * */
+
+ if( arg_debug ) {
+ test_indexID = indexID;
+ /* every time we add a rect, search all the tree to find the last
+ * inserted ID. This is not performance-wise good (rtree only good for
+ * spatial queries) this is just used for debugging reasons
+ * */
+ plow[0] = -limit - 100;
+ plow[1] = -limit - 100;
+ phigh[0] = limit + 100;
+ phigh[1] = limit + 100;
+
+ SpatialIndex::Region test_search_region = SpatialIndex::Region(plow, phigh, 2);
+ TestSearchVisitor test_vis = TestSearchVisitor();
+ // search the tree for the region. Visitor implements the search function hooks
+ tree->intersectsWithQuery( test_search_region, test_vis );
+ }
+
+ indexID++;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)(c-basic-offset . 4))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/rtree-test.cpp b/tests/rtree-test.cpp
new file mode 100644
index 0000000..f01007a
--- /dev/null
+++ b/tests/rtree-test.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+/*
+ initial toy for redblack trees
+*/
+
+
+#include <2geom/rtree.h>
+
+#include <time.h>
+#include <vector>
+
+#include <sstream>
+#include <getopt.h>
+
+
+
+
+//using std::vector;
+using namespace Geom;
+using namespace std;
+
+sadfsdfasdfasdfa
+
+int main(int argc, char **argv) {
+
+ long test_seed = 1243716824;
+
+ char* min_arg = NULL;
+ char* max_arg = NULL;
+ char* filename_arg = NULL;
+
+ int set_min_max = 0;
+
+ int c;
+
+ while (1)
+ {
+ static struct option long_options[] =
+ {
+ /* These options set a flag. */
+ /* These options don't set a flag.
+ We distinguish them by their indices. */
+ {"min-nodes", required_argument, 0, 'n'},
+ {"max-nodes", required_argument, 0, 'm'},
+ {"input-file", required_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+ /* getopt_long stores the option index here. */
+ int option_index = 0;
+
+ c = getopt_long (argc, argv, "n:m:f:h",
+ long_options, &option_index);
+
+ /* Detect the end of the options. */
+ if (c == -1){
+ break;
+ }
+
+ switch (c)
+ {
+ case 'n':
+ min_arg = optarg;
+ set_min_max += 1;
+ break;
+
+
+ case 'm':
+ max_arg = optarg;
+ set_min_max += 2;
+ break;
+
+ case 'f':
+ filename_arg = optarg;
+ set_min_max += 3;
+ break;
+
+
+ case 'h':
+ std::cerr << "Usage: " << argv[0] << " options\n" << std::endl ;
+ std::cerr <<
+ " -n --min-nodes=NUMBER minimum number in node.\n" <<
+ " -m --max-nodes=NUMBER maximum number in node.\n" <<
+ " -f --max-nodes=NUMBER maximum number in node.\n" <<
+ " -h --help Print this help.\n" << std::endl;
+ exit(1);
+ break;
+
+
+ case '?':
+ /* getopt_long already printed an error message. */
+ break;
+
+ default:
+ abort ();
+ }
+ }
+
+ unsigned rmin = 0;
+ unsigned rmax = 0;
+
+ if( set_min_max == 6 ){
+ stringstream s1( min_arg );
+ s1 >> rmin;
+
+ stringstream s2( max_arg );
+ s2 >> rmax;
+
+
+ if( rmax <= rmin || rmax < 2 || rmin < 1 ){
+ std::cerr << "Rtree set to 2, 3" << std::endl ;
+ rmin = 2;
+ rmax = 3;
+ }
+ }
+ else{
+ std::cerr << "Rtree set to 2, 3 ." << std::endl ;
+ rmin = 2;
+ rmax = 3;
+ }
+
+
+
+ std::cout << "rmin: " << rmin << " rmax:" << rmax << " filename_arg:" << filename_arg << std::endl;
+
+ RTree rtree( rmin, rmax, QUADRATIC_SPIT );
+
+ srand(1243716824);
+ rand() % 10;
+
+ return 0;
+}
diff --git a/tests/sbasis-test.cpp b/tests/sbasis-test.cpp
new file mode 100644
index 0000000..045d409
--- /dev/null
+++ b/tests/sbasis-test.cpp
@@ -0,0 +1,268 @@
+#include "testing.h"
+#include <iostream>
+
+#include <2geom/bezier.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <vector>
+#include <iterator>
+#include <glib.h>
+
+using namespace std;
+using namespace Geom;
+
+bool are_equal(SBasis const &A, SBasis const &B) {
+ int maxSize = max(A.size(), B.size());
+ double t = 0., dt = 1./maxSize;
+
+ for(int i = 0; i <= maxSize; i++) {
+ EXPECT_FLOAT_EQ(A.valueAt(t), B.valueAt(t));// return false;
+ t += dt;
+ }
+ return true;
+}
+
+class SBasisTest : public ::testing::Test {
+protected:
+ friend class Geom::SBasis;
+ SBasisTest()
+ : zero(fragments[0])
+ , unit(fragments[1])
+ , hump(fragments[2])
+ , wiggle(fragments[3])
+ {
+ zero = SBasis(Bezier(0.0).toSBasis());
+ unit = SBasis(Bezier(0.0,1.0).toSBasis());
+ hump = SBasis(Bezier(0,1,0).toSBasis());
+ wiggle = SBasis(Bezier(0,1,-2,3).toSBasis());
+ }
+
+ SBasis fragments[4];
+ SBasis &zero, &unit, &hump, &wiggle;
+};
+
+TEST_F(SBasisTest, UnitTests) {
+ EXPECT_TRUE(Bezier(0,0,0,0).toSBasis().isZero());
+ EXPECT_TRUE(Bezier(0,1,2,3).toSBasis().isFinite());
+
+ // note: "size" of sbasis equals half the number of coefficients
+ EXPECT_EQ(2u, Bezier(0,2,4,5).toSBasis().size());
+ EXPECT_EQ(2u, hump.size());
+}
+
+TEST_F(SBasisTest, ValueAt) {
+ EXPECT_EQ(0.0, wiggle.at0());
+ EXPECT_EQ(3.0, wiggle.at1());
+ EXPECT_EQ(0.0, wiggle.valueAt(0.5));
+ EXPECT_EQ(0.0, wiggle(0.5));
+}
+
+TEST_F(SBasisTest, MultiDerivative) {
+ vector<double> vnd = wiggle.valueAndDerivatives(0.5, 5);
+ expect_array((const double[]){0,0,12,72,0,0}, vnd);
+}
+ /*
+TEST_F(SBasisTest, DegreeElevation) {
+ EXPECT_TRUE(are_equal(wiggle, wiggle));
+ SBasis Q = wiggle;
+ SBasis P = Q.elevate_degree();
+ EXPECT_EQ(P.size(), Q.size()+1);
+ //EXPECT_EQ(0, P.forward_difference(1)[0]);
+ EXPECT_TRUE(are_equal(Q, P));
+ Q = wiggle;
+ P = Q.elevate_to_degree(10);
+ EXPECT_EQ(10, P.order());
+ EXPECT_TRUE(are_equal(Q, P));
+ //EXPECT_EQ(0, P.forward_difference(10)[0]);
+}*/
+//std::pair<SBasis, SBasis > subdivide(Coord t);
+
+SBasis linear_root(double t) {
+ return SBasis(Linear(0-t, 1-t));
+}
+
+SBasis array_roots(vector<double> x) {
+ SBasis b(1);
+ for(double i : x) {
+ b = multiply(b, linear_root(i));
+ }
+ return b;
+}
+
+ /*TEST_F(SBasisTest, Deflate) {
+ SBasis b = array_roots(vector_from_array((const double[]){0,0.25,0.5}));
+ EXPECT_FLOAT_EQ(0, b.at0());
+ b = b.deflate();
+ EXPECT_FLOAT_EQ(0, b.valueAt(0.25));
+ b = b.subdivide(0.25).second;
+ EXPECT_FLOAT_EQ(0, b.at0());
+ b = b.deflate();
+ const double rootposition = (0.5-0.25) / (1-0.25);
+ EXPECT_FLOAT_EQ(0, b.valueAt(rootposition));
+ b = b.subdivide(rootposition).second;
+ EXPECT_FLOAT_EQ(0, b.at0());
+}*/
+
+TEST_F(SBasisTest, Roots) {
+ expect_array((const double[]){0, 0.5, 0.5}, roots(wiggle));
+
+ // The results of our rootfinding are at the moment fairly inaccurate.
+ double eps = 5e-4;
+
+ vector<vector<double> > tests;
+ tests.push_back(vector_from_array((const double[]){0}));
+ tests.push_back(vector_from_array((const double[]){0.5}));
+ tests.push_back(vector_from_array((const double[]){0.25,0.75}));
+ tests.push_back(vector_from_array((const double[]){0.5,0.5}));
+ tests.push_back(vector_from_array((const double[]){0, 0.2, 0.6,0.6, 1}));
+ tests.push_back(vector_from_array((const double[]){.1,.2,.3,.4,.5,.6}));
+ tests.push_back(vector_from_array((const double[]){0.25,0.25,0.25,0.75,0.75,0.75}));
+
+ for(auto & test : tests) {
+ SBasis b = array_roots(test);
+ std::cout << test << ": " << b << std::endl;
+ std::cout << roots(b) << std::endl;
+ EXPECT_vector_near(test, roots(b), eps);
+ }
+
+ vector<Linear> broken;
+ broken.emplace_back(0, 42350.1);
+ broken.emplace_back(-71082.3, -67071.5);
+ broken.emplace_back(1783.41, 796047);
+ SBasis b(broken);
+ Bezier bz;
+ sbasis_to_bezier(bz, b);
+ cout << "roots(SBasis(broken))\n";
+ for(int i = 0; i < 10; i++) {
+ double t = i*0.01 + 0.1;
+ cout << b(t) << "," << bz(t) << endl;
+ }
+ cout << roots(b) << endl;
+ EXPECT_EQ(0, bz[0]);
+ //bz = bz.deflate();
+ cout << bz << endl;
+ cout << bz.roots() << endl;
+}
+
+TEST_F(SBasisTest, Subdivide) {
+ std::vector<std::pair<SBasis, double> > errors;
+ for (unsigned i = 0; i < 10000; ++i) {
+ double t = g_random_double_range(0, 1e-6);
+ for (auto & input : fragments) {
+ std::pair<SBasis, SBasis> result;
+ result.first = portion(input, 0, t);
+ result.second = portion(input, t, 1);
+
+ // the endpoints must correspond exactly
+ EXPECT_EQ(result.first.at0(), input.at0());
+ EXPECT_EQ(result.first.at1(), result.second.at0());
+ EXPECT_EQ(result.second.at1(), input.at1());
+
+ // ditto for valueAt
+ EXPECT_EQ(result.first.valueAt(0), input.valueAt(0));
+ EXPECT_EQ(result.first.valueAt(1), result.second.valueAt(0));
+ EXPECT_EQ(result.second.valueAt(1), input.valueAt(1));
+
+ if (result.first.at1() != result.second.at0()) {
+ errors.emplace_back(input, t);
+ }
+ }
+ }
+ if (!errors.empty()) {
+ std::cout << "Found " << errors.size() << " subdivision errors" << std::endl;
+ for (unsigned i = 0; i < errors.size(); ++i) {
+ std::cout << "Error #" << i << ":\n"
+ << "SBasis: " << errors[i].first << "\n"
+ << "t: " << format_coord_nice(errors[i].second) << std::endl;
+ }
+ }
+}
+
+TEST_F(SBasisTest, Reverse) {
+ SBasis reverse_wiggle = reverse(wiggle);
+ EXPECT_EQ(reverse_wiggle.at0(), wiggle.at1());
+ EXPECT_EQ(reverse_wiggle.at1(), wiggle.at0());
+ EXPECT_EQ(reverse_wiggle.valueAt(0.5), wiggle.valueAt(0.5));
+ EXPECT_EQ(reverse_wiggle.valueAt(0.25), wiggle.valueAt(0.75));
+ EXPECT_TRUE(are_equal(reverse(reverse_wiggle), wiggle));
+}
+
+TEST_F(SBasisTest,Operators) {
+ //cout << "scalar operators\n";
+ //cout << hump + 3 << endl;
+ //cout << hump - 3 << endl;
+ //cout << hump*3 << endl;
+ //cout << hump/3 << endl;
+
+ //cout << "SBasis derivative(const SBasis & a);\n";
+ //std::cout << derivative(hump) <<std::endl;
+ //std::cout << integral(hump) <<std::endl;
+
+ EXPECT_TRUE(are_equal(derivative(integral(wiggle)), wiggle));
+ //std::cout << derivative(integral(hump)) << std::endl;
+ expect_array((const double []){0.5}, roots(derivative(hump)));
+
+ EXPECT_TRUE(bounds_fast(hump)->contains(Interval(0,hump.valueAt(0.5))));
+
+ EXPECT_EQ(Interval(0,hump.valueAt(0.5)), *bounds_exact(hump));
+
+ Interval tight_local_bounds(min(hump.valueAt(0.3),hump.valueAt(0.6)),
+ hump.valueAt(0.5));
+ EXPECT_TRUE(bounds_local(hump, Interval(0.3, 0.6))->contains(tight_local_bounds));
+
+ SBasis Bs[] = {unit, hump, wiggle};
+ for(auto B : Bs) {
+ SBasis product = multiply(B, B);
+ for(int i = 0; i <= 16; i++) {
+ double t = i/16.0;
+ double b = B.valueAt(t);
+ EXPECT_FLOAT_EQ(b*b, product.valueAt(t));
+ }
+ }
+}
+
+TEST_F(SBasisTest, ToCubicBezier)
+{
+ vector<double> params = { 0, 1, -2, 3 };
+
+ D2<SBasis> sb(wiggle, wiggle);
+ vector<Point> bz;
+ sbasis_to_cubic_bezier(bz, sb);
+ for (int i = 0; i < params.size(); i++) {
+ EXPECT_FLOAT_EQ(bz[i][0], params[i]);
+ EXPECT_FLOAT_EQ(bz[i][1], params[i]);
+ }
+}
+
+TEST_F(SBasisTest, Roundtrip)
+{
+ auto bz1 = Bezier(1, -2, 3, 7, 11, -24, 42, -1, 9, 1);
+ auto sbasis = bz1.toSBasis();
+ Bezier bz2;
+ sbasis_to_bezier(bz2, sbasis);
+ ASSERT_EQ(bz1, bz2);
+
+ std::vector<Point> pts;
+ for (int i = 0; i < bz1.size(); i++) {
+ pts.emplace_back(bz1[i], bz1[i]);
+ }
+ D2<SBasis> sbasis_d2;
+ bezier_to_sbasis(sbasis_d2, pts);
+ ASSERT_EQ(sbasis_d2[X], sbasis);
+ ASSERT_EQ(sbasis_d2[Y], sbasis);
+ D2<Bezier> bz2_d2;
+ sbasis_to_bezier(bz2_d2, sbasis_d2);
+ ASSERT_EQ(bz2_d2[X], bz1);
+ ASSERT_EQ(bz2_d2[Y], bz1);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/sbasis-text-test.cpp b/tests/sbasis-text-test.cpp
new file mode 100644
index 0000000..ef96407
--- /dev/null
+++ b/tests/sbasis-text-test.cpp
@@ -0,0 +1,225 @@
+#include <iostream>
+#include <math.h>
+#include <cassert>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-poly.h>
+#include <iterator>
+#include <2geom/point.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/solver.h>
+
+using namespace Geom;
+
+Poly roots_to_poly(double *a, unsigned n) {
+ Poly r;
+ r.push_back(1);
+
+ for(unsigned i = 0; i < n; i++) {
+ Poly p;
+ p.push_back(-a[i]);
+ p.push_back(1);
+ r = r*p;
+ }
+ return r;
+}
+
+unsigned small_rand() {
+ return (rand() & 0xff) + 1;
+}
+
+double uniform() {
+ return double(rand()) / RAND_MAX;
+}
+
+int main() {
+ SBasis P0(Linear(0.5, -1)), P1(Linear(3, 1));
+ Linear one(1,1);
+
+ std::cout << "round tripping of poly conversions\n";
+ std::cout << P0
+ << "=>" << sbasis_to_poly(P0)
+ << "=>" << poly_to_sbasis(sbasis_to_poly(P0))
+ << std::endl;
+
+ std::cout << "derivatives and integrals\n";
+
+ Poly test;
+ for(int i = 0; i < 4; i++)
+ test.push_back(1);
+
+ SBasis test_sb = poly_to_sbasis(test);
+ std::cout << test << "(" << test.size() << ")"
+ << " == "
+ << test_sb << "(" << test_sb.size() << ")"
+ << std::endl;
+ std::cout << "derivative\n";
+ std::cout << derivative(test)
+ << " == "
+ << sbasis_to_poly(derivative(test_sb))
+ << std::endl;
+
+ std::cout << "integral\n";
+ std::cout << integral(test)
+ << " == "
+ << sbasis_to_poly(integral(test_sb))
+ << std::endl;
+
+ std::cout << "evaluation\n";
+ std::cout << integral(test)(0.3) - integral(test)(0.)
+ << " == "
+ << integral(test_sb)(0.3) - integral(test_sb)(0.)
+ << std::endl;
+
+ std::cout << "multiplication\n";
+ std::cout << (test*test)
+ << "\n == \n"
+ << sbasis_to_poly(multiply(test_sb,test_sb))
+ << std::endl;
+ std::cout << poly_to_sbasis(test*test)
+ << "\n == \n"
+ << multiply(test_sb,test_sb)
+ << std::endl;
+
+ std::cout << "sqrt\n";
+ std::cout << test
+ << "\n == \n"
+ << sbasis_to_poly(sqrt(multiply(test_sb,test_sb),10))
+ << std::endl;
+ SBasis radicand = sqrt(test_sb,10);
+ std::cout << sbasis_to_poly(truncate(multiply(radicand, radicand),5))
+ << "\n == \n"
+ << test
+ << std::endl;
+
+ std::cout << "division\n";
+ std::cout << test
+ << "\n == \n"
+ << sbasis_to_poly(divide(multiply(test_sb,test_sb),test_sb, 20))
+ << std::endl;
+ std::cout << divide(test_sb, radicand,5)
+ << "\n == \n"
+ << truncate(radicand,6)
+ << std::endl;
+
+ std::cout << "composition\n";
+ std::cout << (compose(test,sbasis_to_poly(Linear(0.5,1))))
+ << "\n == \n"
+ << sbasis_to_poly(compose(test_sb,Linear(0.5,1)))
+ << std::endl;
+ std::cout << poly_to_sbasis(compose(test,test))
+ << "\n == \n"
+ << compose(test_sb,test_sb)
+ << std::endl
+ << std::endl;
+ std::cout << (compose(sbasis_to_poly(Linear(1,2)),sbasis_to_poly(Linear(-1,0))))
+ << std::endl;
+ std::cout << (compose(SBasis(Linear(1,2)),SBasis(Linear(-1,0))))
+ << std::endl;
+
+ std::cout << "inverse of x - 1\n";
+ std::cout << sbasis_to_poly(inverse(Linear(-1,0),2))
+ << " == y + 1\n";
+ std::cout << "f^-1(f(x)) = "
+ << sbasis_to_poly(compose(inverse(Linear(-1,0),2),
+ Linear(-1,0)))
+ << std::endl
+ << std::endl;
+
+ std::cout << "inverse of 3x - 2\n";
+ std::cout << sbasis_to_poly(inverse(Linear(-2,1),2))
+ << " == (y + 2)/3\n";
+ std::cout << "f^-1(f(x)) = "
+ << sbasis_to_poly(compose(inverse(Linear(-2,1),2),
+ Linear(-2,1)))
+ << std::endl
+ << std::endl;
+
+
+ std::cout << "inverse of sqrt(" << sbasis_to_poly(Linear(1,4)) << ") - 1\n";
+ SBasis A = sqrt(Linear(1,4), 5) - one;
+ Poly P;
+ P.push_back(0);
+ P.push_back(2./3);
+ P.push_back(1./3);
+
+ std::cout << "2 term approximation\n";
+ std::cout << sbasis_to_poly(inverse(A,2))
+ << "\n == \n"
+ << P
+ << std::endl;
+ std::cout << "general approximation\n";
+ std::cout << sbasis_to_poly(inverse(A,5))
+ << "\n == \n"
+ << P
+ << std::endl;
+
+ {
+ std::cout << "inverse of (x^2+2x)/3\n";
+ SBasis A = poly_to_sbasis(P);
+ SBasis I = inverse(A,10);
+ std::cout << sbasis_to_poly(truncate(compose(A, I), 10))
+ << " == x\n"
+ << std::endl;
+ std::cout << sbasis_to_poly(truncate(compose(I, A), 10))
+ << " == x\n"
+ << std::endl;
+ std::cout << sbasis_to_poly(truncate(I - (sqrt(Linear(1,4), 10) - one), 10))
+ << std::endl;
+ }
+#ifdef HAVE_GSL
+ for(int i = 0 ; i < 10; i++) {
+ Poly P;
+ P.push_back(0);
+ P.push_back(1);
+ for(int j = 0 ; j < 2; j++) {
+ P.push_back((uniform()-0.5)/10);
+ }
+ std::vector<std::complex<double> > prod_root = solve(derivative(P));
+ copy(prod_root.begin(), prod_root.end(),
+ std::ostream_iterator<std::complex<double> >(std::cout, ",\t"));
+ std::cout << std::endl;
+ std::cout << "inverse of (" << P << " )\n";
+ for(int k = 1; k < 30; k++) {
+ SBasis A = poly_to_sbasis(P);
+ SBasis I = inverse(A,k);
+ SBasis err = compose(A, I) - Linear(0,1); // ideally is 0
+ std::cout << truncate(err, k).tailError(0)
+ << std::endl;
+ /*std::cout << sbasis_to_poly(err)
+ << " == x\n"
+ << std::endl;*/
+ }
+ }
+#endif
+ /*double roots[] = {0.1,0.2,0.6};
+ Poly prod = roots_to_poly(roots, sizeof(roots)/sizeof(double));
+ std::cout << "real solve\n";
+ std::cout << prod
+ << " solves as ";
+ std::vector<std::complex<double> > prod_root = solve(prod);
+ copy(prod_root.begin(), prod_root.end(),
+ std::ostream_iterator<std::complex<double> >(std::cout, ",\t"));
+ std::cout << std::endl;
+
+ SBasis prod_sb = poly_to_sbasis(prod);
+ std::vector<double> bez = sbasis_to_bezier(prod_sb, prod_sb.size());
+
+ copy(bez.begin(), bez.end(),
+ std::ostream_iterator<double>(std::cout, ",\t"));
+ std::cout << std::endl;*/
+
+ /*std::cout << "crossing count = "
+ << crossing_count(bez, bez.size())
+ << std::endl;*/
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/self-intersections-test.cpp b/tests/self-intersections-test.cpp
new file mode 100644
index 0000000..268273f
--- /dev/null
+++ b/tests/self-intersections-test.cpp
@@ -0,0 +1,219 @@
+/** @file
+ * @brief Unit tests for PathVector::intersectSelf()
+ */
+/*
+ * Authors:
+ * Rafał Siejakowski <rs@rs-math.net>
+ *
+ * Copyright 2022 Authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <gtest/gtest.h>
+#include <2geom/pathvector.h>
+#include <2geom/svg-path-parser.h>
+
+using namespace Geom;
+
+#define PV(d) (parse_svg_path(d))
+#define PTH(d) (PV(d)[0])
+
+class PVSelfIntersections : public testing::Test
+{
+protected:
+ PathVector const _rectangle, _bowtie, _bowtie_curved, _bowtie_node, _openpath,
+ _open_closed_nonintersecting, _open_closed_intersecting, _tangential, _degenerate_segments,
+ _degenerate_closing, _degenerate_multiple;
+
+ PVSelfIntersections()
+ // A simple rectangle.
+ : _rectangle{PV("M 0,0 L 5,0 5,8 0,8 Z")}
+ // A polyline path with a self-intersection @(2,1).
+ , _bowtie{PV("M 0,0 L 4,2 V 0 L 0,2 Z")}
+ // A curved bow-tie path with a self-intersection @(10,5) between cubic Béziers.
+ , _bowtie_curved{PV("M 0,0 V 10 C 10,10 10,0 20,0 V 10 C 10,10 10,0 0,0 Z")}
+ // As above, but twice as large and the self-intersection @(20,10) happens at a node.
+ , _bowtie_node{PV("M 0,0 V 20 C 0,20 10,20 20,10 25,5 30,0 40,0 V 20 "
+ "C 30,20 25,15 20,10 10,0 0,0 0,0 Z")}
+ // An open path with no self-intersections ◠―◡
+ , _openpath{PV("M 0,0 A 10,10 0,0,1 20,0 L 40,0 Q 50,10 60,0")}
+ // A line and a square with no intersections | □
+ , _open_closed_nonintersecting{PV("M 0,0 V 20 M 10,0 V 20 H 30 V 0 Z")}
+ // A line slicing through a square; two self-intersections ⎅
+ , _open_closed_intersecting{PV("M 10,0 V 40 M 0,10 V 30 H 20 V 10 Z")}
+ // A circle whose diameter precisely coincides with the top side of a rectangle.
+ , _tangential{PV("M 0,0 A 10,10 0,0,1 20,0 A 10,10, 0,0,1 0,0 Z M 0,0 H 20 V 30 H 0 Z")}
+ // A rectangle containing degenerate segments.
+ , _degenerate_segments{PV("M 0,0 H 5 V 4 L 5,4 V 8 H 5 L 5,8 H 0 Z")}
+ // A rectangle with a degenerate closing segment.
+ , _degenerate_closing{PV("M 0,0 H 5 V 8 H 0 L 0,0 Z")}
+ // Multiple consecutive degenerate segments, with a degenerate closing segment in the middle.
+ , _degenerate_multiple{PV("M 0,0 L 0,0 V 0 H 0 L 5,0 V 8 H 0 L 0,0 V 0 H 0 Z")}
+ {
+ }
+};
+
+/* Ensure that no spurious intersections are returned. */
+TEST_F(PVSelfIntersections, NoSpurious)
+{
+ auto empty = PathVector();
+ EXPECT_EQ(empty.intersectSelf().size(), 0u);
+
+ auto r = _rectangle.intersectSelf();
+ EXPECT_EQ(r.size(), 0u);
+
+ auto o = _openpath.intersectSelf();
+ EXPECT_EQ(o.size(), 0u);
+
+ auto n = _open_closed_nonintersecting.intersectSelf();
+ EXPECT_EQ(n.size(), 0u);
+
+ auto d = _degenerate_segments.intersectSelf();
+ EXPECT_EQ(d.size(), 0u);
+
+ auto dc = _degenerate_closing.intersectSelf();
+ EXPECT_EQ(dc.size(), 0u);
+
+ auto dm = _degenerate_multiple.intersectSelf();
+ EXPECT_EQ(dm.size(), 0u);
+
+ auto cusp_node = PTH("M 1 3 C 12 8 42 101 86 133 C 78 168 136 83 80 64");
+ EXPECT_EQ(cusp_node.intersectSelf().size(), 0u);
+}
+
+/* Test figure-eight shaped paths */
+TEST_F(PVSelfIntersections, Bowties)
+{
+ // Simple triangular bowtie: intersection between straight lines
+ auto triangular = _bowtie.intersectSelf();
+ EXPECT_EQ(triangular.size(), 1u);
+ ASSERT_GT(triangular.size(), 0u); // To ensure access to [0]
+ EXPECT_TRUE(are_near(triangular[0].point(), Point(2, 1)));
+
+ // Curved bowtie: intersection between cubic Bézier curves
+ auto curved_intersections = _bowtie_curved.intersectSelf();
+ EXPECT_EQ(curved_intersections.size(), 1u);
+ ASSERT_GT(curved_intersections.size(), 0u);
+ EXPECT_TRUE(are_near(curved_intersections[0].point(), Point(10, 5)));
+
+ // Curved bowtie but the intersection point is a node on both paths
+ auto node_case_intersections = _bowtie_node.intersectSelf();
+ EXPECT_EQ(node_case_intersections.size(), 1u);
+ ASSERT_GT(node_case_intersections.size(), 0u);
+ EXPECT_TRUE(are_near(node_case_intersections[0].point(), Point(20, 10)));
+}
+
+/* Test intersecting an open path with a closed one */
+TEST_F(PVSelfIntersections, OpenClosed)
+{
+ // Square cut by a vertical line
+ auto open_closed = _open_closed_intersecting.intersectSelf();
+ auto const P1 = Point(10, 10);
+ auto const P2 = Point(10, 30);
+
+ ASSERT_EQ(open_closed.size(), 2u); // Prevent crash on out-of-bounds access
+ // This test doesn't care about how the intersections are ordered.
+ bool points_as_expected = (are_near(open_closed[0].point(), P1) && are_near(open_closed[1].point(), P2))
+ || (are_near(open_closed[0].point(), P2) && are_near(open_closed[1].point(), P1));
+ EXPECT_TRUE(points_as_expected);
+}
+
+/* Test some nasty, tangential crossings: a circle with a rectangle built on its diameter. */
+TEST_F(PVSelfIntersections, Tangential)
+{
+ auto circle_x_rect = _tangential.intersectSelf();
+ auto const P1 = Point(0, 0);
+ auto const P2 = Point(20, 0);
+
+ ASSERT_EQ(circle_x_rect.size(), 2u); // Prevent crash on out-of-bounds access
+ // This test doesn't care how the intersections are ordered.
+ bool points_as_expected = (are_near(circle_x_rect[0].point(), P1) && are_near(circle_x_rect[1].point(), P2))
+ || (are_near(circle_x_rect[0].point(), P2) && are_near(circle_x_rect[1].point(), P1));
+ EXPECT_TRUE(points_as_expected);
+}
+
+/* Regression test for issue https://gitlab.com/inkscape/lib2geom/-/issues/33 */
+TEST_F(PVSelfIntersections, Regression33)
+{
+ // Test case provided by Pascal Bies in the issue description.
+ auto const line = LineSegment(Point(486, 597), Point(313, 285));
+ Point const c{580.1377046525328, 325.5830744834947};
+ Point const d{289.35338528516013, 450.62476639303753};
+ auto const curve = CubicBezier(c, c, d, d);
+
+ EXPECT_EQ(curve.intersect(line).size(), 1);
+}
+
+/* Regression test for issue https://gitlab.com/inkscape/lib2geom/-/issues/46 */
+TEST_F(PVSelfIntersections, NumericalInstability)
+{
+ // Test examples provided by M.B. Fraga in the issue report.
+ auto missing_intersection = PTH("M 138 237 C 293 207 129 12 167 106 Q 205 200 309 198 z");
+ auto missing_xings = missing_intersection.intersectSelf();
+ EXPECT_EQ(missing_xings.size(), 2);
+
+ auto duplicate_intersection = PTH("M 60 280 C 60 213 236 227 158 178 S 174 306 127 310 Q 80 314 60 280 z");
+ auto const only_expected = Point(130.9693916417836, 224.587385497877);
+ auto duplicate_xings = duplicate_intersection.intersectSelf();
+ ASSERT_EQ(duplicate_xings.size(), 1);
+ EXPECT_TRUE(are_near(duplicate_xings[0].point(), only_expected));
+}
+
+/* Check various numerically challenging paths consisting of 2 cubic Béziers. */
+TEST_F(PVSelfIntersections, NumericallyChallenging)
+{
+ auto two_kinks = PTH("M 85 88 C 4 425 19 6 72 426 C 128 6 122 456 68 96");
+ EXPECT_EQ(two_kinks.intersectSelf().size(), 3);
+
+ auto omega = PTH("M 47 132 C 179 343 0 78 106 74 C 187 74 0 358 174 106");
+ EXPECT_EQ(omega.intersectSelf().size(), 0);
+
+ auto spider = PTH("M 47 132 C 203 339 0 78 106 74 C 187 74 0 358 174 106");
+ EXPECT_EQ(spider.intersectSelf().size(), 4);
+
+ auto egret = PTH("M 38 340 C 183 141 16 76 255 311 C 10 79 116 228 261 398");
+ EXPECT_EQ(egret.intersectSelf().size(), 0);
+}
+
+/* Test a regression from 88040ea2aeab8ccec2b0e96c7bda2fc7d500d5ec */
+TEST_F(PVSelfIntersections, BigonFiltering)
+{
+ auto const lens = PTH("M 0,0 C 2,1 3,1 5,0 A 2.5,1 0 1 0 0,0 Z");
+ auto const xings = lens.intersectSelf();
+ // This is a simple closed path, so we expect that no self-intersections are reported.
+ EXPECT_EQ(xings.size(), 0);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/tests/test_pwsb.py b/tests/test_pwsb.py
new file mode 100644
index 0000000..6182d40
--- /dev/null
+++ b/tests/test_pwsb.py
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+
+import sys
+sys.path.append(os.path.join(os.path.dirname(__file__), "..", "py2geom"))
+
+from py2geom import *
+import py2geom
+import numpy
+import random
+from py2geom_glue import *
+
+def poly_to_sbasis(p):
+ sb = SBasis()
+ s = numpy.poly1d([-1, 1, 0])
+ while True:
+ q,r = p / s
+ x = Linear(r[0],r[1]+r[0])
+ sb.append(x)
+ p = q
+ if len(list(p)) <= 1 and p[0] == 0:
+ return sb
+
+def sbasis_to_poly(sb):
+ p = numpy.poly1d([0])
+ s = numpy.poly1d([-1, 1, 0])
+ sp = numpy.poly1d([1])
+ for sbt in sb:
+ p += sp*(sbt[0]*(numpy.poly1d([-1,1])) + sbt[1]*(numpy.poly1d([1,0])))
+ sp *= s
+ return p
+
+random.seed(1)
+trial = numpy.poly1d([random.randrange(0,10) for x in range(6)])
+
+sb = poly_to_sbasis(trial)
+
+pwsb = PiecewiseSBasis()
+pwsb.push_seg(sb)
+pwsb.push_cut(0)
+pwsb.push_cut(1)
+print pwsb.size()
+print "invariants:", pwsb.invariants()
+print pwsb(0)
+
+def l2s(l):
+ sb = py2geom.SBasis()
+ sb.append(l)
+ return sb
+
+X = l2s(py2geom.Linear(0, 1))
+OmX = l2s(py2geom.Linear(1, 0))
+def bezier_to_sbasis(handles, order):
+ print "b2s:", handles, order
+ if(order == 0):
+ return l2s(py2geom.Linear(handles[0]))
+ elif(order == 1):
+ return l2s(py2geom.Linear(handles[0], handles[1]))
+ else:
+ return (py2geom.multiply(OmX, bezier_to_sbasis(handles[:-1], order-1)) +
+ py2geom.multiply(X, bezier_to_sbasis(handles[1:], order-1)))
+
+
+for bz in [[0,1,0], [0,1,2,3]]:
+ sb = bezier_to_sbasis(bz, len(bz)-1)
+ print bz
+ print sb
+ print sbasis_to_bezier(sb,0)
diff --git a/tests/test_py2geom.py b/tests/test_py2geom.py
new file mode 100644
index 0000000..d6ec83e
--- /dev/null
+++ b/tests/test_py2geom.py
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+
+import sys
+sys.path.append(os.path.join(os.path.dirname(__file__), "..", "py2geom"))
+
+from py2geom import *
+import py2geom
+
+P = Point(1,2)
+Q = Point(3,5)
+print P, Q, P+Q
+
+print L2(P)
+print cross(P,Q)
+#print dir(py2geom)
+
+import numpy
+
+ply = numpy.poly1d([1,0,2])
+
+print ply(0.5)
+t = numpy.poly1d([1,0])
+q,r = ply / t
+print q,r
+
+print ply
+print q*t + r
+print ply
+
+def poly_to_sbasis(p):
+ sb = SBasis()
+ s = numpy.poly1d([-1, 1, 0])
+ while True:
+ q,r = p / s
+ #print "r:", r
+ print "r:", repr(r)
+ x = Linear(r[0],r[1]+r[0])
+ sb.append(x)
+ p = q
+ print "q:", repr(p), len(list(p))
+ if len(list(p)) <= 1 and p[0] == 0:
+ return sb
+
+def sbasis_to_poly(sb):
+ p = numpy.poly1d([0])
+ s = numpy.poly1d([-1, 1, 0])
+ sp = numpy.poly1d([1])
+ for sbt in sb:
+ p += sp*(sbt[0]*(numpy.poly1d([-1,1])) + sbt[1]*(numpy.poly1d([1,0])))
+ sp *= s
+ return p
+
+trial = numpy.poly1d([1,0,2])
+sb = poly_to_sbasis(trial)
+print repr(trial),"p2sb:", sb
+print "and back again:", repr(sbasis_to_poly(sb))
+print repr(sbasis_to_poly(derivative(sb))), repr(trial.deriv())
+
+print "unit tests:"
+x = Linear(0,1)
+sb = SBasis()
+sb.append(x)
+print sb
+sb = sb*sb
+print sb
+print sb[0]
+
+print "terms"
+for i in range(6):
+ sb = SBasis()
+ for j in range(3):
+ sb.append(Linear(i==2*j,i==2*j+1))
+ print sb
+
+ print sbasis_to_poly(sb)
diff --git a/tests/testing.h b/tests/testing.h
new file mode 100644
index 0000000..40a588d
--- /dev/null
+++ b/tests/testing.h
@@ -0,0 +1,186 @@
+#include "gtest/gtest.h"
+#include <vector>
+#include <2geom/coord.h>
+#include <2geom/interval.h>
+#include <2geom/intersection.h>
+
+// streams out a vector
+template <class T>
+std::ostream&
+operator<< (std::ostream &out, const std::vector<T,
+ std::allocator<T> > &v)
+{
+ typedef std::ostream_iterator<T, char,
+ std::char_traits<char> > Iter;
+
+ std::copy (v.begin (), v.end (), Iter (out, " "));
+
+ return out;
+}
+
+template <typename T, unsigned xn>
+std::vector<T> vector_from_array(const T (&x)[xn]) {
+ std::vector<T> v;
+ for(unsigned i = 0; i < xn; i++) {
+ v.push_back(x[i]);
+ }
+ return v;
+}
+
+template <typename T, unsigned xn>
+void expect_array(const T (&x)[xn], std::vector<T> y) {
+ EXPECT_EQ(xn, y.size());
+ for(unsigned i = 0; i < y.size(); i++) {
+ EXPECT_EQ(x[i], y[i]);
+ }
+}
+
+Geom::Interval bound_vector(std::vector<double> const &v) {
+ double low = v[0];
+ double high = v[0];
+ for(double i : v) {
+ low = std::min(i, low);
+ high = std::max(i, high);
+ }
+ return Geom::Interval(low-1, high-1);
+}
+
+
+// Custom assertion formatting predicates
+
+template <typename T>
+::testing::AssertionResult ObjectNear(char const *l_expr,
+ char const *r_expr,
+ char const */*eps_expr*/,
+ T const &l,
+ T const &r,
+ Geom::Coord eps)
+{
+ if (!Geom::are_near(l, r, eps)) {
+ return ::testing::AssertionFailure() << "Objects are not near\n"
+ << "First object: " << l_expr << "\n"
+ << "Value: " << l << "\n"
+ << "Second object: " << r_expr << "\n"
+ << "Value: " << r << "\n"
+ << "Threshold: " << Geom::format_coord_nice(eps) << std::endl;
+ }
+ return ::testing::AssertionSuccess();
+}
+
+template <typename T>
+::testing::AssertionResult ObjectNotNear(char const *l_expr,
+ char const *r_expr,
+ char const */*eps_expr*/,
+ T const &l,
+ T const &r,
+ Geom::Coord eps)
+{
+ if (Geom::are_near(l, r, eps)) {
+ return ::testing::AssertionFailure() << "Objects are near\n"
+ << "First object: " << l_expr << "\n"
+ << "Value: " << l << "\n"
+ << "Second object: " << r_expr << "\n"
+ << "Value: " << r << "\n"
+ << "Threshold: " << Geom::format_coord_nice(eps) << std::endl;
+ }
+ return ::testing::AssertionSuccess();
+}
+
+#define EXPECT_near(a, b, eps) EXPECT_PRED_FORMAT3(ObjectNear, a, b, eps)
+#define EXPECT_not_near(a, b, eps) EXPECT_PRED_FORMAT3(ObjectNotNear, a, b, eps)
+
+
+
+template <typename T>
+::testing::AssertionResult VectorEqual(char const *l_expr,
+ char const *r_expr,
+ std::vector<T> const &l,
+ std::vector<T> const &r)
+{
+ if (l.size() != r.size()) {
+ return ::testing::AssertionFailure() << "Vectors differ in size\n"
+ << l_expr << " has size " << l.size() << "\n"
+ << r_expr << " has size " << r.size() << std::endl;
+ }
+ for (unsigned i = 0; i < l.size(); ++i) {
+ if (!(l[i] == r[i])) {
+ return ::testing::AssertionFailure() << "Vectors differ"
+ << "\nVector: " << l_expr
+ << "\nindex " << i << " contains: " << l[i]
+ << "\nVector:" << r_expr
+ << "\nindex " << i << " contains: " << r[i] << std::endl;
+ }
+ }
+ return ::testing::AssertionSuccess();
+}
+
+template <typename T>
+::testing::AssertionResult VectorNear(char const *l_expr,
+ char const *r_expr,
+ char const */*eps_expr*/,
+ std::vector<T> const &l,
+ std::vector<T> const &r,
+ Geom::Coord eps)
+{
+ if (l.size() != r.size()) {
+ return ::testing::AssertionFailure() << "Vectors differ in size\n"
+ << l_expr << "has size " << l.size() << "\n"
+ << r_expr << "has size " << r.size() << std::endl;
+ }
+ for (unsigned i = 0; i < l.size(); ++i) {
+ if (!Geom::are_near(l[i], r[i], eps)) {
+ return ::testing::AssertionFailure() << "Vectors differ by more than "
+ << Geom::format_coord_nice(eps)
+ << "\nVector: " << l_expr
+ << "\nindex " << i << " contains: " << l[i]
+ << "\nVector:" << r_expr
+ << "\nindex " << i << " contains: " << r[i] << std::endl;
+ }
+ }
+ return ::testing::AssertionSuccess();
+}
+
+#define EXPECT_vector_equal(a, b) EXPECT_PRED_FORMAT2(VectorEqual, a, b)
+#define EXPECT_vector_near(a, b, eps) EXPECT_PRED_FORMAT3(VectorNear, a, b, eps)
+
+
+
+template <typename TA, typename TB>
+::testing::AssertionResult IntersectionsValid(
+ char const *l_expr, char const *r_expr, const char */*xs_expr*/, const char */*eps_expr*/,
+ TA const &shape_a, TB const &shape_b,
+ std::vector<Geom::Intersection<typename Geom::ShapeTraits<TA>::TimeType,
+ typename Geom::ShapeTraits<TB>::TimeType> > const &xs,
+ Geom::Coord eps)
+{
+ std::ostringstream os;
+ bool failed = false;
+
+ for (unsigned i = 0; i < xs.size(); ++i) {
+ Geom::Point pa = shape_a.pointAt(xs[i].first);
+ Geom::Point pb = shape_b.pointAt(xs[i].second);
+ if (!Geom::are_near(pa, xs[i].point(), eps) ||
+ !Geom::are_near(pb, xs[i].point(), eps) ||
+ !Geom::are_near(pb, pa, eps))
+ {
+ os << "Intersection " << i << " does not match\n"
+ << Geom::format_coord_nice(xs[i].first) << " evaluates to " << pa << "\n"
+ << Geom::format_coord_nice(xs[i].second) << " evaluates to " << pb << "\n"
+ << "Reported intersection point is " << xs[i].point() << std::endl;
+ failed = true;
+ }
+ }
+
+ if (failed) {
+ return ::testing::AssertionFailure()
+ << "Intersections do not match\n"
+ << "Shape A: " << l_expr << "\n"
+ << "Shape B: " << r_expr << "\n"
+ << os.str()
+ << "Threshold: " << Geom::format_coord_nice(eps) << std::endl;
+ }
+
+ return ::testing::AssertionSuccess();
+}
+
+#define EXPECT_intersections_valid(a, b, xs, eps) EXPECT_PRED_FORMAT4(IntersectionsValid, a, b, xs, eps)
diff --git a/tests/timing-test.cpp b/tests/timing-test.cpp
new file mode 100644
index 0000000..b5714f7
--- /dev/null
+++ b/tests/timing-test.cpp
@@ -0,0 +1,270 @@
+#include <sys/time.h>
+#include <iostream>
+#include <sstream>
+#include <vector>
+#include <algorithm>
+#include <assert.h>
+#include <time.h>
+#include <sched.h>
+#include <math.h>
+
+const long long US_PER_SECOND = 1000000L;
+const long long NS_PER_US = 1000L;
+
+using namespace std;
+
+class Timer{
+public:
+ Timer() {}
+ // note that CPU time is tracked per-thread, so the timer is only useful
+ // in the thread it was start()ed from.
+ void start() {
+ usec(start_time);
+ }
+ void lap(long long &us) {
+ usec(us);
+ us -= start_time;
+ }
+ long long lap() {
+ long long us;
+ usec(us);
+ return us - start_time;
+ }
+ void usec(long long &us) {
+ clock_gettime(clock, &ts);
+ us = ts.tv_sec * US_PER_SECOND + ts.tv_nsec / NS_PER_US;
+ }
+ /** Ask the OS nicely for a big time slice */
+ void ask_for_timeslice() {
+ sched_yield();
+ }
+private:
+ long long start_time;
+ struct timespec ts;
+#ifdef _POSIX_THREAD_CPUTIME
+ static const clockid_t clock = CLOCK_THREAD_CPUTIME_ID;
+#else
+# ifdef CLOCK_MONOTONIC
+ static const clockid_t clock = CLOCK_MONOTONIC;
+# else
+ static const clockid_t clock = CLOCK_REALTIME;
+# endif
+#endif
+};
+
+int estimate_useful_window()
+{
+ Timer tm;
+ tm.ask_for_timeslice();
+ int window = 1;
+
+ while(1) {
+ tm.start();
+ for(int i = 0; i < window; i++) {}
+ long long base_line = tm.lap();
+ if(base_line > 1 and window > 100)
+ return window;
+ window *= 2;
+ }
+}
+
+template <typename T>
+string robust_timer(T &t) {
+ static int base_rate = estimate_useful_window();
+ //cout << "base line iterations:" << base_rate << endl;
+ double sum = 0;
+ vector<double> results;
+ const int n_trials = 20;
+ results.reserve(n_trials);
+ for(int trials = 0; trials < n_trials; trials++) {
+ Timer tm;
+ tm.ask_for_timeslice();
+ tm.start();
+ int iters = 0;
+ while(tm.lap() < 10000) {
+ for(int i = 0; i < base_rate; i++)
+ t();
+ iters+=base_rate;
+ }
+ base_rate = iters;
+ double lap_time = double(tm.lap());
+ double individual_time = lap_time/base_rate;
+ sum += individual_time;
+ results.push_back(individual_time);
+ //cout << individual_time << endl;
+ }
+ double resS = 0;
+ double resN = 0;
+ sort(results.begin(), results.end());
+ double ave = results[results.size()/2];//sum/n_trials; // median
+ //cout << "median:" << ave << endl;
+ double least = ave;
+ double resSS = 0;
+ for(int i = 0; i < n_trials; i++) {
+ double dt = results[i];
+ if(dt <= ave*1.1) {
+ resS += dt;
+ resN += 1;
+ resSS += dt*dt;
+ if(least < dt)
+ least = dt;
+ }
+ }
+
+ double filtered_ave = resS / resN;
+ double stddev = sqrt((resSS - 2*resS*filtered_ave + resN*filtered_ave*filtered_ave)/(resN-1)); // sum(x-u)^2 = sum(x^2-2xu+u*u)
+ assert (least > filtered_ave*0.7); // If this throws something was really screwy
+ std::basic_stringstream<char> ss;
+ ss << filtered_ave << " +/-" << stddev << "us";
+ return ss.str();
+}
+
+struct nop{
+ void operator()() const {}
+};
+
+#define degenerate_imported 1
+#include "degenerate.cpp"
+using namespace Geom;
+
+template <typename T>
+struct copy{
+ T a, b;
+ void operator()() {
+ T c = a;
+ }
+};
+
+template <typename T>
+struct add{
+ T a, b;
+ void operator()() {
+ T c = a + b;
+ }
+};
+
+template <typename T>
+struct add_mutate{
+ T a, b;
+ void operator()() {
+ a += b;
+ }
+};
+
+template <typename T>
+struct scale{
+ T a;
+ double b;
+ void operator()() {
+ T c = a * b;
+ }
+};
+
+template <typename T>
+struct scale_mutate{
+ T a;
+ double b;
+ void operator()() {
+ a *= b;
+ }
+};
+
+template <typename T>
+struct mult{
+ T a, b;
+ void operator()() {
+ T c = a * b;
+ }
+};
+
+template <typename T>
+struct mult_mutate{
+ T a, b, c;
+ void operator()() {
+ c = a;
+ c *= b;
+ }
+};
+
+template <typename T>
+void basic_arith(T const & a, T const & b) {
+ {
+ ::copy<T> A;
+ A.a = a;
+ A.b = b;
+ cout << "copy:"
+ << robust_timer(A) << endl;
+ }
+ {
+ add<T> A;
+ A.a = a;
+ A.b = b;
+ cout << "add:"
+ << robust_timer(A) << endl;
+ }
+ {
+ add_mutate<T> A;
+ A.a = a;
+ A.b = b;
+ cout << "add_mutate:"
+ << robust_timer(A) << endl;
+ }
+ {
+ ::scale<T> A;
+ A.a = a;
+ A.b = 1;
+ cout << "scale:"
+ << robust_timer(A) << endl;
+ }
+ {
+ scale_mutate<T> A;
+ A.a = a;
+ A.b = 1;
+ cout << "scale_mutate:"
+ << robust_timer(A) << endl;
+ }
+ {
+ mult<T> A;
+ A.a = a;
+ A.b = b;
+ cout << "mult:"
+ << robust_timer(A) << endl;
+ }
+ {
+ mult_mutate<T> A;
+ A.a = a;
+ A.b = b;
+ cout << "mult_mutate:"
+ << robust_timer(A) << endl;
+ }
+
+}
+
+#include <valarray>
+#include <2geom/orphan-code/sbasisN.h>
+#include <2geom/piecewise.h>
+int main(int /*argc*/, char** /*argv*/) {
+
+ {
+ nop N;
+ cout << "nop:" << robust_timer(N) << endl;
+ }
+
+ vector<SBasis> sbs;
+ valarray<double> va(4), vb(4);
+ generate_random_sbasis(sbs);
+ cout << "double\n";
+ basic_arith(sbs[0][0][0], sbs[1][0][0]);
+ cout << "valarray\n";
+ basic_arith(va, vb);
+ //cout << "Linear\n";
+ //basic_arith(sbs[0][0], sbs[1][0]);
+ cout << "SBasis\n";
+ basic_arith(sbs[0], sbs[1]);
+ cout << "pw<SBasis>\n";
+ basic_arith(Piecewise<SBasis>(sbs[0]), Piecewise<SBasis>(sbs[1]));
+ /*cout << "SBasisN<1>\n";
+ SBasisN<1> sbnA = sbs[0];
+ SBasisN<1> sbnB = sbs[0];
+ basic_arith(sbnA, sbnB);*/
+}
diff --git a/tests/utest.h b/tests/utest.h
new file mode 100644
index 0000000..eda1eb4
--- /dev/null
+++ b/tests/utest.h
@@ -0,0 +1,134 @@
+#ifndef SEEN_UTEST_UTEST_H
+#define SEEN_UTEST_UTEST_H
+
+/* Ultra-minimal unit testing framework */
+/* This file is in the public domain */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <setjmp.h>
+//#include <glib/gstrfuncs.h> /* g_strdup_printf */
+#ifdef __cplusplus
+};
+#endif
+
+jmp_buf utest__jmp_buf;
+int utest__tests;
+int utest__passed;
+int utest__running;
+const char *utest__name;
+
+/** \brief Initializes the framework for running a series of tests.
+ * \param name A descriptive label for this series of tests.
+ */
+void utest_start(const char *name) {
+ printf("Testing %s...\n", name);
+ utest__name = name;
+ utest__tests = utest__passed = 0;
+ utest__running = 0;
+}
+
+void utest__pass(void) {
+ utest__passed++;
+ utest__running = 0;
+ printf("OK\n");
+}
+
+
+/** \brief Write \a a, \a b, \a c, and exit the current block of tests.
+ *
+ * In the current implementation, any of \a a, \a b, \a c may be NULL, considered equivalent to
+ * empty string; but don't rely on that unless you also change this documentation string. (No
+ * callers use this functionality at the time of writing.)
+ *
+ * No newline needed in the arguments.
+ */
+int
+utest__fail(const char *a, const char *b, const char *c)
+{
+ utest__running = 0;
+ fflush(stdout);
+ fprintf (stderr, "%s%s%s\n",
+ (a ? a : ""),
+ (b ? b : ""),
+ (c ? c : ""));
+ fflush(stderr);
+ longjmp(utest__jmp_buf, 0);
+ return 0;
+}
+
+
+/** \brief Marks a C block constituting a single test.
+ * \param name A descriptive name for this test.
+ *
+ * The block effectively becomes a try statement; if code within the
+ * block triggers an assertion, control will resume at the end of the
+ * block.
+ */
+#define UTEST_TEST(name) if (!setjmp(utest__jmp_buf)&&utest__test((name)))
+
+/** \brief Terminates the current test if \a cond evaluates to nonzero.
+ * \param cond The condition to test.
+ */
+#define UTEST_ASSERT(cond) UTEST_NAMED_ASSERT( #cond, (cond))
+
+/** \brief Terminates the current tests if \a _cond evaluates to nonzero,
+ * and prints a descriptive \a _name instead of the condition
+ * that caused it to fail.
+ * \param _name The descriptive label to use.
+ * \param _cond The condition to test.
+ */
+#define UTEST_NAMED_ASSERT(_name, _cond) static_cast<void>((_cond) || utest__fail("Assertion `", (_name), "' failed"))
+
+#define UTEST_ASSERT_SHOW(_cond, _printf_args) \
+ static_cast<void>((_cond) \
+ || (utest__fail("\nAssertion `" #_cond "' failed; ", "", \
+ g_strdup_printf _printf_args)))
+
+int utest__test(const char *name) {
+ utest__tests++;
+ if (utest__running) {
+ utest__pass();
+ }
+ printf("\t%s...", name);
+ fflush(stdout);
+ utest__running = 1;
+ return 1;
+}
+
+/** \brief Ends a series of tests, reporting test statistics.
+ *
+ * Test statistics are printed to stdout or stderr, then the function returns
+ * nonzero iff all the tests have passed, zero otherwise.
+ */
+int utest_end(void) {
+ if (utest__running) {
+ utest__pass();
+ }
+ if ( utest__passed == utest__tests ) {
+ printf("%s: OK (all %d passed)\n",
+ utest__name, utest__tests);
+ return 1;
+ } else {
+ fflush(stdout);
+ fprintf(stderr, "%s: FAILED (%d/%d tests passed)\n",
+ utest__name, utest__passed, utest__tests);
+ fflush(stderr);
+ return 0;
+ }
+}
+
+
+#endif /* !SEEN_UTEST_UTEST_H */
+
+/*
+ Local Variables:
+ mode:c
+ c-file-style:"linux"
+ fill-column:99
+ End:
+*/
+// vim: filetype=c:noexpandtab:shiftwidth=8:tabstop=8:fileencoding=utf-8:textwidth=99 :